diff --git a/DEPS b/DEPS index 3aaa010..2747ac8a 100644 --- a/DEPS +++ b/DEPS
@@ -308,11 +308,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'src_internal_revision': '3dff8792f0d51d1790b512505334cdc668e54606', + 'src_internal_revision': 'f50f32ae0208beb1302fec22582ab6c8d36ba5eb', # 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': 'b40fdf12342f60a32930e1cc5758380d0c786756', + 'skia_revision': '293de35a9d1e046bf919f32ec6eb693f82ee4df9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -320,7 +320,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': '67fc293ab0b33e82cc151286f22d55a0781e9e86', + 'angle_revision': 'e229afada1d2af012b7ec55395d10f53f1ebfe60', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -328,7 +328,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': '2c66e07e9c3b52feee720c03aa11984895f09803', + 'pdfium_revision': 'fde20e170bebdde902d38dd577a0543e11b6d4d4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. @@ -347,7 +347,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling googletest # and whatever else without interference from each other. - 'googletest_revision': 'b1a777f31913f8a047f43b2a5f823e736e7f5082', + 'googletest_revision': '5197b1a8e6a1ef9f214f4aa537b0be17cbf91946', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling lighttpd # and whatever else without interference from each other. @@ -387,7 +387,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling chromium_variations # and whatever else without interference from each other. - 'chromium_variations_revision': '9bbacc037e69792d5f5c70a389ce780e35b71f29', + 'chromium_variations_revision': '5951e69a2333e36fbd7912bd160f4686347e6c5f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -427,7 +427,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': '5864d1bef534d0e54f64d489c865db7f76deb2fe', + 'dawn_revision': '37f756f60fb233f9fa1d622c3fe277816baab4cc', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -827,7 +827,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '29f78538308b953b929c2d0fa89c036d889ad135', + 'f022452f20012a03b32a2db96eeda986d644287f', 'condition': 'checkout_android and checkout_src_internal', }, @@ -982,7 +982,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'xqtv9T9O_VlqI8q1LyR_YXaaVp40OQok0fUUuiXgGPAC', + 'version': 'W0fbeG8jMjuHI7-r6nQ8WsaiVJB9Jk4koL6eqkKa-OwC', }, ], 'condition': 'checkout_android', @@ -1192,13 +1192,13 @@ }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '8f6d774a8d8ddcd5a4dc6e8aac06a35576c2b113', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '609288a46b37758cbbfd82aefc01359631aec81f', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), 'src/third_party/devtools-frontend-internal': { - 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '5674bb7b0c13888a0222377953c082b1abf53fbd', + 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '1c8f3583f1e10d91097074d41aba0b8f78837ae1', 'condition': 'checkout_src_internal', }, @@ -1851,7 +1851,7 @@ Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'eaa860a0b3a9049d2caf88cb1ce9d7fb82fb05f2', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '83a1c92bb409cb16dfa2cb60f7f3064db388aca6', + Var('webrtc_git') + '/src.git' + '@' + 'f2cdbc9b0717e67821826f7ff93ce7fe0e9485c3', # 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. @@ -2018,7 +2018,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/projector_app/app', - 'version': '922v3fUxqABuiuLoOKxgLPXgjnE9ZZWSMsBsTXVNzNEC', + 'version': 'ds89jlakvYT-oXHipZIUSEQz8MgH15qf0_dcDINkHzIC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -3935,7 +3935,7 @@ 'src/chrome/browser/platform_experience/win': { 'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' + - '065a550502a2de7532a2b3d8fb926af407611ab4', + 'e2261501b3a54bb01cd879dfc0d9297a9a26aa0e', 'condition': 'checkout_src_internal', }, @@ -4125,7 +4125,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - 'b52f647176b2a69101f532235ff01e26a03708c2', + 'a5aea7848cb458595e3fc715475154bc54ca53cd', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/android_webview/browser/aw_field_trials.cc b/android_webview/browser/aw_field_trials.cc index c2759dd4..ed2af211 100644 --- a/android_webview/browser/aw_field_trials.cc +++ b/android_webview/browser/aw_field_trials.cc
@@ -111,7 +111,7 @@ // Disable Shared Storage on WebView. aw_feature_overrides.DisableFeature(blink::features::kSharedStorageAPI); - aw_feature_overrides.DisableFeature(blink::features::kSharedStorageAPIM124); + aw_feature_overrides.DisableFeature(blink::features::kSharedStorageAPIM125); // Disable scrollbar-color on WebView. aw_feature_overrides.DisableFeature(blink::features::kScrollbarColor);
diff --git a/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc b/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc index 103e79c..bd40ecb8 100644 --- a/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc +++ b/android_webview/browser/safe_browsing/aw_ping_manager_factory.cc
@@ -39,6 +39,10 @@ // Never fetch the access token for android_webview since ESB is unsupported auto get_should_fetch_access_token = base::BindRepeating([]() { return false; }); + // Persisted report is not supported on WebView, because only download reports + // are persisted and WebView doesn't have download protection. + auto get_should_send_persisted_report = + base::BindRepeating([]() { return false; }); return PingManager::Create( safe_browsing::GetV4ProtocolConfig(GetProtocolConfigClientName(), /*disable_auto_update=*/false), @@ -51,7 +55,9 @@ // threading the user population through for client reports /*get_user_population_callback=*/base::NullCallback(), /*get_page_load_token_callback_=*/base::NullCallback(), - /*hats_delegate=*/nullptr, /*persister_root_path=*/context->GetPath()); + /*hats_delegate=*/nullptr, /*persister_root_path=*/context->GetPath(), + /*get_should_send_persisted_report=*/ + std::move(get_should_send_persisted_report)); } std::string AwPingManagerFactory::GetProtocolConfigClientName() const {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java index 6c1d1ff..fe5452b 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java
@@ -26,6 +26,7 @@ import org.chromium.android_webview.AwContents; import org.chromium.android_webview.AwContentsClient; +import org.chromium.android_webview.AwContentsClient.AwWebResourceRequest; import org.chromium.android_webview.AwSettings; import org.chromium.android_webview.policy.AwPolicyProvider; import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedErrorHelper; @@ -35,6 +36,7 @@ import org.chromium.base.test.util.Criteria; import org.chromium.base.test.util.CriteriaHelper; import org.chromium.base.test.util.CriteriaNotSatisfiedException; +import org.chromium.base.test.util.DoNotBatch; import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.components.policy.AbstractAppRestrictionsProvider; @@ -52,10 +54,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** Tests for the WebViewClient.shouldOverrideUrlLoading() method. */ +@DoNotBatch(reason = "This test class is historically prone to flakes.") @RunWith(Parameterized.class) @UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class) public class AwContentsClientShouldOverrideUrlLoadingTest extends AwParameterizedTest { @@ -69,13 +74,13 @@ "com.android.browser:EnterpriseAuthenticationAppLinkPolicy"; private TestWebServer mWebServer; - private TestAwContentsClient mContentsClient; + private ShouldOverrideUrlLoadingClient mContentsClient; private AwTestContainerView mTestContainerView; private AwContents mAwContents; private TestAwContentsClient.ShouldOverrideUrlLoadingHelper mShouldOverrideUrlLoadingHelper; - private static class TestDefaultContentsClient extends TestAwContentsClient { + private static class TestDefaultContentsClient extends ShouldOverrideUrlLoadingClient { @Override public boolean hasWebViewClient() { return false; @@ -96,12 +101,39 @@ mWebServer.shutdown(); } + private static class ShouldOverrideUrlLoadingClient extends TestAwContentsClient { + private final BlockingQueue<AwWebResourceRequest> mShouldOverrideUrlLoadingQueue = + new LinkedBlockingQueue<>(); + private final BlockingQueue<String> mOnPageFinishedQueue = new LinkedBlockingQueue<>(); + + @Override + public boolean shouldOverrideUrlLoading(AwWebResourceRequest request) { + boolean value = super.shouldOverrideUrlLoading(request); + mShouldOverrideUrlLoadingQueue.offer(request); + return value; + } + + @Override + public void onPageFinished(String url) { + super.onPageFinished(url); + mOnPageFinishedQueue.offer(url); + } + + public AwWebResourceRequest waitForShouldOverrideUrlLoading() throws Exception { + return AwActivityTestRule.waitForNextQueueElement(mShouldOverrideUrlLoadingQueue); + } + + public String waitForOnPageFinished() throws Exception { + return AwActivityTestRule.waitForNextQueueElement(mOnPageFinishedQueue); + } + } + private void standardSetup() { - setupWithProvidedContentsClient(new TestAwContentsClient()); + setupWithProvidedContentsClient(new ShouldOverrideUrlLoadingClient()); mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper(); } - private void setupWithProvidedContentsClient(TestAwContentsClient contentsClient) { + private void setupWithProvidedContentsClient(ShouldOverrideUrlLoadingClient contentsClient) { mContentsClient = contentsClient; mTestContainerView = mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient); mAwContents = mTestContainerView.getAwContents(); @@ -725,6 +757,29 @@ Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); } + private void waitForRedirectsToFinish(String redirectUrl, String redirectTarget) { + // Drain the onPageFinished callback queue until we get the final expected onPageFinished + // callback. + CriteriaHelper.pollInstrumentationThread( + () -> { + String url = mContentsClient.waitForOnPageFinished(); + if (redirectUrl.equals(url)) { + // We will sometimes receive onPageFinished for the first page load (such as + // for "delayed" JavaScript redirects), but may not receive this for + // "immediate" JavaScript redirects or 302 server-side redirects. + return false; + } + if (redirectTarget.equals(url)) { + // This should be the last onPageFinished callback. + return true; + } + Assert.fail("Received an unexpected URL from onPageFinished: " + url); + return false; // This is unreached, but the compiler still needs a return value. + }, + AwActivityTestRule.WAIT_TIMEOUT_MS, + AwActivityTestRule.CHECK_INTERVAL); + } + /** * Worker method for the various redirect tests. * @@ -757,60 +812,75 @@ // on whether it was a real click done by the user, or has it been done by JS; on click, // both the initial navigation and the redirect are reported via // shouldOverrideUrlLoading. - int directLoadCallCount = mShouldOverrideUrlLoadingHelper.getCallCount(); mActivityTestRule.loadUrlSync( mAwContents, mContentsClient.getOnPageFinishedHelper(), redirectUrl); - - mShouldOverrideUrlLoadingHelper.waitForCallback(directLoadCallCount, 1); - Assert.assertEquals( - redirectTarget, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl()); - Assert.assertEquals(serverSideRedirect, mShouldOverrideUrlLoadingHelper.isRedirect()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); + AwWebResourceRequest request = mContentsClient.waitForShouldOverrideUrlLoading(); + Assert.assertEquals(redirectTarget, request.url); + Assert.assertEquals(serverSideRedirect, request.isRedirect); + Assert.assertFalse(request.hasUserGesture); + Assert.assertTrue(request.isOutermostMainFrame); + waitForRedirectsToFinish(redirectUrl, redirectTarget); // Test clicking with JS, hasUserGesture must be false. int indirectLoadCallCount = mShouldOverrideUrlLoadingHelper.getCallCount(); mActivityTestRule.loadUrlSync( mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithLinkToRedirectUrl); - Assert.assertEquals(indirectLoadCallCount, mShouldOverrideUrlLoadingHelper.getCallCount()); + // This assertion is redundant because loadUrlSync already waits for onPageFinished, + // however we still need to call waitForOnPageFinished() in order to drain the queue. + Assert.assertEquals( + "Expected onPageFinished for pageWithLinkToRedirectUrl", + pageWithLinkToRedirectUrl, + mContentsClient.waitForOnPageFinished()); + Assert.assertEquals( + "shouldOverrideUrlLoading should not be invoked during loadUrlSync", + indirectLoadCallCount, + mShouldOverrideUrlLoadingHelper.getCallCount()); clickOnLinkUsingJs(); - mShouldOverrideUrlLoadingHelper.waitForCallback(indirectLoadCallCount, 1); - Assert.assertEquals( - redirectUrl, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); - mShouldOverrideUrlLoadingHelper.waitForCallback(indirectLoadCallCount + 1, 1); - Assert.assertEquals( - redirectTarget, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl()); - Assert.assertEquals(serverSideRedirect, mShouldOverrideUrlLoadingHelper.isRedirect()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); + request = mContentsClient.waitForShouldOverrideUrlLoading(); + Assert.assertEquals(redirectUrl, request.url); + Assert.assertFalse(request.isRedirect); + Assert.assertFalse(request.hasUserGesture); + Assert.assertTrue(request.isOutermostMainFrame); - // Make sure the redirect target page has finished loading. - mActivityTestRule.pollUiThread(() -> !mAwContents.getTitle().equals(pageTitle)); + request = mContentsClient.waitForShouldOverrideUrlLoading(); + Assert.assertEquals(redirectTarget, request.url); + Assert.assertEquals(serverSideRedirect, request.isRedirect); + Assert.assertFalse(request.hasUserGesture); + Assert.assertTrue(request.isOutermostMainFrame); + waitForRedirectsToFinish(redirectUrl, redirectTarget); + indirectLoadCallCount = mShouldOverrideUrlLoadingHelper.getCallCount(); - mActivityTestRule.loadUrlAsync(mAwContents, pageWithLinkToRedirectUrl); + mActivityTestRule.loadUrlSync( + mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithLinkToRedirectUrl); + // This assertion is redundant because loadUrlSync already waits for onPageFinished, + // however we still need to call waitForOnPageFinished() in order to drain the queue. + Assert.assertEquals( + "Expected onPageFinished for pageWithLinkToRedirectUrl", + pageWithLinkToRedirectUrl, + mContentsClient.waitForOnPageFinished()); mActivityTestRule.pollUiThread(() -> mAwContents.getTitle().equals(pageTitle)); - Assert.assertEquals(indirectLoadCallCount, mShouldOverrideUrlLoadingHelper.getCallCount()); + Assert.assertEquals( + "shouldOverrideUrlLoading should not be invoked during loadUrlSync", + indirectLoadCallCount, + mShouldOverrideUrlLoadingHelper.getCallCount()); // Simulate touch, hasUserGesture must be true only on the first call. JSUtils.clickNodeWithUserGesture(mAwContents.getWebContents(), "link"); - mShouldOverrideUrlLoadingHelper.waitForCallback(indirectLoadCallCount, 1); - Assert.assertEquals( - redirectUrl, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.hasUserGesture()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); - mShouldOverrideUrlLoadingHelper.waitForCallback(indirectLoadCallCount + 1, 1); - Assert.assertEquals( - redirectTarget, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl()); - Assert.assertEquals(serverSideRedirect, mShouldOverrideUrlLoadingHelper.isRedirect()); - Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture()); - Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame()); + request = mContentsClient.waitForShouldOverrideUrlLoading(); + Assert.assertEquals(redirectUrl, request.url); + Assert.assertFalse(request.isRedirect); + Assert.assertTrue(request.hasUserGesture); + Assert.assertTrue(request.isOutermostMainFrame); + + request = mContentsClient.waitForShouldOverrideUrlLoading(); + Assert.assertEquals(redirectTarget, request.url); + Assert.assertEquals(serverSideRedirect, request.isRedirect); + Assert.assertFalse(request.hasUserGesture); + Assert.assertTrue(request.isOutermostMainFrame); + waitForRedirectsToFinish(redirectUrl, redirectTarget); } @Test @@ -913,7 +983,7 @@ @SmallTest @Feature({"AndroidWebView"}) public void testCallDestroyInCallback() throws Throwable { - class DestroyInCallbackClient extends TestAwContentsClient { + class DestroyInCallbackClient extends ShouldOverrideUrlLoadingClient { @Override public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) { mAwContents.destroy(); @@ -947,7 +1017,7 @@ @SmallTest @Feature({"AndroidWebView", "Navigation"}) public void testReloadingUrlDoesNotBreakBackForwardList() throws Throwable { - class ReloadInCallbackClient extends TestAwContentsClient { + class ReloadInCallbackClient extends ShouldOverrideUrlLoadingClient { @Override public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) { super.shouldOverrideUrlLoading(request); @@ -1003,7 +1073,7 @@ @Feature({"AndroidWebView"}) public void testCallStopAndLoadJsInCallback() throws Throwable { final String globalJsVar = "window.testCallStopAndLoadJsInCallback"; - class StopInCallbackClient extends TestAwContentsClient { + class StopInCallbackClient extends ShouldOverrideUrlLoadingClient { @Override public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) { mAwContents.stopLoading(); @@ -1052,7 +1122,7 @@ httpPath, CommonResources.makeHtmlPageWithSimpleLinkTo( getTestPageCommonHeaders(), ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL)); - class StopInCallbackClient extends TestAwContentsClient { + class StopInCallbackClient extends ShouldOverrideUrlLoadingClient { @Override public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) { mAwContents.loadUrl(httpPathOnServer); @@ -1341,7 +1411,7 @@ private static final String BAD_SCHEME = "badscheme://"; // AwContentsClient handling an invalid network scheme - private static class BadSchemeClient extends TestAwContentsClient { + private static class BadSchemeClient extends ShouldOverrideUrlLoadingClient { CountDownLatch mLatch = new CountDownLatch(1); @Override
diff --git a/ash/capture_mode/capture_mode_game_dashboard_unittests.cc b/ash/capture_mode/capture_mode_game_dashboard_unittests.cc index c95a12d..6753daa 100644 --- a/ash/capture_mode/capture_mode_game_dashboard_unittests.cc +++ b/ash/capture_mode/capture_mode_game_dashboard_unittests.cc
@@ -26,9 +26,11 @@ #include "ash/shell.h" #include "ash/strings/grit/ash_strings.h" #include "ash/style/pill_button.h" +#include "ash/system/unified/feature_tile.h" #include "ash/test/ash_test_base.h" #include "ash/test/ash_test_util.h" #include "ash/wm/desks/desks_test_util.h" +#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" #include "base/system/sys_info.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" @@ -1110,6 +1112,40 @@ histogram_name, /*sample=*/EndRecordingReason::kGameToolbarStopRecordingButton, /*expected_count=*/1); + + // Testing stop recording by the tablet mode enum. + // Game dashboard is not available on the tablet mode. + if (GetParam()) { + return; + } + + histogram_tester_.ExpectBucketCount( + histogram_name, + /*sample=*/EndRecordingReason::kGameDashboardTabletMode, + /*expected_count=*/0); + auto game_dashboard_test_api = std::make_unique<GameDashboardContextTestApi>( + GameDashboardController::Get()->GetGameDashboardContext(game_window()), + GetEventGenerator()); + + game_dashboard_test_api->OpenTheMainMenu(); + LeftClickOn(game_dashboard_test_api->GetMainMenuRecordGameTile()); + // Clicking on the record game tile closes the main menu, and asynchronously + // starts the capture session. Run until idle to ensure that the posted task + // runs synchronously and completes before proceeding. + base::RunLoop().RunUntilIdle(); + LeftClickOn(GetStartRecordingButton()); + WaitForRecordingToStart(); + EXPECT_TRUE(CaptureModeController::Get()->is_recording_in_progress()); + TabletModeControllerTestApi().EnterTabletMode(); + WaitForCaptureFileToBeSaved(); + // The histogram name becomes + // "ash.CaptureModeController.EndRecordingReason.TabletMode"; + histogram_tester_.ExpectBucketCount( + BuildHistogramName(kHistogramNameBase, + test_api.GetBehavior(BehaviorType::kDefault), + /*append_ui_mode_suffix=*/true), + /*sample=*/EndRecordingReason::kGameDashboardTabletMode, + /*expected_count=*/1); } TEST_P(GameDashboardCaptureModeHistogramTest,
diff --git a/ash/capture_mode/capture_mode_metrics.h b/ash/capture_mode/capture_mode_metrics.h index 6f05df8..bd9ae75 100644 --- a/ash/capture_mode/capture_mode_metrics.h +++ b/ash/capture_mode/capture_mode_metrics.h
@@ -42,7 +42,8 @@ kKeyboardShortcut, kGameDashboardStopRecordingButton, kGameToolbarStopRecordingButton, - kMaxValue = kGameToolbarStopRecordingButton, + kGameDashboardTabletMode, + kMaxValue = kGameDashboardTabletMode, }; // Enumeration of capture bar buttons that can be pressed while in capture mode.
diff --git a/ash/events/peripheral_customization_event_rewriter.cc b/ash/events/peripheral_customization_event_rewriter.cc index 8806917..0a1ebdbb 100644 --- a/ash/events/peripheral_customization_event_rewriter.cc +++ b/ash/events/peripheral_customization_event_rewriter.cc
@@ -1133,7 +1133,8 @@ auto modifier_key = ConvertDomCodeToModifierKey(key_event.code()); int key_event_characteristic_flag = ConvertKeyCodeToFlags(key_event.key_code()); - if (settings && modifier_key) { + // Modifiers only need to be remapped now if the rewriter fix is disabled. + if (!features::IsKeyboardRewriterFixEnabled() && settings && modifier_key) { auto iter = settings->modifier_remappings.find(*modifier_key); if (iter != settings->modifier_remappings.end()) { key_event_characteristic_flag = ConvertModifierKeyToFlags(iter->second);
diff --git a/ash/events/peripheral_customization_event_rewriter_unittest.cc b/ash/events/peripheral_customization_event_rewriter_unittest.cc index fdff73e..4d7e6f22 100644 --- a/ash/events/peripheral_customization_event_rewriter_unittest.cc +++ b/ash/events/peripheral_customization_event_rewriter_unittest.cc
@@ -911,6 +911,10 @@ TEST_F(PeripheralCustomizationEventRewriterTest, RemappedModifierReleasedDuringSequence) { + // This test is only relevant when the keyboard rewriter fix is disabled. + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature(features::kEnableKeyboardRewriterFix); + keyboard_->settings->modifier_remappings[ui::mojom::ModifierKey::kAlt] = ui::mojom::ModifierKey::kControl;
diff --git a/ash/game_dashboard/game_dashboard_context.cc b/ash/game_dashboard/game_dashboard_context.cc index b47ff2b..c4991a8 100644 --- a/ash/game_dashboard/game_dashboard_context.cc +++ b/ash/game_dashboard/game_dashboard_context.cc
@@ -39,6 +39,7 @@ #include "ui/base/l10n/time_format.h" #include "ui/compositor/layer.h" #include "ui/events/event.h" +#include "ui/events/keycodes/keyboard_codes_posix.h" #include "ui/events/types/event_type.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" @@ -105,6 +106,21 @@ /*animate=*/true); } +// Determines whether a given `key_code` will interact with the toolbar. +bool WillToolbarViewProcessKeyCode(const ui::KeyboardCode key_code) { + switch (key_code) { + case ui::VKEY_RIGHT: + case ui::VKEY_LEFT: + case ui::VKEY_UP: + case ui::VKEY_DOWN: + case ui::VKEY_RETURN: + case ui::VKEY_SPACE: + return true; + default: + return false; + } +} + } // namespace GameDashboardContext::GameDashboardContext(aura::Window* game_window) @@ -262,6 +278,7 @@ std::move(widget_delegate))); main_menu_widget_->AddObserver(this); main_menu_widget_->Show(); + game_dashboard_utils::UpdateAccessibilityTree(GetTraversableWidgets()); game_dashboard_button_->SetToggled(true); AddCursorHandler(); RecordGameDashboardToggleMainMenu(app_id_, toggle_method, @@ -302,6 +319,7 @@ MaybeUpdateToolbarWidgetBounds(); toolbar_widget_->ShowInactive(); + game_dashboard_utils::UpdateAccessibilityTree(GetTraversableWidgets()); // Display the toolbar behind the main menu view. EnsureMainMenuAboveToolbar(); RecordGameDashboardToolbarToggleState(app_id_, /*toggled_on=*/true); @@ -317,6 +335,7 @@ DCHECK(toolbar_widget_); toolbar_view_ = nullptr; toolbar_widget_.reset(); + game_dashboard_utils::UpdateAccessibilityTree(GetTraversableWidgets()); RecordGameDashboardToolbarToggleState(app_id_, /*toggled_on=*/false); } @@ -427,8 +446,26 @@ void GameDashboardContext::OnEvent(ui::Event* event) { // Close the main menu if the user clicks outside of both the main menu // widget and the Game Dashboard button. - if (main_menu_widget_) { - switch (event->type()) { + auto event_type = event->type(); + if (event_type == ui::ET_KEY_PRESSED) { + const ui::KeyEvent* key_event = event->AsKeyEvent(); + if (toolbar_widget_ && toolbar_widget_->IsActive() && main_menu_widget_ && + WillToolbarViewProcessKeyCode(key_event->key_code())) { + // Close the main menu if the toolbar processes the given key. + CloseMainMenu(GameDashboardMainMenuToggleMethod::kOthers); + } else if (ShouldNavigateToNewWidget(key_event)) { + const auto* currently_focused = views::Widget::GetWidgetForNativeWindow( + static_cast<aura::Window*>(event->target())); + const bool reverse = event->IsShiftDown(); + + // Manually move focus from the currently focused widget to the next in + // the widget list. + MoveFocus(game_dashboard_utils::GetNextWidgetToFocus( + GetTraversableWidgets(), currently_focused, reverse), + event, reverse); + } + } else if (main_menu_widget_) { + switch (event_type) { case ui::ET_TOUCH_PRESSED: case ui::ET_MOUSE_PRESSED: { // TODO(b/328852471): Update logic to compare event target with native @@ -756,6 +793,7 @@ DCHECK(main_menu_view_); RemoveCursorHandler(); main_menu_view_ = nullptr; + game_dashboard_utils::UpdateAccessibilityTree(GetTraversableWidgets()); game_dashboard_button_->SetToggled(false); } @@ -765,4 +803,66 @@ } } +bool GameDashboardContext::ShouldNavigateToNewWidget( + const ui::KeyEvent* event) const { + // Tab navigation between Game Dashboard sibling widgets is only supported + // when the GD button is enabled. + if (!game_dashboard_button_->GetEnabled() || + event->type() != ui::ET_KEY_PRESSED || + event->key_code() != ui::VKEY_TAB) { + return false; + } + + if (auto* target_widget = views::Widget::GetWidgetForNativeWindow( + static_cast<aura::Window*>(event->target()))) { + if (auto* focus_manager = target_widget->GetFocusManager()) { + // If `GetNextFocusableView` returns null, navigation has reached the last + // focusable view in the given direction. + return !(focus_manager->GetNextFocusableView( + /*starting_view=*/focus_manager->GetFocusedView(), + /*starting_widget=*/target_widget, + /*reverse=*/event->IsShiftDown(), + /*dont_loop=*/true)); + } + } + + return false; +} + +std::vector<views::Widget*> GameDashboardContext::GetTraversableWidgets() + const { + std::vector<views::Widget*> widget_list; + widget_list.emplace_back(game_dashboard_button_widget_.get()); + if (main_menu_widget_) { + widget_list.emplace_back(main_menu_widget_.get()); + } + if (toolbar_widget_) { + widget_list.emplace_back(toolbar_widget_.get()); + } + if (widget_list.size() == 1) { + // If the toolbar and main menu widgets don't exist but focus is placed on + // the Game Dashboard button, manually move focus to the game window to + // avoid tab support looping just the Game Dashboard button. + widget_list.emplace_back( + views::Widget::GetWidgetForNativeWindow(game_window_.get())); + } + + return widget_list; +} + +void GameDashboardContext::MoveFocus(views::Widget* new_widget, + ui::Event* event, + bool reverse) { + CHECK(new_widget) << "Cannot move focus to a non-existent widget."; + auto* focus_manager = new_widget->GetFocusManager(); + DCHECK(focus_manager) << "Cannot move focus without a focus manager"; + focus_manager->ClearFocus(); + // Avoid having the focus restored to the same view when the parent view + // is refocused. + focus_manager->SetStoredFocusView(nullptr); + focus_manager->AdvanceFocus(reverse); + event->StopPropagation(); + event->SetHandled(); +} + } // namespace ash
diff --git a/ash/game_dashboard/game_dashboard_context.h b/ash/game_dashboard/game_dashboard_context.h index 9b98050b..33a60c1e 100644 --- a/ash/game_dashboard/game_dashboard_context.h +++ b/ash/game_dashboard/game_dashboard_context.h
@@ -224,6 +224,19 @@ // Ensures that the main menu stacks above the toolbar. void EnsureMainMenuAboveToolbar(); + // Determines whether it's required to tab navigate from one Game Dashboard + // widget to another widget. Returns false if tab-navigating within the same + // widget. + bool ShouldNavigateToNewWidget(const ui::KeyEvent* event) const; + + // Returns a list of visible Game Dashboard widgets that are available to be + // traversed. + std::vector<views::Widget*> GetTraversableWidgets() const; + + // Manually moves focus to the `new_widget`. If `reverse` is true, focus will + // move backwards. + void MoveFocus(views::Widget* new_widget, ui::Event* event, bool reverse); + const raw_ptr<aura::Window> game_window_; const std::string app_id_;
diff --git a/ash/game_dashboard/game_dashboard_context_test_api.cc b/ash/game_dashboard/game_dashboard_context_test_api.cc index 8f9a47f..6fa32063 100644 --- a/ash/game_dashboard/game_dashboard_context_test_api.cc +++ b/ash/game_dashboard/game_dashboard_context_test_api.cc
@@ -254,6 +254,13 @@ GameDashboardToolbarView::ToolbarViewId::kScreenshotButton))); } +bool GameDashboardContextTestApi::IsToolbarExpanded() { + auto* toolbar_view = GetToolbarView(); + CHECK(toolbar_view) + << "The toolbar must be opened first before checking expanded state."; + return toolbar_view->is_expanded_; +} + GameDashboardToolbarSnapLocation GameDashboardContextTestApi::GetToolbarSnapLocation() const { return context_->toolbar_snap_location_;
diff --git a/ash/game_dashboard/game_dashboard_context_test_api.h b/ash/game_dashboard/game_dashboard_context_test_api.h index 1a2c570..80578a91 100644 --- a/ash/game_dashboard/game_dashboard_context_test_api.h +++ b/ash/game_dashboard/game_dashboard_context_test_api.h
@@ -108,6 +108,7 @@ IconButton* GetToolbarGameControlsButton(); IconButton* GetToolbarRecordGameButton(); IconButton* GetToolbarScreenshotButton(); + bool IsToolbarExpanded(); // Returns the quadrant that the toolbar is currently placed in. GameDashboardToolbarSnapLocation GetToolbarSnapLocation() const;
diff --git a/ash/game_dashboard/game_dashboard_context_unittest.cc b/ash/game_dashboard/game_dashboard_context_unittest.cc index ac3fb09..d99b374 100644 --- a/ash/game_dashboard/game_dashboard_context_unittest.cc +++ b/ash/game_dashboard/game_dashboard_context_unittest.cc
@@ -742,6 +742,14 @@ EXPECT_EQ(toggled, game_button->toggled()); } + void TabNavigateForward() { + GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_NONE); + } + + void TabNavigateBackward() { + GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN); + } + void PressKeyAndVerify(ui::KeyboardCode key, GameDashboardToolbarSnapLocation desired_location) { GetEventGenerator()->PressAndReleaseKey(key); @@ -1636,6 +1644,133 @@ ASSERT_TRUE(arc_gamepad_button->HasFocus()); } +TEST_F(GameDashboardContextTest, TabNavigationMainMenu) { + // Open the main menu and begin tab navigation. + CreateGameWindow(/*is_arc_window=*/false); + test_api_->OpenTheMainMenu(); + TabNavigateForward(); + + // Verify focus is placed on the main menu's first element then move focus to + // the last element in the main menu. + views::Widget* main_menu_widget = test_api_->GetMainMenuWidget(); + EXPECT_TRUE(main_menu_widget->IsActive()); + EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus()); + main_menu_widget->GetFocusManager()->SetFocusedView( + test_api_->GetMainMenuSettingsButton()); + EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus()); + + // Tab navigate forward and verify focus is placed on the Game Dashboard + // Button. + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate forward and verify focus is placed back on the main menu's + // first element. + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus()); + + // Tab navigate backwards and verify focus is placed back on the Game + // Dashboard button. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate backwards and verify focus is placed on the last element in + // the main menu. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus()); +} + +TEST_F(GameDashboardContextTest, TabNavigationMainMenuAndToolbar) { + // Open the main menu and toolbar, then tab navigate to the last element in + // the main menu. + CreateGameWindow(/*is_arc_window=*/false); + test_api_->OpenTheMainMenu(); + test_api_->OpenTheToolbar(); + TabNavigateForward(); + views::Widget* main_menu_widget = test_api_->GetMainMenuWidget(); + ASSERT_TRUE(main_menu_widget->IsActive()); + ASSERT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus()); + main_menu_widget->GetFocusManager()->SetFocusedView( + test_api_->GetMainMenuSettingsButton()); + ASSERT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus()); + + // Tab navigate forward and verify focus is placed on the first element in the + // toolbar. + TabNavigateForward(); + views::Widget* toolbar_widget = test_api_->GetToolbarWidget(); + EXPECT_TRUE(toolbar_widget->IsActive()); + EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus()); + + // Move focus to the last element in the toolbar, tab navigate forward, and + // verify focus is placed on the Game Dashboard button. + toolbar_widget->GetFocusManager()->SetFocusedView( + test_api_->GetToolbarScreenshotButton()); + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate forward and verify focus is placed back on the main menu's + // first element. + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus()); + + // Tab navigate backwards and verify focus is placed back on the Game + // Dashboard button. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate backwards and verify focus is placed back on the last element + // in the toolbar. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus()); + + // Move focus to the first element in the toolbar, tab navigate backwards, and + // verify focus is placed on the last element in the main menu. + toolbar_widget->GetFocusManager()->SetFocusedView( + test_api_->GetToolbarGamepadButton()); + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus()); +} + +TEST_F(GameDashboardContextTest, TabNavigationToolbar) { + // Open the main menu and toolbar, close the main menu, then begin tab + // navigation. + CreateGameWindow(/*is_arc_window=*/false); + test_api_->OpenTheMainMenu(); + test_api_->OpenTheToolbar(); + test_api_->CloseTheMainMenu(); + test_api_->SetFocusOnToolbar(); + ASSERT_TRUE(test_api_->IsToolbarExpanded()); + TabNavigateForward(); + + // Verify the toolbar is active and has focus. + views::Widget* toolbar_widget = test_api_->GetToolbarWidget(); + EXPECT_TRUE(toolbar_widget->IsActive()); + EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus()); + + // Move focus to the last element in the toolbar, tab navigate forward, and + // verify focus is placed on the Game Dashboard button. + toolbar_widget->GetFocusManager()->SetFocusedView( + test_api_->GetToolbarScreenshotButton()); + ASSERT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus()); + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate forward and verify focus is placed back on the toolbar's + // first element. + TabNavigateForward(); + EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus()); + + // Tab navigate backwards and verify focus is placed back on the Game + // Dashboard button. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus()); + + // Tab navigate backwards and verify focus is placed back on the last element + // in the toolbar. + TabNavigateBackward(); + EXPECT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus()); +} + // ----------------------------------------------------------------------------- // GameTypeGameDashboardContextTest: // Test fixture to test both ARC and GeForceNow game window depending on the @@ -2441,7 +2576,7 @@ .append(kGameDashboardHistogramSeparator) .append(kGameDashboardHistogramOff); - // Toggle on/off main menu by pressing GD button. + // Toggle on/off main menu by pressing Game Dashboard button. test_api_->OpenTheMainMenu(); VerifyToggleMainMenuHistogram( histograms, histogram_name_on,
diff --git a/ash/game_dashboard/game_dashboard_controller.cc b/ash/game_dashboard/game_dashboard_controller.cc index cca81ac..275fa6d 100644 --- a/ash/game_dashboard/game_dashboard_controller.cc +++ b/ash/game_dashboard/game_dashboard_controller.cc
@@ -239,10 +239,8 @@ if (active_recording_context_) { auto* capture_mode_controller = CaptureModeController::Get(); CHECK(capture_mode_controller->is_recording_in_progress()); - // TODO(b/316036118): Update the end recording reason in the capture - // mode. capture_mode_controller->EndVideoRecording( - EndRecordingReason::kGameDashboardStopRecordingButton); + EndRecordingReason::kGameDashboardTabletMode); } MaybeEnableFeatures(/*enable=*/false, GameDashboardMainMenuToggleMethod::kTabletMode);
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.cc b/ash/game_dashboard/game_dashboard_main_menu_view.cc index 648d38ce..ff79ba7 100644 --- a/ash/game_dashboard/game_dashboard_main_menu_view.cc +++ b/ash/game_dashboard/game_dashboard_main_menu_view.cc
@@ -58,12 +58,16 @@ #include "ui/views/border.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/image_button.h" #include "ui/views/controls/focus_ring.h" #include "ui/views/controls/highlight_path_generator.h" +#include "ui/views/controls/label.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout_view.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/layout/flex_layout_view.h" +#include "ui/views/layout/layout_types.h" +#include "ui/views/style/typography_provider.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" #include "ui/views/widget/widget.h" @@ -86,6 +90,10 @@ constexpr float kDetailRowCornerRadius = 16.0f; // Corner radius for feature tiles. constexpr int kTileCornerRadius = 20; +// Line height for feature tiles with sub-labels +constexpr int kTileSublabelLineHeight = 16; +// Line height for feature tiles with no sub-labels +constexpr int kTileLabelLineHeight = 32; constexpr gfx::RoundedCornersF kGCDetailRowCorners = gfx::RoundedCornersF(/*upper_left=*/kDetailRowCornerRadius, @@ -118,6 +126,7 @@ const std::optional<std::u16string>& sub_label) { auto tile = std::make_unique<FeatureTile>(std::move(callback), is_togglable, type); + tile->SetID(id); tile->SetVectorIcon(icon); tile->SetLabel(text); @@ -140,9 +149,57 @@ // Disabled state colors. tile->SetBackgroundDisabledColorId(cros_tokens::kCrosSysSystemOnBaseOpaque); + views::ImageButton* tile_icon = tile->icon_button(); + views::FlexLayoutView* tile_label_container = tile->title_container(); + views::Label* tile_label = tile->label(); + views::Label* tile_sub_label = tile->sub_label(); + + // Readjust Compact Tiles. + if (type == FeatureTile::TileType::kCompact) { + // Adjust internal spacing. + tile->SetProperty(views::kInternalPaddingKey, + gfx::Insets::TLBR(0, 8, 0, 8)); + tile_icon->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(12, 0, 4, 0)); + tile_icon->SetPreferredSize(gfx::Size(20, 20)); + + // Adjust text and icon alignment for text wrapping. + tile_icon->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE); + tile_label_container->SetCrossAxisAlignment( + views::LayoutAlignment::kCenter); + + tile_label_container->SetProperty(views::kMarginsKey, + gfx::Insets::TLBR(0, 0, 10, 0)); + + // Adjust line and text specifications. + tile_label->SetFontList( + TypographyProvider::Get() + ->ResolveTypographyToken(TypographyToken::kCrosAnnotation2) + .DeriveWithSizeDelta(1) + .DeriveWithHeightUpperBound(16)); + tile_sub_label->SetFontList( + TypographyProvider::Get()->ResolveTypographyToken( + TypographyToken::kCrosAnnotation2)); + + tile_label->SetLineHeight(kTileSublabelLineHeight); + tile_label->SetPreferredSize(gfx::Size(80, kTileLabelLineHeight)); + + } else { + // Resize the icon and its margins. + tile_icon->SetPreferredSize( + gfx::Size(20, tile_icon->GetPreferredSize().height())); + tile_icon->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(6, 20, 6, 16)); + + // Adjust line specifications and enable text wrapping. + tile_label->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(0, 0, 0, 15)); + tile_label->SetLineHeight(tile->sub_label() ? kTileSublabelLineHeight + : kTileLabelLineHeight); + tile_label->SetMultiLine(true); + } + if (sub_label.has_value()) { tile->SetSubLabel(sub_label.value()); tile->SetSubLabelVisibility(true); + tile_sub_label->SetLineHeight(kTileSublabelLineHeight); } // Setup focus ring. views::FocusRing::Get(tile.get())->SetColorId(cros_tokens::kCrosSysPrimary);
diff --git a/ash/game_dashboard/game_dashboard_toolbar_view.cc b/ash/game_dashboard/game_dashboard_toolbar_view.cc index 5597e92..aeda597 100644 --- a/ash/game_dashboard/game_dashboard_toolbar_view.cc +++ b/ash/game_dashboard/game_dashboard_toolbar_view.cc
@@ -33,6 +33,7 @@ #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector2d_conversions.h" +#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/background.h" #include "ui/views/border.h" #include "ui/views/highlight_border.h" @@ -383,6 +384,7 @@ l10n_util::GetStringUTF16( IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_BUTTON_TITLE), /*is_togglable=*/false, /*icon_color=*/cros_tokens::kCrosSysPrimary)); + gamepad_button_->GetViewAccessibility().SetRole(ax::mojom::Role::kButton); UpdateGamepadButtonTooltipText(); MayAddGameControlsTile(); @@ -397,6 +399,8 @@ l10n_util::GetStringUTF16( IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_BUTTON_TITLE), /*is_togglable=*/true)); + record_game_button_->GetViewAccessibility().SetRole( + ax::mojom::Role::kButton); record_game_button_->SetVectorIcon(kGdRecordGameIcon); record_game_button_->SetBackgroundToggledColor(cros_tokens::kCrosSysError); record_game_button_->SetToggledVectorIcon(kCaptureModeCircleStopIcon); @@ -432,6 +436,8 @@ l10n_util::GetStringUTF16( IDS_ASH_GAME_DASHBOARD_CONTROLS_TILE_BUTTON_TITLE), /*is_togglable=*/true)); + game_controls_button_->GetViewAccessibility().SetRole( + ax::mojom::Role::kButton); UpdateViewForGameControls(*flags); }
diff --git a/ash/game_dashboard/game_dashboard_utils.cc b/ash/game_dashboard/game_dashboard_utils.cc index 611867a6..a3238ea 100644 --- a/ash/game_dashboard/game_dashboard_utils.cc +++ b/ash/game_dashboard/game_dashboard_utils.cc
@@ -17,6 +17,7 @@ #include "ui/aura/window.h" #include "ui/base/l10n/l10n_util.h" #include "ui/display/screen.h" +#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/controls/button/button.h" #include "ui/views/view_utils.h" @@ -44,6 +45,26 @@ !display::Screen::GetScreen()->InTabletMode(); } +views::Widget* GetNextWidgetToFocus( + const std::vector<views::Widget*> widget_list, + const views::Widget* focused_widget, + bool reverse) { + if (auto it = + std::find(widget_list.begin(), widget_list.end(), focused_widget); + it != widget_list.end()) { + const int focused_widget_index = + std::distance(widget_list.begin(), + it); // it - widget_list_.begin(); + const size_t widget_list_size = widget_list.size(); + const size_t next_focus_widget_index = + (reverse ? (focused_widget_index + widget_list_size - 1u) + : (focused_widget_index + 1u)) % + widget_list_size; + return widget_list[next_focus_widget_index]; + } + return nullptr; +} + std::optional<ArcGameControlsFlag> GetGameControlsFlag(aura::Window* window) { if (!IsArcWindow(window)) { return std::nullopt; @@ -152,4 +173,23 @@ : 0; } +void UpdateAccessibilityTree(const std::vector<views::Widget*>& widget_list) { + const size_t widget_list_size = widget_list.size(); + if (widget_list_size <= 1u) { + return; + } + + for (size_t i = 0; i < widget_list_size; i++) { + auto* contents_view = widget_list[i]->GetContentsView(); + auto& view_a11y = contents_view->GetViewAccessibility(); + const size_t prev_index = (i + widget_list_size - 1u) % widget_list_size; + const size_t next_index = (i + 1u) % widget_list_size; + + view_a11y.SetPreviousFocus(widget_list[prev_index]); + view_a11y.SetNextFocus(widget_list[next_index]); + contents_view->NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, + /*send_native_event=*/true); + } +} + } // namespace ash::game_dashboard_utils
diff --git a/ash/game_dashboard/game_dashboard_utils.h b/ash/game_dashboard/game_dashboard_utils.h index 43ed949..0a0817da 100644 --- a/ash/game_dashboard/game_dashboard_utils.h +++ b/ash/game_dashboard/game_dashboard_utils.h
@@ -6,6 +6,7 @@ #define ASH_GAME_DASHBOARD_GAME_DASHBOARD_UTILS_H_ #include <optional> +#include <vector> #include "ash/ash_export.h" #include "ash/public/cpp/arc_game_controls_flag.h" @@ -16,6 +17,7 @@ namespace views { class Button; +class Widget; } // namespace views namespace ash::game_dashboard_utils { @@ -40,6 +42,14 @@ // GD features availability dependency may change. ASH_EXPORT bool ShouldEnableFeatures(); +// Returns the next `views::Widget` from the `widget_list` that should be +// focused. This is determined by looking at the currently `focused_widget` and +// whether or not the tab navigation is moving in `reverse`. +ASH_EXPORT views::Widget* GetNextWidgetToFocus( + const std::vector<views::Widget*> widget_list, + const views::Widget* focused_widget, + bool reverse); + // Returns flags value if `window` is an ARC game window. Otherwise, it returns // nullopt. std::optional<ArcGameControlsFlag> GetGameControlsFlag(aura::Window* window); @@ -74,6 +84,12 @@ // header is not found or when the header is invisible. ASH_EXPORT int GetFrameHeaderHeight(aura::Window* window); +// Updates the accessibility tree to match the given `widget_list`. This ensures +// that the order of widgets in the `widget_list` reflects accessibility +// navigation and tab navigation. +ASH_EXPORT void UpdateAccessibilityTree( + const std::vector<views::Widget*>& widget_list); + } // namespace ash::game_dashboard_utils #endif // ASH_GAME_DASHBOARD_GAME_DASHBOARD_UTILS_H_
diff --git a/ash/shelf/hotseat_widget.cc b/ash/shelf/hotseat_widget.cc index 3f5ecd1..571d783b 100644 --- a/ash/shelf/hotseat_widget.cc +++ b/ash/shelf/hotseat_widget.cc
@@ -22,6 +22,7 @@ #include "ash/shell.h" #include "ash/style/system_shadow.h" #include "ash/system/status_area_widget.h" +#include "ash/utility/forest_util.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/overview/overview_observer.h" #include "base/functional/bind.h" @@ -32,6 +33,7 @@ #include "chromeos/constants/chromeos_features.h" #include "ui/aura/scoped_window_targeter.h" #include "ui/aura/window_targeter.h" +#include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/compositor/animation_throughput_reporter.h" #include "ui/compositor/layer_animation_sequence.h" #include "ui/compositor/scoped_layer_animation_settings.h" @@ -440,6 +442,9 @@ // the visibility of shadow. void UpdateHighlightBorder(bool update_corner_radius); + // Returns the target background color for the hotseat. + SkColor GetBackgroundColor(); + void SetTranslucentBackground(const gfx::Rect& translucent_background_bounds); // Sets whether the background should be blurred as requested by the argument, @@ -511,8 +516,9 @@ OverviewController* overview_controller = Shell::Get()->overview_controller(); if (overview_controller) { overview_controller->AddObserver(this); - if (overview_controller->InOverviewSession()) + if (overview_controller->InOverviewSession() && !IsForestFeatureEnabled()) { ++blur_lock_; + } } DCHECK(scrollable_shelf_view); scrollable_shelf_view_ = scrollable_shelf_view; @@ -592,6 +598,20 @@ translucent_background_->SetBorder(std::move(border)); } +SkColor HotseatWidget::DelegateView::GetBackgroundColor() { + auto* widget = GetWidget(); + CHECK(widget); + aura::Window* window = widget->GetNativeWindow(); + // A forest session uses system-on-base. + if (IsForestFeatureEnabled() && + OverviewController::Get()->InOverviewSession() && + !SplitViewController::Get(window)->InSplitViewMode()) { + return widget->GetColorProvider()->GetColor( + cros_tokens::kCrosSysSystemOnBase); + } + return ShelfConfig::Get()->GetDefaultShelfColor(widget); +} + void HotseatWidget::DelegateView::SetTranslucentBackground( const gfx::Rect& background_bounds) { DCHECK(HotseatWidget::ShouldShowHotseatBackground()); @@ -607,12 +627,11 @@ hotseat_widget_->GetTranslucentBackgroundReportCallback()); } - const auto* widget = GetWidget(); - DCHECK(widget); - if (ShelfConfig::Get()->GetDefaultShelfColor(widget) != target_color_) { + SkColor background_color = GetBackgroundColor(); + if (background_color != target_color_) { ui::ScopedLayerAnimationSettings color_animation_setter(animator); DoScopedAnimationSetting(&color_animation_setter); - target_color_ = ShelfConfig::Get()->GetDefaultShelfColor(widget); + target_color_ = background_color; translucent_background_->SetBackground( views::CreateSolidBackground(target_color_)); } @@ -696,6 +715,10 @@ } void HotseatWidget::DelegateView::OnOverviewModeWillStart() { + // Forest uses background blur in overview. + if (IsForestFeatureEnabled()) { + return; + } DCHECK_LE(blur_lock_, 2); SetBackgroundBlur(false); @@ -704,6 +727,10 @@ void HotseatWidget::DelegateView::OnOverviewModeEndingAnimationComplete( bool canceled) { + // Forest uses background blur in overview. + if (IsForestFeatureEnabled()) { + return; + } DCHECK_GT(blur_lock_, 0); --blur_lock_;
diff --git a/ash/shelf/hotseat_widget_unittest.cc b/ash/shelf/hotseat_widget_unittest.cc index 427f82e..08a861a6 100644 --- a/ash/shelf/hotseat_widget_unittest.cc +++ b/ash/shelf/hotseat_widget_unittest.cc
@@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "ash/shelf/hotseat_widget.h" + #include <memory> #include <tuple> #include <vector> @@ -11,13 +13,13 @@ #include "ash/app_list/views/app_list_view.h" #include "ash/assistant/assistant_controller_impl.h" #include "ash/constants/ash_features.h" +#include "ash/constants/ash_switches.h" #include "ash/focus_cycler.h" #include "ash/public/cpp/assistant/controller/assistant_ui_controller.h" #include "ash/public/cpp/test/assistant_test_api.h" #include "ash/public/cpp/test/shell_test_api.h" #include "ash/shelf/drag_window_from_shelf_controller_test_api.h" #include "ash/shelf/home_button.h" -#include "ash/shelf/hotseat_widget.h" #include "ash/shelf/scrollable_shelf_view.h" #include "ash/shelf/shelf.h" #include "ash/shelf/shelf_app_button.h" @@ -215,8 +217,7 @@ GetEventGenerator()->GestureTapAt(overview_button_center); } - private: - friend class StackedHotseatWidgetTest; + protected: const ShelfAutoHideBehavior shelf_auto_hide_behavior_; const bool is_assistant_enabled_; const bool navigation_buttons_shown_in_tablet_mode_; @@ -224,6 +225,23 @@ base::test::ScopedFeatureList scoped_feature_list_; }; +class HotseatWidgetForestTest : public HotseatWidgetTest { + public: + HotseatWidgetForestTest() { switches::SetIgnoreForestSecretKeyForTest(true); } + + ~HotseatWidgetForestTest() { + switches::SetIgnoreForestSecretKeyForTest(false); + } + + // HotseatWidgetTest: + void SetupFeatureLists() override { + scoped_feature_list_.InitWithFeatureStates( + {{features::kHideShelfControlsInTabletMode, + !navigation_buttons_shown_in_tablet_mode()}, + {features::kForestFeature, true}}); + } +}; + class StackedHotseatWidgetTest : public HotseatWidgetTest { public: void SetupFeatureLists() override { @@ -349,6 +367,15 @@ INSTANTIATE_TEST_SUITE_P( All, + HotseatWidgetForestTest, + testing::Combine( + testing::Values(ShelfAutoHideBehavior::kNever, + ShelfAutoHideBehavior::kAlways), + /*is_assistant_enabled*/ testing::Bool(), + /*navigation_buttons_shown_in_tablet_mode*/ testing::Bool())); + +INSTANTIATE_TEST_SUITE_P( + All, StackedHotseatWidgetTest, testing::Combine( testing::Values(ShelfAutoHideBehavior::kNever, @@ -988,6 +1015,31 @@ GetShelfWidget()->hotseat_widget()->GetHotseatBackgroundBlurForTest()); } +TEST_P(HotseatWidgetForestTest, EnableBlurDuringOverviewMode) { + TabletModeControllerTestApi().EnterTabletMode(); + + const int expected_blur_radius = ShelfConfig::Get()->shelf_blur_radius(); + ASSERT_EQ( + GetShelfWidget()->hotseat_widget()->GetHotseatBackgroundBlurForTest(), + expected_blur_radius); + + // Go into overview and check that at the end of the animation, background + // blur is still enabled. + StartOverview(); + WaitForOverviewAnimation(/*enter=*/true); + EXPECT_EQ( + GetShelfWidget()->hotseat_widget()->GetHotseatBackgroundBlurForTest(), + expected_blur_radius); + + // Exit overview and check that at the end of the animation, background + // blur is still enabled. + EndOverview(); + WaitForOverviewAnimation(/*enter=*/false); + EXPECT_EQ( + GetShelfWidget()->hotseat_widget()->GetHotseatBackgroundBlurForTest(), + expected_blur_radius); +} + // Tests that releasing the hotseat gesture below the threshold results in a // kHidden hotseat when the shelf is shown. TEST_P(HotseatWidgetTest, ReleasingSlowDragBelowThreshold) {
diff --git a/ash/system/mahi/mahi_constants.h b/ash/system/mahi/mahi_constants.h index 4df1c9b..4dd828b 100644 --- a/ash/system/mahi/mahi_constants.h +++ b/ash/system/mahi/mahi_constants.h
@@ -86,8 +86,14 @@ inline constexpr char kMahiFeedbackHistogramName[] = "Ash.Mahi.Feedback"; inline constexpr char kMahiButtonClickHistogramName[] = "Ash.Mahi.ButtonClicked"; +inline constexpr char kAnswerLoadingTimeHistogramName[] = + "Ash.Mahi.QuestionAnswer.LoadingTime"; +inline constexpr char kSummaryLoadingTimeHistogramName[] = + "Ash.Mahi.Summary.LoadingTime"; inline constexpr char kMahiUserJourneyTimeHistogramName[] = "Ash.Mahi.UserJourneyTime"; +inline constexpr char kMahiQuestionSourceHistogramName[] = + "Ash.Mahi.QuestionSource"; } // namespace ash::mahi_constants
diff --git a/ash/system/mahi/mahi_panel_view_unittest.cc b/ash/system/mahi/mahi_panel_view_unittest.cc index b7451fd..c36a3f3 100644 --- a/ash/system/mahi/mahi_panel_view_unittest.cc +++ b/ash/system/mahi/mahi_panel_view_unittest.cc
@@ -101,8 +101,9 @@ void ReturnDefaultAnswerAsyncly( base::test::TestFuture<void>& waiter, MahiResponseStatus status, - chromeos::MahiManager::MahiAnswerQuestionCallback callback) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + chromeos::MahiManager::MahiAnswerQuestionCallback callback, + base::TimeDelta delay = base::TimeDelta()) { + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce( [](base::OnceClosure unblock_closure, MahiResponseStatus status, @@ -110,7 +111,8 @@ std::move(callback).Run(u"fake answer", status); std::move(unblock_closure).Run(); }, - waiter.GetCallback(), status, std::move(callback))); + waiter.GetCallback(), status, std::move(callback)), + delay); } // Returns `kFakeOutlines` syncly. @@ -142,8 +144,9 @@ void ReturnDefaultSummaryAsyncly( base::test::TestFuture<void>& waiter, MahiResponseStatus status, - chromeos::MahiManager::MahiSummaryCallback callback) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + chromeos::MahiManager::MahiSummaryCallback callback, + base::TimeDelta delay = base::TimeDelta()) { + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce( [](base::OnceClosure unblock_closure, MahiResponseStatus status, @@ -151,7 +154,8 @@ std::move(callback).Run(u"fake summary", status); std::move(unblock_closure).Run(); }, - waiter.GetCallback(), status, std::move(callback))); + waiter.GetCallback(), status, std::move(callback)), + delay); } // Returns a long summary. @@ -223,16 +227,20 @@ // Creates a widget hosting `MahiPanelView`. Recreates if there is one. void CreatePanelWidget() { - // Avoid creating a dangling pointer. - panel_view_ = nullptr; - - widget_.reset(); + ResetPanelWidget(); widget_ = CreateFramelessTestWidget(); widget_->SetFullscreen(true); panel_view_ = widget_->SetContentsView( std::make_unique<MahiPanelView>(&ui_controller_)); } + void ResetPanelWidget() { + // Avoid creating a dangling pointer. + panel_view_ = nullptr; + + widget_.reset(); + } + // Submit a test question by setting the text in the textfield and click send // button. void SubmitTestQuestion(const std::u16string& question = u"fake question") { @@ -613,6 +621,67 @@ EXPECT_FALSE(outlines_container->GetVisible()); } +TEST_F(MahiPanelViewTest, SummaryLoadingAnimationsMetricsRecord) { + // Reset the default panel to avoid unnecessary histogram record. + ResetPanelWidget(); + + base::HistogramTester histogram_tester; + auto delay_time = base::Milliseconds(100); + + // Config the mock mahi manager to return a summary asyncly. + base::test::TestFuture<void> summary_waiter; + ON_CALL(mock_mahi_manager(), GetSummary) + .WillByDefault([&summary_waiter, delay_time]( + chromeos::MahiManager::MahiSummaryCallback callback) { + ReturnDefaultSummaryAsyncly( + summary_waiter, MahiResponseStatus::kSuccess, std::move(callback), + /*delay=*/delay_time); + }); + + MahiPanelView mahi_view(ui_controller()); + + histogram_tester.ExpectTimeBucketCount( + mahi_constants::kSummaryLoadingTimeHistogramName, delay_time, 0); + + // Test that loading time metrics is recorded when summary is loaded. + ASSERT_TRUE(summary_waiter.WaitAndClear()); + histogram_tester.ExpectTimeBucketCount( + mahi_constants::kSummaryLoadingTimeHistogramName, delay_time, 1); + + // Test that loading time metrics is recorded when the content is refreshed. + ui_controller()->RefreshContents(); + ASSERT_TRUE(summary_waiter.Wait()); + histogram_tester.ExpectTimeBucketCount( + mahi_constants::kSummaryLoadingTimeHistogramName, delay_time, 2); +} + +TEST_F(MahiPanelViewTest, AnswerLoadingAnimationsMetricsRecord) { + base::HistogramTester histogram_tester; + auto delay_time = base::Milliseconds(100); + + // Config the mock mahi manager to return an answer asyncly. + base::test::TestFuture<void> answer_waiter; + EXPECT_CALL(mock_mahi_manager(), AnswerQuestion) + .WillOnce( + [&answer_waiter, delay_time]( + const std::u16string& question, bool current_panel_content, + chromeos::MahiManager::MahiAnswerQuestionCallback callback) { + ReturnDefaultAnswerAsyncly( + answer_waiter, MahiResponseStatus::kSuccess, + std::move(callback), /*delay=*/delay_time); + }); + + SubmitTestQuestion(); + + histogram_tester.ExpectTimeBucketCount( + mahi_constants::kAnswerLoadingTimeHistogramName, delay_time, 0); + + // Test that loading time metrics is recorded when a question is answered. + ASSERT_TRUE(answer_waiter.Wait()); + histogram_tester.ExpectTimeBucketCount( + mahi_constants::kAnswerLoadingTimeHistogramName, delay_time, 1); +} + // Tests that pressing on the send button with a valid textfield takes the user // to the Q&A View and the back to summary outlines button that appears on top // takes the user back to the main view. @@ -1123,9 +1192,12 @@ std::move(callback)); }); + base::HistogramTester histogram_tester; const std::u16string question(u"A question that brings errors"); SubmitTestQuestion(question); - + histogram_tester.ExpectBucketCount( + mahi_constants::kMahiQuestionSourceHistogramName, + MahiUiController::QuestionSource::kPanel, 1); Mock::VerifyAndClearExpectations(&mock_mahi_manager()); // After a question is posted and before an answer is loaded, the Q&A view @@ -1195,7 +1267,13 @@ /*callback=*/_)); EXPECT_CALL(mock_mahi_manager(), GetOutlines).Times(0); EXPECT_CALL(mock_mahi_manager(), GetSummary).Times(0); + histogram_tester.ExpectBucketCount( + mahi_constants::kMahiQuestionSourceHistogramName, + MahiUiController::QuestionSource::kRetry, 0); GetEventGenerator()->ClickLeftButton(); + histogram_tester.ExpectBucketCount( + mahi_constants::kMahiQuestionSourceHistogramName, + MahiUiController::QuestionSource::kRetry, 1); Mock::VerifyAndClear(&mock_mahi_manager()); }
diff --git a/ash/system/mahi/mahi_question_answer_view.cc b/ash/system/mahi/mahi_question_answer_view.cc index b562e432..21a313f 100644 --- a/ash/system/mahi/mahi_question_answer_view.cc +++ b/ash/system/mahi/mahi_question_answer_view.cc
@@ -24,6 +24,8 @@ #include "base/check.h" #include "base/functional/bind.h" #include "base/logging.h" +#include "base/metrics/histogram_functions.h" +#include "base/time/time.h" #include "chromeos/components/mahi/public/cpp/mahi_manager.h" #include "components/vector_icons/vector_icons.h" #include "ui/base/l10n/l10n_util.h" @@ -239,6 +241,10 @@ case MahiUiUpdateType::kAnswerLoaded: RemoveLoadingAnimatedImage(); + base::UmaHistogramTimes( + mahi_constants::kAnswerLoadingTimeHistogramName, + base::TimeTicks::Now() - answer_start_loading_time_); + AddChildView( CreateQuestionAnswerRow(update.GetAnswer(), /*is_question=*/false)); return; @@ -298,6 +304,8 @@ answer_loading_animated_image_.SetView(answer_loading_animated_image); + answer_start_loading_time_ = base::TimeTicks::Now(); + return; } case MahiUiUpdateType::kQuestionReAsked: {
diff --git a/ash/system/mahi/mahi_question_answer_view.h b/ash/system/mahi/mahi_question_answer_view.h index 018cd7d..9da617f 100644 --- a/ash/system/mahi/mahi_question_answer_view.h +++ b/ash/system/mahi/mahi_question_answer_view.h
@@ -7,6 +7,7 @@ #include "ash/ash_export.h" #include "ash/system/mahi/mahi_ui_controller.h" +#include "base/time/time.h" #include "chromeos/components/mahi/public/cpp/mahi_manager.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/layout/flex_layout_view.h" @@ -52,6 +53,10 @@ // an answer. The image is created when waiting and destroyed when the answer // is loaded. views::ViewTracker answer_loading_animated_image_; + + // Records the time when `answer_loading_animated_image_` starts showing and + // playing the animation. Used for metrics collection. + base::TimeTicks answer_start_loading_time_; }; BEGIN_VIEW_BUILDER(ASH_EXPORT, MahiQuestionAnswerView, views::FlexLayoutView)
diff --git a/ash/system/mahi/mahi_ui_controller.cc b/ash/system/mahi/mahi_ui_controller.cc index 76bfdf7a..a467f9a 100644 --- a/ash/system/mahi/mahi_ui_controller.cc +++ b/ash/system/mahi/mahi_ui_controller.cc
@@ -4,8 +4,10 @@ #include "ash/system/mahi/mahi_ui_controller.h" +#include "ash/system/mahi/mahi_constants.h" #include "ash/system/mahi/mahi_ui_update.h" #include "base/logging.h" +#include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "chromeos/components/mahi/public/cpp/mahi_manager.h" #include "ui/views/view.h" @@ -91,6 +93,9 @@ void MahiUiController::SendQuestion(const std::u16string& question, bool current_panel_content, QuestionSource source) { + base::UmaHistogramEnumeration( + mahi_constants::kMahiQuestionSourceHistogramName, source); + if (source != QuestionSource::kRetry) { most_recent_question_params_.emplace(question, current_panel_content); }
diff --git a/ash/system/mahi/mahi_ui_controller.h b/ash/system/mahi/mahi_ui_controller.h index 1061b9b9..fb76c32e 100644 --- a/ash/system/mahi/mahi_ui_controller.h +++ b/ash/system/mahi/mahi_ui_controller.h
@@ -48,6 +48,8 @@ }; // Lists question sources. + // Note: this should be kept in sync with `MahiQuestionSource` enum in + // tools/metrics/histograms/metadata/ash/enums.xml enum class QuestionSource { // From the Mahi menu view. kMenuView, @@ -57,6 +59,8 @@ // From the retry button. kRetry, + + kMaxValue = kRetry, }; MahiUiController();
diff --git a/ash/system/mahi/summary_outlines_section.cc b/ash/system/mahi/summary_outlines_section.cc index 9c7efe39..8b21b68 100644 --- a/ash/system/mahi/summary_outlines_section.cc +++ b/ash/system/mahi/summary_outlines_section.cc
@@ -13,6 +13,8 @@ #include "ash/system/mahi/resources/grit/mahi_resources.h" #include "base/check.h" #include "base/check_is_test.h" +#include "base/metrics/histogram_functions.h" +#include "base/time/time.h" #include "chromeos/components/mahi/public/cpp/mahi_manager.h" #include "chromeos/strings/grit/chromeos_strings.h" #include "chromeos/ui/vector_icons/vector_icons.h" @@ -191,6 +193,9 @@ outlines_loading_animated_image_->SetVisible(false); // TODO(b/330643995): Show the outlines section once it is ready. outlines_container_->SetVisible(false); + + // TODO(b/333916944): Add metrics recording the outline loading animation time + // here. } void SummaryOutlinesSection::HandleSummaryLoaded( @@ -199,6 +204,9 @@ summary_label_->SetText(summary_text); summary_loading_animated_image_->Stop(); summary_loading_animated_image_->SetVisible(false); + + base::UmaHistogramTimes(mahi_constants::kSummaryLoadingTimeHistogramName, + base::Time::Now() - summary_start_loading_time_); } void SummaryOutlinesSection::LoadSummaryAndOutlines() { @@ -227,6 +235,8 @@ *outlines_loading_animated_image_->animated_image()->skottie(), IDR_MAHI_LOADING_OUTLINES_ANIMATION)); + summary_start_loading_time_ = base::Time::Now(); + ui_controller_->UpdateSummaryAndOutlines(); }
diff --git a/ash/system/mahi/summary_outlines_section.h b/ash/system/mahi/summary_outlines_section.h index 97cb10a..558396bb 100644 --- a/ash/system/mahi/summary_outlines_section.h +++ b/ash/system/mahi/summary_outlines_section.h
@@ -12,6 +12,7 @@ #include "ash/ash_export.h" #include "ash/system/mahi/mahi_ui_controller.h" #include "base/memory/raw_ptr.h" +#include "base/time/time.h" #include "chromeos/components/mahi/public/cpp/mahi_manager.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/layout/box_layout_view.h" @@ -57,6 +58,8 @@ raw_ptr<views::AnimatedImageView> outlines_loading_animated_image_ = nullptr; raw_ptr<views::Label> summary_label_ = nullptr; raw_ptr<views::View> outlines_container_ = nullptr; + + base::Time summary_start_loading_time_; }; BEGIN_VIEW_BUILDER(ASH_EXPORT, SummaryOutlinesSection, views::BoxLayoutView)
diff --git a/ash/system/phonehub/phone_hub_ui_controller.cc b/ash/system/phonehub/phone_hub_ui_controller.cc index b7f2cffc..df68c9f 100644 --- a/ash/system/phonehub/phone_hub_ui_controller.cc +++ b/ash/system/phonehub/phone_hub_ui_controller.cc
@@ -351,6 +351,8 @@ phone_hub_manager_->GetPhoneHubStructuredMetricsLogger() ->LogPhoneHubUiStateUpdated( phonehub::PhoneHubUiState::kDisconnected); + phone_hub_manager_->GetPhoneHubStructuredMetricsLogger() + ->ResetSessionId(); } break; case UiState::kPhoneConnecting:
diff --git a/ash/system/unified/feature_tile.h b/ash/system/unified/feature_tile.h index ea1be931..3818d3b 100644 --- a/ash/system/unified/feature_tile.h +++ b/ash/system/unified/feature_tile.h
@@ -196,6 +196,7 @@ TileType tile_type() { return type_; } bool is_icon_clickable() const { return is_icon_clickable_; } views::ImageButton* icon_button() { return icon_button_; } + views::FlexLayoutView* title_container() const { return title_container_; } views::Label* label() { return label_; } views::Label* sub_label() { return sub_label_; } views::ImageView* drill_in_arrow() { return drill_in_arrow_; }
diff --git a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts index 52bdb6dc..7eda4bd 100644 --- a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts +++ b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
@@ -549,7 +549,8 @@ * The fake does not use the status list parameter, the fake data is never * updated. */ - startCalibration(): Promise<{stateResult: StateResult}> { + startCalibration(_components: CalibrationComponentStatus[]): + Promise<{stateResult: StateResult}> { return this.getNextStateForMethod( 'startCalibration', State.kCheckCalibration); }
diff --git a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.ts b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.ts index bed9cba..b2991a4e 100644 --- a/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.ts +++ b/ash/webui/shimless_rma/resources/reimaging_calibration_failed_page.ts
@@ -369,6 +369,10 @@ disableNextButton(this); } } + + getComponentsListForTesting(): CalibrationComponentStatus[] { + return this.getComponentsList(); + } } declare global {
diff --git a/ash/wm/overview/overview_drop_target.cc b/ash/wm/overview/overview_drop_target.cc index fbb05108..e4873441 100644 --- a/ash/wm/overview/overview_drop_target.cc +++ b/ash/wm/overview/overview_drop_target.cc
@@ -143,6 +143,10 @@ return nullptr; } +bool OverviewDropTarget::ShouldHaveShadow() const { + return false; +} + void OverviewDropTarget::UpdateRoundedCornersAndShadow() {} void OverviewDropTarget::SetOpacity(float opacity) {}
diff --git a/ash/wm/overview/overview_drop_target.h b/ash/wm/overview/overview_drop_target.h index 98f278d..b28daef 100644 --- a/ash/wm/overview/overview_drop_target.h +++ b/ash/wm/overview/overview_drop_target.h
@@ -48,6 +48,7 @@ void EnsureVisible() override; std::vector<OverviewFocusableView*> GetFocusableViews() const override; views::View* GetBackDropView() const override; + bool ShouldHaveShadow() const override; void UpdateRoundedCornersAndShadow() override; void SetOpacity(float opacity) override; float GetOpacity() const override;
diff --git a/ash/wm/overview/overview_group_item.cc b/ash/wm/overview/overview_group_item.cc index d3f8773..fb9bea1 100644 --- a/ash/wm/overview/overview_group_item.cc +++ b/ash/wm/overview/overview_group_item.cc
@@ -293,6 +293,10 @@ return overview_group_container_view_; } +bool OverviewGroupItem::ShouldHaveShadow() const { + return overview_items_.size() > 1u; +} + void OverviewGroupItem::UpdateRoundedCornersAndShadow() { for (const auto& overview_item : overview_items_) { overview_item->UpdateRoundedCorners(); @@ -422,12 +426,16 @@ for (const auto& item : overview_items_) { if (item && item.get() != overview_item) { + // Remove the group-level shadow and apply it on the window-level to + // ensure that the shadow bounds get updated properly. + item->set_eligible_for_shadow_config(/*eligible_for_shadow_config=*/true); + OverviewItemView* item_view = item->overview_item_view(); item_view->ResetRoundedCorners(); } } - overview_grid_->PositionWindows(/*animate=*/false); + overview_grid_->PositionWindows(/*animate=*/true); } void OverviewGroupItem::HandleDragEvent(const gfx::PointF& location_in_screen) { @@ -445,7 +453,7 @@ desks_util::GetActiveDeskContainerForRoot(overview_grid_->root_window()), "OverviewGroupItemWidget", /*accept_events=*/true)); - ConfigureTheShadow(); + CreateShadow(); overview_group_container_view_ = item_widget_->SetContentsView( std::make_unique<OverviewGroupContainerView>(this));
diff --git a/ash/wm/overview/overview_group_item.h b/ash/wm/overview/overview_group_item.h index d23d350..16e2aa6 100644 --- a/ash/wm/overview/overview_group_item.h +++ b/ash/wm/overview/overview_group_item.h
@@ -57,6 +57,7 @@ void EnsureVisible() override; std::vector<OverviewFocusableView*> GetFocusableViews() const override; views::View* GetBackDropView() const override; + bool ShouldHaveShadow() const override; void UpdateRoundedCornersAndShadow() override; void SetOpacity(float opacity) override; float GetOpacity() const override;
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc index b1b4a2d4..46ec340 100644 --- a/ash/wm/overview/overview_item.cc +++ b/ash/wm/overview/overview_item.cc
@@ -577,6 +577,10 @@ return overview_item_view_->backdrop_view(); } +bool OverviewItem::ShouldHaveShadow() const { + return eligible_for_shadow_config_; +} + void OverviewItem::UpdateRoundedCornersAndShadow() { UpdateRoundedCorners(); @@ -1148,7 +1152,7 @@ wm::SetWindowVisibilityAnimationTransition(widget_window, wm::ANIMATE_NONE); if (eligible_for_shadow_config_) { - ConfigureTheShadow(); + CreateShadow(); } overview_item_view_ =
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h index 10a1f09..2d0b142f 100644 --- a/ash/wm/overview/overview_item.h +++ b/ash/wm/overview/overview_item.h
@@ -72,6 +72,10 @@ OverviewItemView* overview_item_view() { return overview_item_view_; } + void set_eligible_for_shadow_config(bool eligible_for_shadow_config) { + eligible_for_shadow_config_ = eligible_for_shadow_config; + } + // Handles events forwarded from the contents view. void OnFocusedViewActivated(); void OnFocusedViewClosed(); @@ -107,6 +111,7 @@ gfx::RectF GetTransformedBounds() const override; std::vector<OverviewFocusableView*> GetFocusableViews() const override; views::View* GetBackDropView() const override; + bool ShouldHaveShadow() const override; void UpdateRoundedCornersAndShadow() override; void SetOpacity(float opacity) override; float GetOpacity() const override; @@ -233,8 +238,10 @@ // If true, `shadow_` is eligible to be created, false otherwise. The shadow // should not be created if `this` is hosted by an `OverviewGroupItem` // together with another `OverviewItem` (the group-level shadow will be - // installed instead). - const bool eligible_for_shadow_config_; + // installed instead). However if a window inside an `OverviewGroupItem` is + // destroyed, `eligible_for_shadow_config_` is set to true to ensure the + // shadow bounds get updated correctly. + bool eligible_for_shadow_config_; // The view associated with |item_widget_|. Contains a title, close button and // maybe a backdrop. Forwards certain events to |this|.
diff --git a/ash/wm/overview/overview_item_base.cc b/ash/wm/overview/overview_item_base.cc index ec9bdf9..5d655cf 100644 --- a/ash/wm/overview/overview_item_base.cc +++ b/ash/wm/overview/overview_item_base.cc
@@ -68,16 +68,26 @@ } void OverviewItemBase::RefreshShadowVisuals(bool shadow_visible) { - // Shadow is normally turned off during animations and reapplied when on - // animation complete. On destruction, `shadow_` is cleaned up before - // `transform_window_`, which may call this function, so early exit if - // `shadow_` is nullptr. + const bool should_have_shadow = ShouldHaveShadow(); + if (should_have_shadow != !!shadow_) { + if (should_have_shadow) { + CreateShadow(); + } else { + shadow_.reset(); + } + } + + // On destruction, `shadow_` is cleaned up before `transform_window_`, which + // may call this function, so early exit if `shadow_` is nullptr. if (!shadow_) { return; } const gfx::RectF shadow_bounds_in_screen = target_bounds_; auto* shadow_layer = shadow_->GetLayer(); + + // Shadow is normally turned off during animations and reapplied when on + // animation complete. if (!shadow_visible || shadow_bounds_in_screen.IsEmpty()) { shadow_layer->SetVisible(false); return; @@ -93,7 +103,9 @@ } void OverviewItemBase::UpdateShadowTypeForDrag(bool is_dragging) { - shadow_->SetType(is_dragging ? kDraggedShadowType : kDefaultShadowType); + if (shadow_) { + shadow_->SetType(is_dragging ? kDraggedShadowType : kDefaultShadowType); + } } void OverviewItemBase::HandleGestureEventForTabletModeLayout( @@ -241,7 +253,7 @@ return params; } -void OverviewItemBase::ConfigureTheShadow() { +void OverviewItemBase::CreateShadow() { shadow_ = SystemShadow::CreateShadowOnNinePatchLayer( kDefaultShadowType, SystemShadow::LayerRecreatedCallback()); auto* shadow_layer = shadow_->GetLayer();
diff --git a/ash/wm/overview/overview_item_base.h b/ash/wm/overview/overview_item_base.h index 62ac1b8..830e6cf 100644 --- a/ash/wm/overview/overview_item_base.h +++ b/ash/wm/overview/overview_item_base.h
@@ -204,6 +204,9 @@ // Returns the backdrop view of `this`. virtual views::View* GetBackDropView() const = 0; + // Returns true if `shadow_` should be created on the item, false otherwise. + virtual bool ShouldHaveShadow() const = 0; + // Updates the rounded corners and shadow on `this`. virtual void UpdateRoundedCornersAndShadow() = 0; @@ -307,8 +310,10 @@ target_bounds_ = target_bounds; } + SystemShadow* shadow_for_testing() { return shadow_.get(); } + gfx::Rect get_shadow_content_bounds_for_testing() const { - return shadow_.get()->GetContentBounds(); + return shadow_ ? shadow_.get()->GetContentBounds() : gfx::Rect(); } RoundedLabelWidget* get_cannot_snap_widget_for_testing() { @@ -328,7 +333,7 @@ // Creates the `shadow_` and stacks the shadow layer to be at the bottom after // `item_widget_` has been created. - void ConfigureTheShadow(); + void CreateShadow(); // Drag event can be handled differently based on the concreate instance of // `this`. For `OverviewItem`, the drag will be on window-level. For
diff --git a/ash/wm/snap_group/snap_group.cc b/ash/wm/snap_group/snap_group.cc index 21994c1..d55fdd0 100644 --- a/ash/wm/snap_group/snap_group.cc +++ b/ash/wm/snap_group/snap_group.cc
@@ -139,22 +139,25 @@ SnapGroupController::Get()->RemoveSnapGroup(this); } -void SnapGroup::OnWindowParentChanged(aura::Window* window, - aura::Window* parent) { +void SnapGroup::OnWindowAddedToRootWindow(aura::Window* window) { DCHECK(window == window1_ || window == window2_); // Skip any recursive updates during the other window move. - if (is_moving_display_ || !parent) { + if (is_moving_display_) { return; } base::AutoReset<bool> lock(&is_moving_display_, true); // Hide the divider, then move the other window to the same display as the // moved `window`. + const bool old_visibility = snap_group_divider_.divider_widget()->IsVisible(); snap_group_divider_.SetVisible(false); window_util::MoveWindowToDisplay( window == window1_ ? window2_ : window1_, - display::Screen::GetScreen()->GetDisplayNearestWindow(parent).id()); - // Re-show the divider after both windows are moved to the target display. - snap_group_divider_.SetVisible(true); + display::Screen::GetScreen() + ->GetDisplayNearestWindow(window->GetRootWindow()) + .id()); + // Re-show the divider if needed after both windows are moved to the target + // display. + snap_group_divider_.SetVisible(old_visibility); ApplyPrimarySnapRatio(WindowState::Get(window1_)->snap_ratio().value_or( chromeos::kDefaultSnapRatio)); }
diff --git a/ash/wm/snap_group/snap_group.h b/ash/wm/snap_group/snap_group.h index fddf31c..517c5e0 100644 --- a/ash/wm/snap_group/snap_group.h +++ b/ash/wm/snap_group/snap_group.h
@@ -63,8 +63,7 @@ // aura::WindowObserver: void OnWindowDestroying(aura::Window* window) override; - void OnWindowParentChanged(aura::Window* window, - aura::Window* parent) override; + void OnWindowAddedToRootWindow(aura::Window* window) override; // WindowStateObserver: void OnPreWindowStateTypeChange(WindowState* window_state,
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc index c6785d3b..1d886c0 100644 --- a/ash/wm/snap_group/snap_group_unittest.cc +++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -3299,6 +3299,18 @@ std::unique_ptr<aura::Window> w1(CreateAppWindow()); SnapTwoTestWindows(w0.get(), w1.get()); + // Create more windows to ensure the position of the `OverviewGroupItem` needs + // to be updated during the Overview grid re-layout since the Overview grid + // layout is left-aligned. + std::unique_ptr<aura::Window> w2( + CreateAppWindow(gfx::Rect(100, 100, 200, 100))); + std::unique_ptr<aura::Window> w3( + CreateAppWindow(gfx::Rect(200, 200, 100, 200))); + std::unique_ptr<aura::Window> w4( + CreateAppWindow(gfx::Rect(100, 200, 200, 300))); + std::unique_ptr<aura::Window> w5( + CreateAppWindow(gfx::Rect(200, 100, 300, 200))); + OverviewController* overview_controller = Shell::Get()->overview_controller(); overview_controller->StartOverview(OverviewStartAction::kTests, OverviewEnterExitType::kImmediateEnter); @@ -3307,18 +3319,28 @@ GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); ASSERT_TRUE(overview_grid); const auto& window_list = overview_grid->window_list(); - ASSERT_EQ(window_list.size(), 1u); + ASSERT_EQ(window_list.size(), 5u); + + OverviewGroupItem* overview_group_item = + static_cast<OverviewGroupItem*>(window_list[4].get()); + const auto& overview_items = + overview_group_item->overview_items_for_testing(); + ASSERT_EQ(overview_items.size(), 2u); w0.reset(); - EXPECT_EQ(window_list.size(), 1u); + EXPECT_EQ(window_list.size(), 5u); + EXPECT_EQ(overview_items.size(), 1u); - // Verify that the shadow bounds will be refreshed to fit with the remaining + // Verify that the group-level shadow will be reset and the window-level + // shadow bounds of the remaining item is refreshed to fit with the remaining // item. - auto& overview_item = window_list[0]; - const auto shadow_content_bounds = - overview_item->get_shadow_content_bounds_for_testing(); - EXPECT_EQ(shadow_content_bounds.size(), - gfx::ToRoundedSize(overview_item->target_bounds().size())); + auto* group_shadow = overview_group_item->shadow_for_testing(); + EXPECT_FALSE(group_shadow); + + auto* window1_shadow = overview_items[0]->shadow_for_testing(); + ASSERT_TRUE(window1_shadow); + EXPECT_EQ(gfx::ToRoundedSize(overview_group_item->target_bounds().size()), + window1_shadow->GetContentBounds().size()); } // Tests the basic functionality of focus cycling in overview through tabbing,
diff --git a/base/message_loop/message_pump_win.cc b/base/message_loop/message_pump_win.cc index f212eb9a..b136708 100644 --- a/base/message_loop/message_pump_win.cc +++ b/base/message_loop/message_pump_win.cc
@@ -160,7 +160,7 @@ // See MessageLoopTest.PostDelayedTaskFromSystemPump for an example. // TODO(gab): This could potentially be replaced by a ForegroundIdleProc hook // if Windows ends up being the only platform requiring ScheduleDelayedWork(). - if (nested_state_ != NestedState::kNone && + if (nested_state_ == NestedState::kNestedNativeLoopAnnounced && !native_msg_scheduled_.load(std::memory_order_relaxed)) { ScheduleNativeTimer(next_work_info); }
diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h index 33ffd0b4..3607f768 100644 --- a/base/message_loop/message_pump_win.h +++ b/base/message_loop/message_pump_win.h
@@ -187,8 +187,7 @@ // There are no nested message loops running. kNone, // kMsgHaveWork was pumped from a native queue. The state will return to - // `kNormal` whenever DoRunLoop() regains control. In this state, - // ScheduleDelayedWork() will start a native timer. + // `kNormal` whenever DoRunLoop() regains control. // // It is reset to `kNone` when DoRunLoop() gets control back after // ProcessNextWindowsMessage() or DoWork(). @@ -196,9 +195,8 @@ // HandleNestedNativeLoopWithApplicationTasks(true) was called (when a // `ScopedAllowApplicationTasksInNativeNestedLoop` is instantiated). When // running with `event_`, switches to pumping `kMsgHaveWork` MSGs when there - // are application tasks to be done during native runloops. Is a 'superset' - // of `kNestedNativeLoopDetected`, and will also start a native timer when - // ScheduleDelayedWork() is called. + // are application tasks to be done during native runloops. In this state, + // ScheduleDelayedWork() will start a native timer. // // It is reset to `kNone` when: // - DoRunLoop() gets control back after ProcessNextWindowsMessage().
diff --git a/chrome/VERSION b/chrome/VERSION index 80e0cc2..2d2597a 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=125 MINOR=0 -BUILD=6416 +BUILD=6417 PATCH=0
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java index a567a5876..570600cc 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.tab.state; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -45,6 +46,8 @@ @Mock private PersistedTabData.Natives mPersistedTabDataJni; + @Mock Tab mTab; + @Rule public JniMocker jniMocker = new JniMocker(); @Before @@ -171,6 +174,69 @@ verify(mShoppingPersistedTabDataMock, times(1)).disableSaving(); } + @SmallTest + @Test + public void testUninitializedTab() throws TimeoutException { + doReturn(false).when(mTab).isInitialized(); + doReturn(false).when(mTab).isDestroyed(); + doReturn(false).when(mTab).isCustomTab(); + CallbackHelper helper = new CallbackHelper(); + ThreadUtils.runOnUiThreadBlocking( + () -> { + PersistedTabData.from( + mTab, + null, + MockPersistedTabData.class, + (res) -> { + Assert.assertNull(res); + helper.notifyCalled(); + }); + }); + helper.waitForCallback(0); + } + + @SmallTest + @Test + public void testDestroyedTab() throws TimeoutException { + doReturn(true).when(mTab).isInitialized(); + doReturn(true).when(mTab).isDestroyed(); + doReturn(false).when(mTab).isCustomTab(); + CallbackHelper helper = new CallbackHelper(); + ThreadUtils.runOnUiThreadBlocking( + () -> { + PersistedTabData.from( + mTab, + null, + MockPersistedTabData.class, + (res) -> { + Assert.assertNull(res); + helper.notifyCalled(); + }); + }); + helper.waitForCallback(0); + } + + @SmallTest + @Test + public void testCustomTab() throws TimeoutException { + doReturn(true).when(mTab).isInitialized(); + doReturn(false).when(mTab).isDestroyed(); + doReturn(true).when(mTab).isCustomTab(); + CallbackHelper helper = new CallbackHelper(); + ThreadUtils.runOnUiThreadBlocking( + () -> { + PersistedTabData.from( + mTab, + null, + MockPersistedTabData.class, + (res) -> { + Assert.assertNull(res); + helper.notifyCalled(); + }); + }); + helper.waitForCallback(0); + } + static class ThreadVerifierMockPersistedTabData extends MockPersistedTabData { ThreadVerifierMockPersistedTabData(Tab tab) { super(
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index baafaa7..c232eca 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd
@@ -5638,6 +5638,9 @@ <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS" desc="Permission string for chrome.os.diagnostcs API."> Run ChromeOS Flex diagnostic tests </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB" desc="Permission string for chrome.os.diagnostics.network_info_mlab API."> + Collect IP address and network measurement results for Measurement Lab, according to their privacy policy (measurementlab.net/privacy) + </message> <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_EVENTS" desc="Permission string for chrome.os.events API."> Subscribe to ChromeOS Flex system events </message> @@ -5664,6 +5667,9 @@ <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS" desc="Permission string for chrome.os.diagnostcs API."> Run ChromeOS diagnostic tests </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB" desc="Permission string for chrome.os.diagnostics.network_info_mlab API."> + Collect IP address and network measurement results for Measurement Lab, according to their privacy policy (measurementlab.net/privacy) + </message> <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_EVENTS" desc="Permission string for chrome.os.events API."> Subscribe to ChromeOS system events </message>
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB.png.sha1 new file mode 100644 index 0000000..2ad4b14d --- /dev/null +++ b/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB.png.sha1
@@ -0,0 +1 @@ +341f40a9ceb9d9b9f2d738665fe364ae136565a9
diff --git a/chrome/app/settings_chromium_strings.grdp b/chrome/app/settings_chromium_strings.grdp index 7d8bf0b..3d155e7 100644 --- a/chrome/app/settings_chromium_strings.grdp +++ b/chrome/app/settings_chromium_strings.grdp
@@ -265,7 +265,7 @@ Warns you about dangerous sites, even ones Google didn't know about before, by analyzing more data from sites than standard protection. You can choose to skip Chromium warnings. </message> <message name="IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL" desc="The text for a link to a help center article that gives more information about Safe Browsing."> - Learn more about <ph name="BEGIN_LINK"><a href="$1" target="_blank"><ex><a href="$1" target="_blank"></ex></ph>how Chromium keeps your data private<ph name="END_LINK"></a><ex></a></ex></ph> + Learn more about <ph name="BEGIN_LINK"><a href="#" id="enhancedProtectionLearnMoreLink" on-click="onEnhancedProtectionLearnMoreClick_"></ph>how Chromium keeps your data private<ph name="END_LINK"></a><ex></a></ex></ph> </message> <message name="IDS_SETTINGS_SECURE_DNS_DESCRIPTION" desc="Secondary, continued explanation of secure DNS in Privacy options"> Make it harder for people with access to your internet traffic to see which sites you visit. Chromium uses a secure connection to look up a site's IP address in the DNS (Domain Name System).
diff --git a/chrome/app/settings_chromium_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 b/chrome/app/settings_chromium_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 index b029d06..1129811 100644 --- a/chrome/app/settings_chromium_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 +++ b/chrome/app/settings_chromium_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1
@@ -1 +1 @@ -ca5b85e661217f84b4379f7ca38c12a5cf007755 \ No newline at end of file +a0c8c5f67627143214bfdfeb033eb56f46618d11 \ No newline at end of file
diff --git a/chrome/app/settings_google_chrome_strings.grdp b/chrome/app/settings_google_chrome_strings.grdp index e373d7a97..7f59defb 100644 --- a/chrome/app/settings_google_chrome_strings.grdp +++ b/chrome/app/settings_google_chrome_strings.grdp
@@ -258,7 +258,7 @@ Warns you about dangerous sites, even ones Google didn't know about before, by analyzing more data from sites than standard protection. You can choose to skip Chrome warnings. </message> <message name="IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL" desc="The text for a link to a help center article that gives more information about Safe Browsing."> - Learn more about <ph name="BEGIN_LINK"><a href="$1" target="_blank"><ex><a href="$1" target="_blank"></ex></ph>how Chrome keeps your data private<ph name="END_LINK"></a><ex></a></ex></ph> + Learn more about <ph name="BEGIN_LINK"><a href="#" id="enhancedProtectionLearnMoreLink" on-click="onEnhancedProtectionLearnMoreClick_"></ph>how Chrome keeps your data private<ph name="END_LINK"></a><ex></a></ex></ph> </message> <message name="IDS_SETTINGS_SECURE_DNS_DESCRIPTION" desc="Secondary, continued explanation of secure DNS in Privacy options"> Make it harder for people with access to your internet traffic to see which sites you visit. Chrome uses a secure connection to look up a site's IP address in the DNS (Domain Name System).
diff --git a/chrome/app/settings_google_chrome_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 b/chrome/app/settings_google_chrome_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 index a0b16c50..8909aa0 100644 --- a/chrome/app/settings_google_chrome_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1 +++ b/chrome/app/settings_google_chrome_strings_grdp/IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL.png.sha1
@@ -1 +1 @@ -55a272c424b34769b71a9b3604682eb8a39d3eca \ No newline at end of file +2d95dc82f0f93cd6a1c4b3460f3600ab3d81e6fe \ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 6fc5c7a..ff3c5fb3 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -6139,6 +6139,10 @@ kOmniboxStarterPackExpansionVariations, "StarterPackExpansion")}, + {"omnibox-starter-pack-iph", flag_descriptions::kOmniboxStarterPackIPHName, + flag_descriptions::kOmniboxStarterPackIPHDescription, kOsDesktop, + FEATURE_VALUE_TYPE(omnibox::kStarterPackIPH)}, + #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA)
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc index 7cc42a0d..3be52a4 100644 --- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_manager_unittest.cc
@@ -7,6 +7,7 @@ #include <memory> #include "ash/components/arc/test/fake_compatibility_mode_instance.h" +#include "ash/public/cpp/arc_game_controls_flag.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" @@ -14,6 +15,7 @@ #include "base/test/scoped_feature_list.h" #include "chrome/browser/ash/app_list/arc/arc_app_test.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/test/arc_test_window.h" #include "chrome/browser/ash/arc/input_overlay/test/event_capturer.h" @@ -46,6 +48,25 @@ constexpr const gfx::Rect window_bounds = gfx::Rect(10, 10, 100, 100); +// Simulates the feature (if `is_feature` is true) or hint (if `is_feature` is +// false) toggle on `window`. When toggling the feature, it also +// toggles the hint. +void ToggleGameControls(aura::Window* window, bool is_feature) { + const bool toggle_on = + !IsFlagSet(window->GetProperty(ash::kArcGameControlsFlagsKey), + is_feature ? ash::ArcGameControlsFlag::kEnabled + : ash::ArcGameControlsFlag::kHint); + window->SetProperty( + ash::kArcGameControlsFlagsKey, + UpdateFlag(window->GetProperty(ash::kArcGameControlsFlagsKey), + is_feature + ? static_cast<ash::ArcGameControlsFlag>( + /*enable_flag=*/ash::ArcGameControlsFlag::kEnabled | + ash::ArcGameControlsFlag::kHint) + : ash::ArcGameControlsFlag::kHint, + toggle_on)); +} + } // namespace class TestArcInputOverlayManager : public ArcInputOverlayManager { @@ -691,6 +712,174 @@ } } +TEST_P(VersionArcInputOverlayManagerTest, TestHistograms) { + if (!IsBetaVersion()) { + return; + } + + base::HistogramTester histograms; + std::map<MappingSource, int> expected_histogram_values_for_hint_on; + std::map<MappingSource, int> expected_histogram_values_for_hint_off; + std::map<MappingSource, int> expected_histogram_values_for_feature_on; + std::map<MappingSource, int> expected_histogram_values_for_feature_off; + + const std::string feature_on_histogram_name = + BuildGameControlsHistogramName( + base::JoinString( + std::vector<std::string>{kFeatureHistogramName, + kToggleWithMappingSourceHistogram}, + "")) + .append(kGameControlsHistogramSeparator) + .append(kToggleOnHistogramName); + + const std::string feature_off_histogram_name = + BuildGameControlsHistogramName( + base::JoinString( + std::vector<std::string>{kFeatureHistogramName, + kToggleWithMappingSourceHistogram}, + "")) + .append(kGameControlsHistogramSeparator) + .append(kToggleOffHistogramName); + + const std::string hint_on_histogram_name = + BuildGameControlsHistogramName( + base::JoinString( + std::vector<std::string>{kHintHistogramName, + kToggleWithMappingSourceHistogram}, + "")) + .append(kGameControlsHistogramSeparator) + .append(kToggleOnHistogramName); + + const std::string hint_off_histogram_name = + BuildGameControlsHistogramName( + base::JoinString( + std::vector<std::string>{kHintHistogramName, + kToggleWithMappingSourceHistogram}, + "")) + .append(kGameControlsHistogramSeparator) + .append(kToggleOffHistogramName); + + // 1. Test with the default mapping. + auto arc_window = CreateArcWindowSyncAndWait( + task_environment(), ash::Shell::GetPrimaryRootWindow(), window_bounds, + kEnabledPackageName); + // Toggle hint off. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kDefault); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle hint on. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kDefault); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); + // Toggle feature off. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_off, + MappingSource::kDefault); + // Hint is also toggle off with feature toggle off. + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kDefault); + VerifyHistogramValues(histograms, feature_off_histogram_name, + expected_histogram_values_for_feature_off); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle feature on. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_on, + MappingSource::kDefault); + // Hint is also toggle on with feature toggle on. + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kDefault); + VerifyHistogramValues(histograms, feature_on_histogram_name, + expected_histogram_values_for_feature_on); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); + + // 2. Add the default mapping with extra user-added mapping. + auto* injector = GetTouchInjector(arc_window->GetNativeWindow()); + injector->AddNewAction(ActionType::TAP, + arc_window->GetNativeWindow()->bounds().CenterPoint()); + // Toggle hint off. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kDefaultAndUserAdded); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle hint on. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kDefaultAndUserAdded); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); + // Toggle feature off. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_off, + MappingSource::kDefaultAndUserAdded); + // Hint is also toggle off with feature toggle off. + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kDefaultAndUserAdded); + VerifyHistogramValues(histograms, feature_off_histogram_name, + expected_histogram_values_for_feature_off); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle feature on. + ToggleGameControls(arc_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_on, + MappingSource::kDefaultAndUserAdded); + // Hint is also toggle on with feature toggle on. + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kDefaultAndUserAdded); + VerifyHistogramValues(histograms, feature_on_histogram_name, + expected_histogram_values_for_feature_on); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); + + // 3. Test with user-added mapping only. + auto game_window = CreateArcWindowSyncAndWait( + task_environment(), ash::Shell::GetPrimaryRootWindow(), window_bounds, + kRandomGamePackageName); + injector = GetTouchInjector(game_window->GetNativeWindow()); + injector->AddNewAction( + ActionType::TAP, game_window->GetNativeWindow()->bounds().CenterPoint()); + // Toggle hint off. + ToggleGameControls(game_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kUserAdded); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle hint on. + ToggleGameControls(game_window->GetNativeWindow(), /*is_feature=*/false); + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kUserAdded); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); + // Toggle feature off. + ToggleGameControls(game_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_off, + MappingSource::kUserAdded); + // Hint is also toggle off with feature toggle off. + MapIncreaseValueByOne(expected_histogram_values_for_hint_off, + MappingSource::kUserAdded); + VerifyHistogramValues(histograms, feature_off_histogram_name, + expected_histogram_values_for_feature_off); + VerifyHistogramValues(histograms, hint_off_histogram_name, + expected_histogram_values_for_hint_off); + // Toggle feature on. + ToggleGameControls(game_window->GetNativeWindow(), /*is_feature=*/true); + MapIncreaseValueByOne(expected_histogram_values_for_feature_on, + MappingSource::kUserAdded); + // Hint is also toggle on with feature toggle on. + MapIncreaseValueByOne(expected_histogram_values_for_hint_on, + MappingSource::kUserAdded); + VerifyHistogramValues(histograms, feature_on_histogram_name, + expected_histogram_values_for_feature_on); + VerifyHistogramValues(histograms, hint_on_histogram_name, + expected_histogram_values_for_hint_on); +} + INSTANTIATE_TEST_SUITE_P(All, VersionArcInputOverlayManagerTest, ::testing::Bool());
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.cc b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.cc index ad5bf5af..e302003 100644 --- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.cc +++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.cc
@@ -6,6 +6,7 @@ #include "ash/wm/window_state.h" #include "base/metrics/histogram_functions.h" +#include "base/strings/string_util.h" #include "components/ukm/app_source_url_recorder.h" #include "services/metrics/public/cpp/delegating_ukm_recorder.h" #include "services/metrics/public/cpp/ukm_builders.h" @@ -13,6 +14,12 @@ namespace arc::input_overlay { +namespace { + +constexpr char kGameControlsHistogramNameRoot[] = "Arc.GameControls"; + +} // namespace + class InputOverlayUkm { public: static void RecordInputOverlayFeatureState(std::string package_name, @@ -81,6 +88,11 @@ } }; +std::string BuildGameControlsHistogramName(const std::string& name) { + return base::JoinString({kGameControlsHistogramNameRoot, name}, + kGameControlsHistogramSeparator); +} + void RecordInputOverlayFeatureState(const std::string& package_name, bool enable) { base::UmaHistogramBoolean("Arc.InputOverlay.FeatureState", enable); @@ -134,4 +146,38 @@ package_name, reposition_type, state_type); } +void RecordEditingListFunctionTriggered(EditingListFunction function) { + base::UmaHistogramEnumeration( + BuildGameControlsHistogramName(kEditingListFunctionTriggeredHistogram), + function); +} + +void RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction function) { + base::UmaHistogramEnumeration( + BuildGameControlsHistogramName( + kButtonOptionsMenuFunctionTriggeredHistogram), + function); +} + +void RecordEditDeleteMenuFunctionTriggered(EditDeleteMenuFunction function) { + base::UmaHistogramEnumeration( + BuildGameControlsHistogramName(kEditDeleteMenuFunctionTriggeredHistogram), + function); +} + +void RecordToggleWithMappingSource(bool is_feature, + bool is_on, + MappingSource source) { + base::UmaHistogramEnumeration( + BuildGameControlsHistogramName( + base::JoinString( + {(is_feature ? kFeatureHistogramName : kHintHistogramName), + kToggleWithMappingSourceHistogram}, + "")) + .append(kGameControlsHistogramSeparator) + .append(is_on ? kToggleOnHistogramName : kToggleOffHistogramName), + source); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h index e4ac06e..d8c9d5e3 100644 --- a/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h +++ b/chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h
@@ -11,13 +11,80 @@ namespace arc::input_overlay { +inline constexpr char kEditingListFunctionTriggeredHistogram[] = + "EditingListFunctionTriggered"; +inline constexpr char kButtonOptionsMenuFunctionTriggeredHistogram[] = + "ButtonOptionsMenuFunctionTriggered"; +inline constexpr char kEditDeleteMenuFunctionTriggeredHistogram[] = + "EditDeleteMenuFuctionTriggered"; + +inline constexpr char kToggleWithMappingSourceHistogram[] = + "ToggleWithMappingSource"; + +inline constexpr char kToggleOnHistogramName[] = "On"; +inline constexpr char kToggleOffHistogramName[] = "Off"; + +inline constexpr char kFeatureHistogramName[] = "Feature"; +inline constexpr char kHintHistogramName[] = "Hint"; + +inline constexpr char kGameControlsHistogramSeparator[] = "."; + +// This enum should be kept in sync with the +// `GameControlsButtonOptionsMenuFunction` in +// tools/metrics/histograms/enums.xml. +enum class ButtonOptionsMenuFunction { + kOptionSingleButton, + kOptionJoystick, + kEditLabelFocused, + kKeyAssigned, + kDone, + kDelete, + kMaxValue = kDelete, +}; + +// This enum should be kept in sync with the +// `GameControlsEditDeleteMenuFunction` in +// tools/metrics/histograms/enums.xml. +enum class EditDeleteMenuFunction { + kEdit, + kDelete, + kMaxValue = kDelete, +}; + +// This enum should be kept in sync with the `GameControlsEditingListFunction` +// in tools/metrics/histograms/enums.xml. +enum class EditingListFunction { + kAdd, + kDone, + kHoverListItem, + kPressListItem, + kEditLabelFocused, + kKeyAssigned, + kMaxValue = kKeyAssigned, +}; + +// This enum should be kept in sync with the `GameControlsMappingSource` +// in tools/metrics/histograms/enums.xml. +enum class MappingSource { + kEmpty, + // Only pre-defined default mapping. May include position change. + kDefault, + // Only user-added mapping. + kUserAdded, + // Includes default and user-added mapping. + kDefaultAndUserAdded, + kMaxValue = kDefaultAndUserAdded, +}; + +std::string BuildGameControlsHistogramName(const std::string& name); + // Records whether the feature is on or off. void RecordInputOverlayFeatureState(const std::string& package_name, bool enable); // Records whether the mapping hint is on or off. void RecordInputOverlayMappingHintState(const std::string& package_name, - bool enable); + bool enable); // Records whether the overlay is customized. void RecordInputOverlayCustomizedUsage(const std::string& package_name); @@ -39,6 +106,19 @@ RepositionType reposition_type, InputOverlayWindowStateType state_type); +void RecordEditingListFunctionTriggered(EditingListFunction function); + +void RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction function); + +void RecordEditDeleteMenuFunctionTriggered(EditDeleteMenuFunction function); + +// Records feature toggle data if `is_feature` is true. Otherwise, records the +// hint toggle data. +void RecordToggleWithMappingSource(bool is_feature, + bool is_on, + MappingSource source); + } // namespace arc::input_overlay #endif // CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_ARC_INPUT_OVERLAY_METRICS_H_
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc index 40a8332d..35b7739 100644 --- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc +++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -9,6 +9,7 @@ #include "ash/frame/non_client_frame_view_ash.h" #include "ash/game_dashboard/game_dashboard_controller.h" +#include "ash/game_dashboard/game_dashboard_utils.h" #include "ash/public/cpp/arc_game_controls_flag.h" #include "ash/public/cpp/window_properties.h" #include "ash/shell.h" @@ -17,6 +18,7 @@ #include "base/functional/bind.h" #include "base/memory/ptr_util.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/touch_injector.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_highlight.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_view.h" @@ -54,6 +56,10 @@ namespace arc::input_overlay { namespace { + +using ash::game_dashboard_utils::GetNextWidgetToFocus; +using ash::game_dashboard_utils::UpdateAccessibilityTree; + // UI specs. constexpr int kMenuEntrySideMargin = 24; constexpr int kNudgeVerticalAlign = 8; @@ -132,7 +138,8 @@ } // Change focus to the next widget. - if (auto* next_widget = GetNextWidgetToFocus(target_widget, reverse)) { + if (auto* next_widget = + GetNextWidgetToFocus(widget_list_, target_widget, reverse)) { next_widget->GetFocusManager()->AdvanceFocus(reverse); // Change the event target. ui::Event::DispatcherApi(&event).set_target( @@ -145,7 +152,7 @@ if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget); it == widget_list_.end()) { widget_list_.emplace_back(widget); - OnWidgetListUpdated(); + UpdateAccessibilityTree(widget_list_); } } @@ -153,45 +160,10 @@ if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget); it != widget_list_.end()) { widget_list_.erase(it); - OnWidgetListUpdated(); + UpdateAccessibilityTree(widget_list_); } } - void OnWidgetListUpdated() { - const size_t widget_list_size = widget_list_.size(); - if (widget_list_size <= 1u) { - return; - } - - // Update the widget's accessibility tree. - for (size_t i = 0; i < widget_list_size; i++) { - auto* curr_view = widget_list_[i]->GetContentsView(); - auto& curr_view_a11y = curr_view->GetViewAccessibility(); - const size_t prev_index = (i + widget_list_size - 1u) % widget_list_size; - const size_t next_index = (i + 1u) % widget_list_size; - - curr_view_a11y.SetPreviousFocus(widget_list_[prev_index]); - curr_view_a11y.SetNextFocus(widget_list_[next_index]); - curr_view->NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, - /*send_native_event=*/true); - } - } - - views::Widget* GetNextWidgetToFocus(views::Widget* focused_widget, - bool reverse) { - if (auto it = - std::find(widget_list_.begin(), widget_list_.end(), focused_widget); - it != widget_list_.end()) { - const int index = std::distance(widget_list_.begin(), it); - const size_t widget_list_size = widget_list_.size(); - const size_t next_index = - reverse ? (index - 1u + widget_list_size) % widget_list_size - : (index + 1u) % widget_list_size; - return widget_list_[next_index]; - } - return nullptr; - } - // Only contains visible and unique widgets. std::vector<views::Widget*> widget_list_; }; @@ -803,6 +775,28 @@ return it != actions.end() && !(it->get()->IsDeleted()); } +MappingSource DisplayOverlayController::GetMappingSource() const { + const auto& actions = touch_injector_->actions(); + if (actions.empty()) { + return MappingSource::kEmpty; + } + + // Check if there is any default action. + auto default_it = std::find_if( + actions.begin(), actions.end(), + [&](const std::unique_ptr<Action>& p) { return p->IsDefaultAction(); }); + + // Check if there is any user added action. + auto user_added_it = std::find_if( + actions.begin(), actions.end(), + [&](const std::unique_ptr<Action>& p) { return !p->IsDefaultAction(); }); + + return default_it != actions.end() && user_added_it != actions.end() + ? MappingSource::kDefaultAndUserAdded + : (default_it != actions.end() ? MappingSource::kDefault + : MappingSource::kUserAdded); +} + void DisplayOverlayController::AddTouchInjectorObserver( TouchInjectorObserver* observer) { touch_injector_->AddObserver(observer); @@ -1087,6 +1081,21 @@ } UpdateEventRewriteCapability(); + + // Record metrics. + const auto mapping_source = GetMappingSource(); + if (IsFlagChanged(flags, old_flags, ash::ArcGameControlsFlag::kEnabled)) { + RecordToggleWithMappingSource( + /*is_feature=*/true, + /*is_on=*/IsFlagSet(flags, ash::ArcGameControlsFlag::kEnabled), + mapping_source); + } + if (IsFlagChanged(flags, old_flags, ash::ArcGameControlsFlag::kHint)) { + RecordToggleWithMappingSource( + /*is_feature=*/false, + /*is_on=*/IsFlagSet(flags, ash::ArcGameControlsFlag::kHint), + mapping_source); + } } } }
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h index 6b64ae0..cecbfafe 100644 --- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h +++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -13,6 +13,7 @@ #include "base/memory/raw_ptr.h" #include "base/scoped_multi_source_observation.h" #include "chrome/browser/ash/arc/input_overlay/actions/input_element.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "ui/aura/window_observer.h" #include "ui/compositor/property_change_reason.h" #include "ui/events/event.h" @@ -103,6 +104,8 @@ // Return true if action is not deleted. bool IsActiveAction(Action* action) const; + MappingSource GetMappingSource() const; + // For menu entry hover state: void SetMenuEntryHoverState(bool curr_hover_state);
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc index 1a75d31..01049fd2 100644 --- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
@@ -7,8 +7,11 @@ #include <vector> #include "ash/public/cpp/arc_game_controls_flag.h" +#include "base/test/metrics/histogram_tester.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/test/game_controls_test_base.h" #include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h" +#include "chrome/browser/ash/arc/input_overlay/test/test_utils.h" #include "chrome/browser/ash/arc/input_overlay/touch_injector.h" #include "chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h" #include "chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h" @@ -377,4 +380,39 @@ /*button_options_visible=*/true, /*delete_edit_menu_visible=*/false); } +TEST_F(EditModeDisplayOverlayControllerTest, TestHistograms) { + base::HistogramTester histograms; + + // Check button options histograms. + const std::string button_options_histogram_name = + BuildGameControlsHistogramName( + kButtonOptionsMenuFunctionTriggeredHistogram); + std::map<ButtonOptionsMenuFunction, int> + expected_button_options_histogram_values; + + ShowButtonOptionsMenu(tap_action_); + PressDoneButtonOnButtonOptionsMenu(); + MapIncreaseValueByOne(expected_button_options_histogram_values, + ButtonOptionsMenuFunction::kDone); + VerifyHistogramValues(histograms, button_options_histogram_name, + expected_button_options_histogram_values); + + ShowButtonOptionsMenu(move_action_); + PressDeleteButtonOnButtonOptionsMenu(); + MapIncreaseValueByOne(expected_button_options_histogram_values, + ButtonOptionsMenuFunction::kDelete); + VerifyHistogramValues(histograms, button_options_histogram_name, + expected_button_options_histogram_values); + + // Check editing list histograms. + const std::string editing_list_histogram_name = + BuildGameControlsHistogramName(kEditingListFunctionTriggeredHistogram); + std::map<EditingListFunction, int> expected_editing_list_histogram_values; + PressDoneButton(); + MapIncreaseValueByOne(expected_editing_list_histogram_values, + EditingListFunction::kDone); + VerifyHistogramValues(histograms, editing_list_histogram_name, + expected_editing_list_histogram_values); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc index b40ce4d..107cb1c 100644 --- a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc +++ b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.cc
@@ -51,6 +51,13 @@ LeftClickOn(editing_list_->GetAddContainerButtonForTesting()); } +void OverlayViewTestBase::PressDoneButton() { + if (!editing_list_) { + return; + } + LeftClickOn(editing_list_->done_button_); +} + void OverlayViewTestBase::AddNewActionInCenter() { DCHECK(editing_list_);
diff --git a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h index b30df24..d7dbe3b 100644 --- a/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h +++ b/chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h
@@ -33,6 +33,7 @@ void EnableEditMode(); void PressAddButton(); void PressAddContainerButton(); + void PressDoneButton(); // Adds a new action in the center of the main window. void AddNewActionInCenter();
diff --git a/chrome/browser/ash/arc/input_overlay/test/test_utils.h b/chrome/browser/ash/arc/input_overlay/test/test_utils.h index 353843d..cde6321 100644 --- a/chrome/browser/ash/arc/input_overlay/test/test_utils.h +++ b/chrome/browser/ash/arc/input_overlay/test/test_utils.h
@@ -5,10 +5,12 @@ #ifndef CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_TEST_TEST_UTILS_H_ #define CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_TEST_TEST_UTILS_H_ +#include <map> #include <memory> #include <string> #include <vector> +#include "base/test/metrics/histogram_tester.h" #include "base/time/time.h" #include "chrome/browser/ash/arc/input_overlay/db/proto/app_data.pb.h" #include "ui/gfx/geometry/rect.h" @@ -69,6 +71,27 @@ std::u16string GetControlName(ActionType action_type, std::u16string key_string); +// Increases the value for `key` by one. If there is no `key`, set the value +// to 1. +template <typename T> +void MapIncreaseValueByOne(std::map<T, int>& map, T key) { + auto it = map.find(key); + if (it == map.end()) { + map[key] = 1; + } else { + map[key]++; + } +} + +template <typename T> +void VerifyHistogramValues(const base::HistogramTester& histograms, + const std::string& histogram_name, + const std::map<T, int>& histogram_values) { + for (const auto& value : histogram_values) { + histograms.ExpectBucketCount(histogram_name, value.first, value.second); + } +} + } // namespace arc::input_overlay #endif // CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_TEST_TEST_UTILS_H_
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_type_button_group.cc b/chrome/browser/ash/arc/input_overlay/ui/action_type_button_group.cc index 57130901..f792de40 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/action_type_button_group.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/action_type_button_group.cc
@@ -9,6 +9,7 @@ #include "base/notreached.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chromeos/strings/grit/chromeos_strings.h" #include "ui/accessibility/ax_enums.mojom.h" @@ -150,6 +151,8 @@ } selected_action_type_ = ActionType::TAP; controller_->ChangeActionType(action_, ActionType::TAP); + RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction::kOptionSingleButton); } void ActionTypeButtonGroup::OnActionMoveButtonPressed() { @@ -158,6 +161,8 @@ } selected_action_type_ = ActionType::MOVE; controller_->ChangeActionType(action_, ActionType::MOVE); + RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction::kOptionJoystick); } BEGIN_METADATA(ActionTypeButtonGroup)
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc index fde2730..5b55a26 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/ui/edit_labels.h" #include "chrome/browser/ash/arc/input_overlay/ui/name_tag.h" @@ -22,12 +23,14 @@ ActionViewListItem::~ActionViewListItem() = default; void ActionViewListItem::ClickCallback() { + RecordEditingListFunctionTriggered(EditingListFunction::kPressListItem); controller_->AddButtonOptionsMenuWidget(action_); } void ActionViewListItem::OnMouseEntered(const ui::MouseEvent& event) { controller_->AddActionHighlightWidget(action_); controller_->AddDeleteEditShortcutWidget(this); + RecordEditingListFunctionTriggered(EditingListFunction::kHoverListItem); } void ActionViewListItem::OnMouseExited(const ui::MouseEvent& event) {
diff --git a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc index ef09c18..d75fa51 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
@@ -14,6 +14,7 @@ #include "ash/style/typography.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/touch_injector.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_type_button_group.h" @@ -281,11 +282,13 @@ } void ButtonOptionsMenu::OnTrashButtonPressed() { + RecordButtonOptionsMenuFunctionTriggered(ButtonOptionsMenuFunction::kDelete); controller_->RemoveAction(action_); } void ButtonOptionsMenu::OnDoneButtonPressed() { controller_->SaveToProtoFile(); + RecordButtonOptionsMenuFunctionTriggered(ButtonOptionsMenuFunction::kDone); controller_->SetEditingListVisibility(/*visible=*/true);
diff --git a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu_unittest.cc index af12a32..6a3f8d0 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu_unittest.cc
@@ -11,7 +11,9 @@ #include "ash/root_window_controller.h" #include "ash/shelf/shelf.h" #include "ash/style/icon_button.h" +#include "base/test/metrics/histogram_tester.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/db/proto/app_data.pb.h" #include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h" #include "chrome/browser/ash/arc/input_overlay/test/test_utils.h" @@ -282,4 +284,22 @@ menu->GetWidget()->GetNativeWindow()->bounds().bottom()); } +TEST_F(ButtonOptionsMenuTest, TestHistograms) { + base::HistogramTester histograms; + const std::string histogram_name = BuildGameControlsHistogramName( + kButtonOptionsMenuFunctionTriggeredHistogram); + std::map<ButtonOptionsMenuFunction, int> expected_histogram_values; + + auto* menu = ShowButtonOptionsMenu(tap_action_); + PressActionMoveButton(menu); + MapIncreaseValueByOne(expected_histogram_values, + ButtonOptionsMenuFunction::kOptionJoystick); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); + + PressTapButton(menu); + MapIncreaseValueByOne(expected_histogram_values, + ButtonOptionsMenuFunction::kOptionSingleButton); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc index 24c826825..fb0f5bb 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.cc
@@ -10,6 +10,7 @@ #include "ash/style/icon_button.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/constants.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h" @@ -119,6 +120,7 @@ } void DeleteEditShortcut::OnEditButtonPressed() { + RecordEditDeleteMenuFunctionTriggered(EditDeleteMenuFunction::kEdit); if (auto* anchor_view = views::AsViewClass<ActionViewListItem>(GetAnchorView())) { controller_->AddButtonOptionsMenuWidget(anchor_view->action()); @@ -126,6 +128,7 @@ } void DeleteEditShortcut::OnDeleteButtonPressed() { + RecordEditDeleteMenuFunctionTriggered(EditDeleteMenuFunction::kDelete); if (auto* anchor_view = views::AsViewClass<ActionViewListItem>(GetAnchorView())) { controller_->RemoveAction(anchor_view->action());
diff --git a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut_unittest.cc index 92899f5f..1752b506 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut_unittest.cc
@@ -4,9 +4,12 @@ #include "chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h" +#include "base/test/metrics/histogram_tester.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h" +#include "chrome/browser/ash/arc/input_overlay/test/test_utils.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h" #include "ui/views/view_utils.h" @@ -109,4 +112,24 @@ EXPECT_EQ(original_size - 1, GetActionViewSize()); } +TEST_F(DeleteEditShortcutTest, TestHistograms) { + base::HistogramTester histograms; + const std::string histogram_name = + BuildGameControlsHistogramName(kEditDeleteMenuFunctionTriggeredHistogram); + std::map<EditDeleteMenuFunction, int> expected_histogram_values; + + HoverAtActionViewListItem(/*index=*/0u); + PressEditButton(); + MapIncreaseValueByOne(expected_histogram_values, + EditDeleteMenuFunction::kEdit); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); + + PressDoneButtonOnButtonOptionsMenu(); + HoverAtActionViewListItem(/*index=*/1u); + PressDeleteButton(); + MapIncreaseValueByOne(expected_histogram_values, + EditDeleteMenuFunction::kDelete); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc index 12e7d97d..c712b08 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
@@ -14,6 +14,7 @@ #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" #include "chrome/browser/ash/arc/input_overlay/actions/input_element.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/constants.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h" @@ -276,6 +277,10 @@ SetToFocused(); if (for_editing_list_) { controller_->AddActionHighlightWidget(action_); + RecordEditingListFunctionTriggered(EditingListFunction::kEditLabelFocused); + } else { + RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction::kEditLabelFocused); } } @@ -333,6 +338,12 @@ } SetTextLabel(new_bind); + if (for_editing_list_) { + RecordEditingListFunctionTriggered(EditingListFunction::kKeyAssigned); + } else { + RecordButtonOptionsMenuFunctionTriggered( + ButtonOptionsMenuFunction::kKeyAssigned); + } std::unique_ptr<InputElement> input; switch (action_->GetType()) {
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_label_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_label_unittest.cc index d44b5aa4..b69d8281 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/edit_label_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/edit_label_unittest.cc
@@ -9,6 +9,7 @@ #include "base/memory/raw_ptr.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/constants.h" #include "chrome/browser/ash/arc/input_overlay/db/proto/app_data.pb.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" @@ -308,4 +309,48 @@ {u"", u"a", u"", u""}, GetControlName(ActionType::MOVE, u"a")); } +TEST_F(EditLabelTest, TestHistograms) { + widget_->GetNativeWindow()->SetBounds(gfx::Rect(310, 10, 300, 500)); + base::HistogramTester histograms; + + // Check histograms for editing list. + const std::string editing_list_histogram_name = + BuildGameControlsHistogramName(kEditingListFunctionTriggeredHistogram); + std::map<EditingListFunction, int> expected_editing_list_histogram_values; + LeftClickOn(GetEditLabel(tap_action_list_item_, /*index=*/0)); + MapIncreaseValueByOne(expected_editing_list_histogram_values, + EditingListFunction::kEditLabelFocused); + VerifyHistogramValues(histograms, editing_list_histogram_name, + expected_editing_list_histogram_values); + + auto* event_generator = GetEventGenerator(); + event_generator->PressAndReleaseKey(ui::VKEY_M, ui::EF_NONE); + MapIncreaseValueByOne(expected_editing_list_histogram_values, + EditingListFunction::kKeyAssigned); + VerifyHistogramValues(histograms, editing_list_histogram_name, + expected_editing_list_histogram_values); + + // Check histograms for button options menu. + const std::string button_options_histogram_name = + BuildGameControlsHistogramName( + kButtonOptionsMenuFunctionTriggeredHistogram); + std::map<ButtonOptionsMenuFunction, int> + expected_button_options_histogram_values; + auto* menu = ShowButtonOptionsMenu(move_action_); + LeftClickOn(GetEditLabel(menu, /*index=*/1)); + MapIncreaseValueByOne(expected_button_options_histogram_values, + ButtonOptionsMenuFunction::kEditLabelFocused); + VerifyHistogramValues(histograms, button_options_histogram_name, + expected_button_options_histogram_values); + + event_generator->PressAndReleaseKey(ui::VKEY_N, ui::EF_NONE); + // After assign a key, the focus is automatically moved to the next one. + MapIncreaseValueByOne(expected_button_options_histogram_values, + ButtonOptionsMenuFunction::kEditLabelFocused); + MapIncreaseValueByOne(expected_button_options_histogram_values, + ButtonOptionsMenuFunction::kKeyAssigned); + VerifyHistogramValues(histograms, button_options_histogram_name, + expected_button_options_histogram_values); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc index 44fdbe92..b4c1b21 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
@@ -21,6 +21,7 @@ #include "base/notreached.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/constants.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/touch_injector.h" @@ -318,14 +319,14 @@ help_button->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(0, 0, 0, 8)); // Add done button. - auto* done_button = + done_button_ = header_container->AddChildView(std::make_unique<ash::PillButton>( base::BindRepeating(&EditingList::OnDoneButtonPressed, base::Unretained(this)), l10n_util::GetStringUTF16( IDS_INPUT_OVERLAY_EDITING_DONE_BUTTON_LABEL), ash::PillButton::Type::kSecondaryWithoutIcon)); - done_button->SetAccessibleName(l10n_util::GetStringUTF16( + done_button_->SetAccessibleName(l10n_util::GetStringUTF16( IDS_INPUT_OVERLAY_EDITING_LIST_DONE_BUTTON_A11Y_LABEL)); } @@ -421,10 +422,12 @@ ash::Shell::Get()->anchored_nudge_manager()->Cancel(kKeyEditNudgeID); } controller_->EnterButtonPlaceMode(ActionType::TAP); + RecordEditingListFunctionTriggered(EditingListFunction::kAdd); } void EditingList::OnDoneButtonPressed() { DCHECK(controller_); + RecordEditingListFunctionTriggered(EditingListFunction::kDone); controller_->OnCustomizeSave(); }
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h index 544807e..bf93cc2 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h +++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
@@ -7,6 +7,7 @@ #include <memory> +#include "ash/style/pill_button.h" #include "base/callback_list.h" #include "base/memory/raw_ptr.h" #include "chrome/browser/ash/arc/input_overlay/touch_injector_observer.h" @@ -15,6 +16,7 @@ namespace ash { class AnchoredNudge; +class PillButton; class SystemShadow; } // namespace ash @@ -140,6 +142,7 @@ raw_ptr<views::Label> editing_header_label_; raw_ptr<AddContainerButton> add_container_; + raw_ptr<ash::PillButton> done_button_; // Owned by this view. std::unique_ptr<ash::SystemShadow> shadow_;
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc index cdcc2701..ebbdcc3 100644 --- a/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc +++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list_unittest.cc
@@ -10,7 +10,9 @@ #include "ash/system/toast/anchored_nudge.h" #include "ash/system/toast/anchored_nudge_manager_impl.h" #include "base/check.h" +#include "base/test/metrics/histogram_tester.h" #include "chrome/browser/ash/arc/input_overlay/actions/action.h" +#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h" #include "chrome/browser/ash/arc/input_overlay/constants.h" #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h" #include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h" @@ -527,4 +529,26 @@ EXPECT_FALSE(GetTargetView()); } +TEST_F(EditingListTest, TestHistograms) { + base::HistogramTester histograms; + const std::string histogram_name = + BuildGameControlsHistogramName(kEditingListFunctionTriggeredHistogram); + std::map<EditingListFunction, int> expected_histogram_values; + + PressAddButton(); + MapIncreaseValueByOne(expected_histogram_values, EditingListFunction::kAdd); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); + + GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE); + HoverAtActionViewListItem(/*index=*/0u); + MapIncreaseValueByOne(expected_histogram_values, + EditingListFunction::kHoverListItem); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); + + LeftClickAtActionViewListItem(/*index=*/0); + MapIncreaseValueByOne(expected_histogram_values, + EditingListFunction::kPressListItem); + VerifyHistogramValues(histograms, histogram_name, expected_histogram_values); +} + } // namespace arc::input_overlay
diff --git a/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc b/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc index 98dc0359..6654dcc 100644 --- a/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc +++ b/chrome/browser/ash/phonehub/phone_hub_manager_factory.cc
@@ -32,6 +32,7 @@ #include "chromeos/ash/components/phonehub/onboarding_ui_tracker_impl.h" #include "chromeos/ash/components/phonehub/phone_hub_manager.h" #include "chromeos/ash/components/phonehub/phone_hub_manager_impl.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl.h" #include "chromeos/ash/components/phonehub/screen_lock_manager_impl.h" #include "chromeos/ash/components/phonehub/user_action_recorder_impl.h" @@ -223,6 +224,7 @@ OnboardingUiTrackerImpl::RegisterPrefs(registry); ScreenLockManagerImpl::RegisterPrefs(registry); RecentAppsInteractionHandlerImpl::RegisterPrefs(registry); + PhoneHubStructuredMetricsLogger::RegisterPrefs(registry); } } // namespace ash::phonehub
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/common/base_telemetry_extension_browser_test.cc b/chrome/browser/chromeos/extensions/telemetry/api/common/base_telemetry_extension_browser_test.cc index 367b43e..023c5a7 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/common/base_telemetry_extension_browser_test.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/common/base_telemetry_extension_browser_test.cc
@@ -90,6 +90,7 @@ "os.attached_device_info", "os.bluetooth_peripherals_info", "os.diagnostics", + "os.diagnostics.network_info_mlab", "os.events", "os.management.audio", "os.telemetry",
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api.cc index eb8ae9f..d16e8c7 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api.cc
@@ -65,7 +65,7 @@ bool IsPendingApprovalRoutine( const crosapi::mojom::TelemetryDiagnosticRoutineArgumentPtr& arg) { - return arg->is_network_bandwidth(); + return false; } } // namespace @@ -526,6 +526,18 @@ NewUnrecognizedArgument(false); } + // Network bandwidth routine is guarded by `os.diagnostics.network_info_mlab` + // permission. + if (mojo_arg.value()->is_network_bandwidth() && + !extension()->permissions_data()->HasAPIPermission( + extensions::mojom::APIPermissionID:: + kChromeOSDiagnosticsNetworkInfoForMlab)) { + RespondWithError( + "Unauthorized access to chrome.os.diagnostics.CreateRoutine with " + "networkBandwidth argument. Extension doesn't have the permission."); + return; + } + auto* routines_manager = DiagnosticRoutineManager::Get(browser_context()); auto result = routines_manager->CreateRoutine(extension_id(), std::move(mojo_arg.value())); @@ -763,6 +775,19 @@ NewUnrecognizedArgument(false); } + // Network bandwidth routine is guarded by `os.diagnostics.network_info_mlab` + // permission. + if (mojo_arg.value()->is_network_bandwidth() && + !extension()->permissions_data()->HasAPIPermission( + extensions::mojom::APIPermissionID:: + kChromeOSDiagnosticsNetworkInfoForMlab)) { + RespondWithError( + "Unauthorized access to " + "chrome.os.diagnostics.isRoutineArgumentSupported with " + "networkBandwidth argument. Extension doesn't have the permission."); + return; + } + auto* routines_manager = DiagnosticRoutineManager::Get(browser_context()); routines_manager->IsRoutineArgumentSupported( std::move(mojo_arg.value()),
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_v2_browsertest.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_v2_browsertest.cc index 1d70b018..b9c4b72 100644 --- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_v2_browsertest.cc +++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_v2_browsertest.cc
@@ -1130,32 +1130,6 @@ )"); } -IN_PROC_BROWSER_TEST_F( - TelemetryExtensionDiagnosticsApiV2BrowserTest, - CreateNetworkBandwidthRoutineWithoutFeatureFlagUnrecognized) { - fake_service().SetOnCreateRoutineCalled(base::BindLambdaForTesting([this]() { - auto* control = fake_service().GetCreatedRoutineControlForRoutineType( - crosapi::TelemetryDiagnosticRoutineArgument::Tag:: - kUnrecognizedArgument); - ASSERT_TRUE(control); - })); - - OpenAppUiAndMakeItSecure(); - - CreateExtensionAndRunServiceWorker(R"( - chrome.test.runTests([ - async function createNetworkBandwidthRoutineUnrecognized() { - const result = await chrome.os.diagnostics.createRoutine({ - networkBandwidth: {}, - }); - - chrome.test.assertTrue(result !== undefined); - chrome.test.succeed(); - } - ]); - )"); -} - IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticsApiV2BrowserTest, ReplyToRoutineInquiryUnknownUuidError) { OpenAppUiAndMakeItSecure(); @@ -1289,21 +1263,8 @@ EXPECT_TRUE(led_routine_created.Wait()); } -class PendingApprovalTelemetryExtensionDiagnosticsApiV2BrowserTest - : public TelemetryExtensionDiagnosticsApiV2BrowserTest { - public: - PendingApprovalTelemetryExtensionDiagnosticsApiV2BrowserTest() { - feature_list_.InitAndEnableFeature( - extensions_features::kTelemetryExtensionPendingApprovalApi); - } - - private: - base::test::ScopedFeatureList feature_list_; -}; - -IN_PROC_BROWSER_TEST_F( - PendingApprovalTelemetryExtensionDiagnosticsApiV2BrowserTest, - CreateNetworkBandwidthRoutineWithFeatureFlagSuccess) { +IN_PROC_BROWSER_TEST_F(TelemetryExtensionDiagnosticsApiV2BrowserTest, + CreateNetworkBandwidthRoutineSuccess) { fake_service().SetOnCreateRoutineCalled(base::BindLambdaForTesting([this]() { auto* control = fake_service().GetCreatedRoutineControlForRoutineType( crosapi::TelemetryDiagnosticRoutineArgument::Tag::kNetworkBandwidth); @@ -1374,4 +1335,55 @@ )"); } +class NoExtraPermissionTelemetryExtensionDiagnosticsApiV2BrowserTest + : public TelemetryExtensionDiagnosticsApiV2BrowserTest { + public: + NoExtraPermissionTelemetryExtensionDiagnosticsApiV2BrowserTest() = default; + + protected: + std::string GetManifestFile(const std::string& manifest_key, + const std::string& matches_origin) override { + return base::StringPrintf(R"( + { + "key": "%s", + "name": "Test Telemetry Extension", + "version": "1", + "manifest_version": 3, + "chromeos_system_extension": {}, + "background": { + "service_worker": "sw.js" + }, + "permissions": [ "os.diagnostics" ], + "externally_connectable": { + "matches": [ + "%s" + ] + }, + "options_page": "options.html" + } + )", + manifest_key.c_str(), matches_origin.c_str()); + } +}; + +IN_PROC_BROWSER_TEST_F( + NoExtraPermissionTelemetryExtensionDiagnosticsApiV2BrowserTest, + NetworkBandwidthRoutineNoPermissionFail) { + CreateExtensionAndRunServiceWorker(R"( + chrome.test.runTests([ + async function createNetworkBandwidthRoutineNoPermission() { + await chrome.test.assertPromiseRejects( + chrome.os.diagnostics.createRoutine({ + networkBandwidth: {}, + }), + 'Error: Unauthorized access to ' + + 'chrome.os.diagnostics.CreateRoutine with networkBandwidth ' + + 'argument. Extension doesn\'t have the permission.' + ); + chrome.test.succeed(); + } + ]); + )"); +} + } // namespace chromeos
diff --git a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc index 7fe4fd0..166f20ad1 100644 --- a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc +++ b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
@@ -50,6 +50,9 @@ const std::u16string kBluetoothPeripheralsInfo = u"Read Bluetooth peripherals information and data"; const std::u16string kManagementAudio = u"Manage ChromeOS audio settings"; +const std::u16string kDiagnosticsNetworkInfoForMlab = + u"Collect IP address and network measurement results for Measurement Lab, " + u"according to their privacy policy (measurementlab.net/privacy)"; } // namespace // Tests that ChromePermissionMessageProvider provides not only correct, but @@ -188,6 +191,26 @@ EXPECT_EQ(kDiagnosticsPermissionMessage, active_permissions()[0]); } +TEST_F(ChromeOSPermissionMessageUnittest, OsDiagnosticsNetworkInfoForMlab) { + CreateAndInstallExtensionWithPermissions( + base::Value::List(), + base::Value::List().Append("os.diagnostics.network_info_mlab")); + + ASSERT_EQ(1U, optional_permissions().size()); + EXPECT_EQ(kDiagnosticsNetworkInfoForMlab, optional_permissions()[0]); + ASSERT_EQ(1U, GetInactiveOptionalPermissionMessages().size()); + EXPECT_EQ(kDiagnosticsNetworkInfoForMlab, + GetInactiveOptionalPermissionMessages()[0]); + EXPECT_EQ(0U, required_permissions().size()); + EXPECT_EQ(0U, active_permissions().size()); + + GrantOptionalPermissions(); + + EXPECT_EQ(0U, GetInactiveOptionalPermissionMessages().size()); + ASSERT_EQ(1U, active_permissions().size()); + EXPECT_EQ(kDiagnosticsNetworkInfoForMlab, active_permissions()[0]); +} + TEST_F(ChromeOSPermissionMessageUnittest, OsTelemetryMessage) { CreateAndInstallExtensionWithPermissions( base::Value::List().Append("os.telemetry"), base::Value::List());
diff --git a/chrome/browser/download/download_core_service_impl.cc b/chrome/browser/download/download_core_service_impl.cc index 01dd185..5005b4f2 100644 --- a/chrome/browser/download/download_core_service_impl.cc +++ b/chrome/browser/download/download_core_service_impl.cc
@@ -130,9 +130,11 @@ DownloadManager* download_manager = profile_->GetDownloadManager(); DownloadManager::DownloadVector downloads; download_manager->GetAllDownloads(&downloads); - for (auto it = downloads.begin(); it != downloads.end(); ++it) { - if ((*it)->GetState() == download::DownloadItem::IN_PROGRESS) - (*it)->Cancel(false); + for (auto& download : downloads) { + if (download->GetState() == download::DownloadItem::IN_PROGRESS) { + download->Cancel(/*user_cancel=*/false); + manager_delegate_->OnDownloadCanceledAtShutdown(download); + } } }
diff --git a/chrome/browser/download/download_core_service_impl_unittest.cc b/chrome/browser/download/download_core_service_impl_unittest.cc new file mode 100644 index 0000000..5409877 --- /dev/null +++ b/chrome/browser/download/download_core_service_impl_unittest.cc
@@ -0,0 +1,104 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/download/download_core_service_impl.h" + +#include "base/memory/raw_ptr.h" +#include "chrome/browser/download/chrome_download_manager_delegate.h" +#include "chrome/test/base/testing_profile.h" +#include "components/download/public/common/download_item.h" +#include "components/download/public/common/mock_download_item.h" +#include "content/public/test/browser_task_environment.h" +#include "content/public/test/mock_download_manager.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using DownloadState = download::DownloadItem::DownloadState; +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; + +class TestDownloadManagerDelegate : public ChromeDownloadManagerDelegate { + public: + explicit TestDownloadManagerDelegate(Profile* profile) + : ChromeDownloadManagerDelegate(profile) {} + ~TestDownloadManagerDelegate() override = default; + + void OnDownloadCanceledAtShutdown(download::DownloadItem* item) override { + canceled_at_shutdown_called_count_++; + } + + int OnDownloadCanceledAtShutdownCalledCount() { + return canceled_at_shutdown_called_count_; + } + + private: + int canceled_at_shutdown_called_count_ = 0; +}; + +class DownloadCoreServiceImplTest : public testing::Test { + public: + void SetUp() override { + profile_ = std::make_unique<TestingProfile>(); + auto download_manager = + std::make_unique<NiceMock<content::MockDownloadManager>>(); + download_manager_ = download_manager.get(); + profile_->SetDownloadManagerForTesting(std::move(download_manager)); + download_core_service_ = + std::make_unique<DownloadCoreServiceImpl>(profile_.get()); + auto delegate = + std::make_unique<TestDownloadManagerDelegate>(profile_.get()); + delegate_ = delegate.get(); + download_core_service_->SetDownloadManagerDelegateForTesting( + std::move(delegate)); + } + + void TearDown() override { + download_core_service_->GetDownloadManagerDelegate()->Shutdown(); + delegate_ = nullptr; + download_manager_ = nullptr; + download_core_service_ = nullptr; + profile_ = nullptr; + } + + protected: + std::unique_ptr<download::MockDownloadItem> CreateDownloadItem( + DownloadState state) { + auto item = std::make_unique<NiceMock<download::MockDownloadItem>>(); + EXPECT_CALL(*item, GetState()).WillRepeatedly(Return(state)); + return item; + } + + content::BrowserTaskEnvironment task_environment_; + raw_ptr<NiceMock<content::MockDownloadManager>> download_manager_; + std::unique_ptr<TestingProfile> profile_; + raw_ptr<TestDownloadManagerDelegate> delegate_; + std::unique_ptr<DownloadCoreServiceImpl> download_core_service_; +}; + +TEST_F(DownloadCoreServiceImplTest, CancelDownloads) { + auto completed_item = CreateDownloadItem(DownloadState::COMPLETE); + auto in_progress_item1 = CreateDownloadItem(DownloadState::IN_PROGRESS); + auto in_progress_item2 = CreateDownloadItem(DownloadState::IN_PROGRESS); + std::vector<raw_ptr<download::DownloadItem, VectorExperimental>> items; + items.push_back(completed_item.get()); + items.push_back(in_progress_item1.get()); + items.push_back(in_progress_item2.get()); + EXPECT_CALL(*download_manager_, GetAllDownloads) + .WillRepeatedly(SetArgPointee<0>(items)); + + // Only in progress items should be canceled. + EXPECT_CALL(*completed_item, Cancel(_)).Times(0); + EXPECT_CALL(*in_progress_item1, Cancel(/*user_cancel=*/false)).Times(1); + EXPECT_CALL(*in_progress_item2, Cancel(/*user_cancel=*/false)).Times(1); + + download_core_service_->CancelDownloads(); + + EXPECT_EQ(delegate_->OnDownloadCanceledAtShutdownCalledCount(), 2); +} + +} // namespace
diff --git a/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc b/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc index 0871c48..4b31aed1 100644 --- a/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc +++ b/chrome/browser/extensions/api/bluetooth_low_energy/bluetooth_low_energy_apitest.cc
@@ -1168,7 +1168,7 @@ EXPECT_CALL(*mock_adapter_, GetDevice(kTestLeDeviceAddress1)) .WillRepeatedly(Return(device1_.get())); static_assert( - BluetoothDevice::NUM_CONNECT_ERROR_CODES == 14, + BluetoothDevice::NUM_CONNECT_ERROR_CODES == 15, "Update required if the number of BluetoothDevice enums changes."); EXPECT_CALL(*device0_, CreateGattConnection(_, _)) .Times(9)
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 62b1b188..e01b7f0 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -6384,6 +6384,14 @@ "expiry_milestone": 130 }, { + "name": "omnibox-starter-pack-iph", + "owners": [ + "yoangela@chromium.org", + "chrome-omnibox-team@google.com" + ], + "expiry_milestone": 130 + }, + { "name": "omnibox-suggestion-answer-migration", "owners": ["jennserrano@google.com", "chrome-omnibox-team@google.com"], "expiry_milestone": 130
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index d44be8f..152c5bb 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -2561,6 +2561,12 @@ const char kOmniboxStarterPackExpansionDescription[] = "Enables additional providers for the Site search starter pack feature"; +const char kOmniboxStarterPackIPHName[] = + "IPH message for the Site search starter pack"; +const char kOmniboxStarterPackIPHDescription[] = + "Enables an informational IPH message for the Site search starter pack " + "feature"; + #if BUILDFLAG(IS_ANDROID) const char kOmnibox2023RefreshConnectionSecurityIndicatorsName[] = "Omnibox 2023 refresh connection security indicators";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 2312e94e..47e3ec83 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -1489,6 +1489,9 @@ extern const char kOmniboxStarterPackExpansionName[]; extern const char kOmniboxStarterPackExpansionDescription[]; +extern const char kOmniboxStarterPackIPHName[]; +extern const char kOmniboxStarterPackIPHDescription[]; + extern const char kOmniboxZeroSuggestPrefetchingName[]; extern const char kOmniboxZeroSuggestPrefetchingDescription[];
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win index 065a550..e226150 160000 --- a/chrome/browser/platform_experience/win +++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@ -Subproject commit 065a550502a2de7532a2b3d8fb926af407611ab4 +Subproject commit e2261501b3a54bb01cd879dfc0d9297a9a26aa0e
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc index 1c1ffa5..14abe72 100644 --- a/chrome/browser/printing/print_browsertest.cc +++ b/chrome/browser/printing/print_browsertest.cc
@@ -391,7 +391,12 @@ PrintBrowserTest::KillPrintRenderFrame::KillPrintRenderFrame( content::RenderProcessHost* rph) - : rph_(rph) {} + : rph_(rph), print_render_frame_(nullptr) {} + +PrintBrowserTest::KillPrintRenderFrame::KillPrintRenderFrame( + content::RenderProcessHost* rph, + mojom::PrintRenderFrame* print_render_frame) + : rph_(rph), print_render_frame_(print_render_frame) {} PrintBrowserTest::KillPrintRenderFrame::~KillPrintRenderFrame() = default; @@ -418,8 +423,9 @@ mojom::PrintRenderFrame* PrintBrowserTest::KillPrintRenderFrame::GetForwardingInterface() { - NOTREACHED(); - return nullptr; + CHECK(print_render_frame_); + rph_->Shutdown(0); + return print_render_frame_; } void PrintBrowserTest::KillPrintRenderFrame::PrintFrameContent(
diff --git a/chrome/browser/printing/print_browsertest.h b/chrome/browser/printing/print_browsertest.h index 07f9282..912d095 100644 --- a/chrome/browser/printing/print_browsertest.h +++ b/chrome/browser/printing/print_browsertest.h
@@ -96,6 +96,8 @@ : public mojom::PrintRenderFrameInterceptorForTesting { public: explicit KillPrintRenderFrame(content::RenderProcessHost* rph); + KillPrintRenderFrame(content::RenderProcessHost* rph, + mojom::PrintRenderFrame* print_render_frame); ~KillPrintRenderFrame() override; void OverrideBinderForTesting(content::RenderFrameHost* render_frame_host); @@ -113,6 +115,7 @@ private: const raw_ptr<content::RenderProcessHost> rph_; + const raw_ptr<mojom::PrintRenderFrame> print_render_frame_; mojo::AssociatedReceiver<mojom::PrintRenderFrame> receiver_{this}; };
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc index 261742ba..6b1bc5d 100644 --- a/chrome/browser/printing/print_view_manager_base.cc +++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -897,10 +897,19 @@ if (render_frame_host != printing_rfh_) return; + for (auto& observer : GetTestObservers()) { + observer.OnRenderFrameDeleted(); + } + printing_rfh_ = nullptr; PrintManager::PrintingRenderFrameDeleted(); ReleasePrinterQuery(); +#if BUILDFLAG(ENABLE_OOP_PRINTING) + if (ShouldPrintJobOop()) { + UnregisterSystemPrintClient(); + } +#endif if (!print_job_) return;
diff --git a/chrome/browser/printing/print_view_manager_base.h b/chrome/browser/printing/print_view_manager_base.h index 43e3536..eda41e7 100644 --- a/chrome/browser/printing/print_view_manager_base.h +++ b/chrome/browser/printing/print_view_manager_base.h
@@ -69,6 +69,8 @@ virtual void OnRegisterSystemPrintClient(bool succeeded) {} virtual void OnDidPrintDocument() {} + + virtual void OnRenderFrameDeleted() {} }; PrintViewManagerBase(const PrintViewManagerBase&) = delete;
diff --git a/chrome/browser/printing/system_access_process_print_browsertest.cc b/chrome/browser/printing/system_access_process_print_browsertest.cc index 58fe98f..72af5a8d 100644 --- a/chrome/browser/printing/system_access_process_print_browsertest.cc +++ b/chrome/browser/printing/system_access_process_print_browsertest.cc
@@ -30,6 +30,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "content/public/test/no_renderer_crashes_assertion.h" #include "mojo/public/cpp/bindings/pending_associated_remote.h" #include "mojo/public/cpp/bindings/remote.h" #include "printing/buildflags/buildflags.h" @@ -793,6 +794,12 @@ CheckForQuit(); } + void OnRenderFrameDeleted() override { + if (check_for_render_frame_deleted_) { + CheckForQuit(); + } + } + // PrintJob::Observer: void OnDestruction() override { ++print_job_destruction_count_; @@ -1055,6 +1062,10 @@ } #endif + void SetCheckForRenderFrameDeleted(bool check) { + check_for_render_frame_deleted_ = check; + } + const std::optional<bool> system_print_registration_succeeded() const { return system_print_registration_succeeded_; } @@ -1255,6 +1266,7 @@ #endif #if BUILDFLAG(ENABLE_OOP_PRINTING) bool check_for_print_preview_done_ = false; + bool check_for_render_frame_deleted_ = false; TestPrintJobWorker::PrintCallbacks test_print_job_worker_callbacks_; TestPrintJobWorkerOop::PrintCallbacks test_print_job_worker_oop_callbacks_; CreatePrinterQueryCallback test_create_printer_query_callback_; @@ -2439,6 +2451,78 @@ #endif // BUILDFLAG(IS_WIN) IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest, + PrintPreviewPrintAfterSystemPrintRendererCrash) { + AddPrinter("printer1"); + SetPrinterNameForSubsequentContexts("printer1"); + + ASSERT_TRUE(embedded_test_server()->Started()); + GURL url(embedded_test_server()->GetURL("/printing/test3.html")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + SetUpPrintViewManager(web_contents); + + content::RenderFrameHost* frame = web_contents->GetPrimaryMainFrame(); + content::RenderProcessHost* frame_rph = frame->GetProcess(); + + KillPrintRenderFrame frame_content(frame_rph, + GetPrintRenderFrame(frame).get()); + frame_content.OverrideBinderForTesting(frame); + + // With the renderer being prepared to fake a crash, the test needs to watch + // for it being deleted. + SetCheckForRenderFrameDeleted(/*check=*/true); + content::ScopedAllowRendererCrashes allow_renderer_crash; + + // First invoke system print directly. + + // The expected events for this are: + // 1. Printing is attempted, but quickly get notified that the render frame + // has been deleted because the renderer "crashed". + SetNumExpectedMessages(/*num=*/1); + + StartBasicPrint(web_contents); + + WaitUntilCallbackReceived(); + + // After renderer crash, reload the page again in the same tab. + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + // Now try to initiate print from a Print Preview. + PrepareRunloop(); + ResetNumReceivedMessages(); + + // No longer interested in when the renderer is deleted. + SetCheckForRenderFrameDeleted(/*check=*/false); + + // The expected events for this are: + // 1. Update print settings. + // 2. A print job is started. + // 3. Rendering for 1 page of document of content. + // 4. Completes with document done. + // 5. Wait for the one print job to be destroyed, to ensure printing + // finished cleanly before completing the test. + SetNumExpectedMessages(/*num=*/5); + + PrintAfterPreviewIsReadyAndLoaded(); + + EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess); +#if BUILDFLAG(IS_WIN) + // TODO(crbug.com/40100562) Include Windows coverage of + // RenderPrintedDocument() once XPS print pipeline is added. + EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(render_printed_page_count(), 1); +#else + EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess); +#endif + EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess); + EXPECT_EQ(error_dialog_shown_count(), 0u); + EXPECT_EQ(print_job_destruction_count(), 1); +} + +IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest, StartBasicPrint) { AddPrinter("printer1"); SetPrinterNameForSubsequentContexts("printer1");
diff --git a/chrome/browser/resources/lens/overlay/lens_overlay_app.html b/chrome/browser/resources/lens/overlay/lens_overlay_app.html index 8561e85..4677e1e3 100644 --- a/chrome/browser/resources/lens/overlay/lens_overlay_app.html +++ b/chrome/browser/resources/lens/overlay/lens_overlay_app.html
@@ -6,20 +6,10 @@ background-color: rgba(60, 60, 60, 0.5); } - .action-button { - position: absolute; - left: 0; - } - #closeButton { - top: 25px; - } - - lens-selection-overlay { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + inset-block-start: 24px; + inset-inline-end: 24px; } </style> <div class="app-container">
diff --git a/chrome/browser/resources/lens/overlay/region_selection.html b/chrome/browser/resources/lens/overlay/region_selection.html index 93f484b..c4ba3cae5 100644 --- a/chrome/browser/resources/lens/overlay/region_selection.html +++ b/chrome/browser/resources/lens/overlay/region_selection.html
@@ -13,5 +13,5 @@ width="[[canvasWidth]]"></canvas> <!-- Provides image source for canvas; element is not displayed. --> <div id="highlightImgContainer"> - <img id="highlightImgSrc" src="screenshot.jpeg"> + <img id="highlightImg" src="screenshot.jpeg"> </div>
diff --git a/chrome/browser/resources/lens/overlay/region_selection.ts b/chrome/browser/resources/lens/overlay/region_selection.ts index e49da09..c425a7ef 100644 --- a/chrome/browser/resources/lens/overlay/region_selection.ts +++ b/chrome/browser/resources/lens/overlay/region_selection.ts
@@ -13,7 +13,10 @@ import type {GestureEvent} from './selection_utils.js'; export interface RegionSelectionElement { - $: {regionSelectionCanvas: HTMLCanvasElement}; + $: { + highlightImg: HTMLImageElement, + regionSelectionCanvas: HTMLCanvasElement, + }; } /* @@ -141,9 +144,8 @@ // Draw the highlight image clipped to the path. this.context.save(); this.context.clip(); - const image = this.shadowRoot!.querySelector('#highlightImgSrc'); this.context.drawImage( - image as HTMLImageElement, 0, 0, this.canvasWidth, this.canvasHeight); + this.$.highlightImg, 0, 0, this.canvasWidth, this.canvasHeight); this.context.restore(); // Stroke the path on top of the image.
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.html b/chrome/browser/resources/lens/overlay/selection_overlay.html index 7533fd01f..9c169d26 100644 --- a/chrome/browser/resources/lens/overlay/selection_overlay.html +++ b/chrome/browser/resources/lens/overlay/selection_overlay.html
@@ -1,20 +1,40 @@ <style> + :host { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + width: 100%; + } + #selectionOverlay { display: grid; + position: relative; + } + + :host([is-resized]) #selectionOverlay { + border-radius: 28px; + overflow: hidden; } /* Force all child elements to share the same grid cell so they overlap. */ #selectionOverlay > * { grid-column: 1; grid-row: 1; + + user-select: none; + -webkit-user-drag: none; + -webkit-user-select: none; } #backgroundImage { - pointer-events: none; max-width: 100vw; max-height: 100vh; - pointer-events: none; - user-select: none; + } + + :host([is-resized]) #backgroundImage{ + max-width: calc(100vw - 24px); + max-height: calc(100vh - 24px); } #selectionElements > * { @@ -23,7 +43,7 @@ } /* Temporary red scrim to make it evident the overlay is opened. */ #scrim { - position: absolute; + position: fixed; inset: 0; background-color: rgba(255, 0, 0, 0.15); pointer-events: none;
diff --git a/chrome/browser/resources/lens/overlay/selection_overlay.ts b/chrome/browser/resources/lens/overlay/selection_overlay.ts index e06aae4..58b0537e 100644 --- a/chrome/browser/resources/lens/overlay/selection_overlay.ts +++ b/chrome/browser/resources/lens/overlay/selection_overlay.ts
@@ -16,8 +16,11 @@ import {DRAG_THRESHOLD, DragFeature, emptyGestureEvent, type GestureEvent, GestureState} from './selection_utils.js'; import type {TextLayerElement} from './text_layer.js'; +const RESIZE_THRESHOLD = 8; + export interface SelectionOverlayElement { $: { + backgroundImage: HTMLImageElement, objectSelectionLayer: ObjectLayerElement, postSelectionRenderer: PostSelectionRendererElement, regionSelectionLayer: RegionSelectionElement, @@ -42,6 +45,16 @@ return getTemplate(); } + static get properties() { + return { + isResized: { + type: Boolean, + value: false, + reflectToAttribute: true, + }, + }; + } + // The current gesture event. The coordinate values are only accurate if a // gesture has started. private currentGesture: GestureEvent = emptyGestureEvent(); @@ -51,15 +64,28 @@ private resizeObserver: ResizeObserver = new ResizeObserver(() => { this.handleResize(); }); + // We need to listen to resizes on the selectionElements separately, since + // resizeObserver will trigger before the selectionElements have a chance to + // resize. + private selectionElementsResizeObserver: ResizeObserver = + new ResizeObserver(() => { + this.handleSelectionElementsResize(); + }); + private initialWidth: number = 0; + private initialHeight: number = 0; + // Whether the selection overlay is its initial size, or has changed size. + private isResized: boolean; override connectedCallback() { super.connectedCallback(); this.resizeObserver.observe(this); + this.selectionElementsResizeObserver.observe(this.$.selectionOverlay); } override disconnectedCallback() { super.disconnectedCallback(); this.resizeObserver.unobserve(this); + this.selectionElementsResizeObserver.unobserve(this.$.selectionOverlay); } override ready() { @@ -167,7 +193,24 @@ private handleResize() { const newRect = this.getBoundingClientRect(); - this.$.regionSelectionLayer.setCanvasSizeTo(newRect.width, newRect.height); + + if (this.initialHeight === 0 || this.initialWidth === 0) { + this.initialWidth = newRect.width; + this.initialHeight = newRect.height; + } + // We allow a buffer threshold when determining if the page has been + // resized so that subtle one pixel adjustments don't trigger an entire + // page reflow. + this.isResized = + Math.abs(newRect.height - this.initialHeight) >= RESIZE_THRESHOLD || + Math.abs(newRect.width - this.initialWidth) >= RESIZE_THRESHOLD; + } + + handleSelectionElementsResize() { + const selectionOverlayBounds = + this.$.selectionOverlay.getBoundingClientRect(); + this.$.regionSelectionLayer.setCanvasSizeTo( + selectionOverlayBounds.width, selectionOverlayBounds.height); } // Updates the currentGesture to correspond with the given PointerEvent.
diff --git a/chrome/browser/resources/settings/privacy_page/security_page.html b/chrome/browser/resources/settings/privacy_page/security_page.html index 37cbcacd1..8398d22b 100644 --- a/chrome/browser/resources/settings/privacy_page/security_page.html +++ b/chrome/browser/resources/settings/privacy_page/security_page.html
@@ -59,6 +59,10 @@ padding-block-start: var(--cr-section-vertical-padding); padding-block-end: var(--cr-section-vertical-padding); } + + #learnMoreLabelContainer { + pointer-events: auto; + } </style> <picture> <source @@ -187,7 +191,7 @@ </li> </ul> <div id="learnMoreLabelContainer" class="cr-secondary-text"> - $i18nRaw{enhancedProtectionLearnMoreLabel} + $i18nRaw{safeBrowsingEnhancedLearnMoreLabel} </div> </div> </div>
diff --git a/chrome/browser/resources/settings/privacy_page/security_page.ts b/chrome/browser/resources/settings/privacy_page/security_page.ts index cbc1613..8d0bcae 100644 --- a/chrome/browser/resources/settings/privacy_page/security_page.ts +++ b/chrome/browser/resources/settings/privacy_page/security_page.ts
@@ -562,6 +562,12 @@ } // </if> + private onEnhancedProtectionLearnMoreClick_(e: Event) { + OpenWindowProxyImpl.getInstance().openUrl( + loadTimeData.getString('enhancedProtectionHelpCenterURL')); + e.preventDefault(); + } + private onSafeBrowsingExtendedReportingChange_() { this.metricsBrowserProxy_.recordSettingsPageHistogram( PrivacyElementInteractions.IMPROVE_SECURITY);
diff --git a/chrome/browser/resources/welcome/BUILD.gn b/chrome/browser/resources/welcome/BUILD.gn index 2fd06475..89b854c 100644 --- a/chrome/browser/resources/welcome/BUILD.gn +++ b/chrome/browser/resources/welcome/BUILD.gn
@@ -74,7 +74,6 @@ "google_apps/google_app_proxy.ts", "google_apps/google_apps_metrics_proxy.ts", "landing_view_proxy.ts", - "navigation_mixin.ts", "navigation_mixin_lit.ts", "ntp_background/ntp_background_metrics_proxy.ts", "ntp_background/ntp_background_proxy.ts", @@ -89,6 +88,7 @@ # Files that are passed as input to css_to_wrapper(). css_files = [ + "google_apps/nux_google_apps.css", "landing_view.css", "ntp_background/nux_ntp_background.css", "set_as_default/nux_set_as_default.css", @@ -105,6 +105,7 @@ "shared/splash_pages_shared_lit.css", "shared/step_indicator.css", "signin_view.css", + "welcome_app.css", ] icons_html_files = [ "shared/icons.html" ]
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.css b/chrome/browser/resources/welcome/google_apps/nux_google_apps.css new file mode 100644 index 0000000..a8b57c2 --- /dev/null +++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.css
@@ -0,0 +1,164 @@ +/* Copyright 2024 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * import=chrome://resources/cr_elements/cr_shared_vars.css.js + * #import=../shared/animations_lit.css.js + * #import=../shared/chooser_shared_lit.css.js + * #include=animations-lit chooser-shared-lit + * #css_wrapper_metadata_end */ + +.apps-ask { + text-align: center; +} + +.chrome-logo { + background-image: url(../images/module_icons/add_bookmarks.svg); + background-position: center bottom; + background-size: 170px 170px; + height: 146px; + margin: auto; + margin-bottom: 32px; + width: 170px; +} + +h1 { + color: var(--cr-primary-text-color); + font-size: 1.5rem; + font-weight: 500; + margin: 0; + margin-bottom: 48px; + outline: none; +} + +#appChooser { + display: block; + white-space: nowrap; +} + +.button-bar { + margin-top: 4rem; +} + +.option { + -webkit-appearance: none; + align-items: center; + border-radius: 8px; + box-sizing: border-box; + display: inline-flex; + font-family: inherit; + height: 7.5rem; + justify-content: center; + outline: 0; + position: relative; + transition-duration: 500ms; + transition-property: box-shadow; + vertical-align: bottom; + width: 6.25rem; +} + +.option:not(:first-of-type) { + margin-inline-start: 1.5rem; +} + +.option[active] { + border: 1px solid var(--cr-checked-color); + color: var(--cr-checked-color); + font-weight: 500; +} + +.option.keyboard-focused:focus { + outline: var(--navi-keyboard-focus-color) solid 3px; +} + +.option-name { + flex-grow: 0; + line-height: 1.25rem; + text-align: center; + white-space: normal; +} + +.option-icon { + background-position: center; + background-repeat: no-repeat; + background-size: contain; + height: 2rem; + margin: auto; + width: 2rem; +} + +.option-icon-shadow { + background-color: var(--navi-option-icon-shadow-color); + border-radius: 50%; + display: flex; + height: 3rem; + margin-bottom: .25rem; + width: 3rem; +} + +.option iron-icon { + --iron-icon-fill-color: var(--cr-card-background-color); + background: var(--navi-check-icon-color); + border-radius: 50%; + display: none; + height: .75rem; + margin: 0; + position: absolute; + right: .375rem; + top: .375rem; + width: .75rem; +} + +:host-context([dir=rtl]) .option iron-icon { + left: .375rem; + right: unset; +} + +.option.keyboard-focused:focus iron-icon[icon='cr:check'], +.option:hover iron-icon[icon='cr:check'], +.option[active] iron-icon[icon='cr:check'] { + display: block; +} + +.option[active] iron-icon[icon='cr:check'] { + background: var(--cr-checked-color); +} + +/* App Icons */ +.gmail { + content: image-set( + url(chrome://theme/IDS_WELCOME_GMAIL@1x) 1x, + url(chrome://theme/IDS_WELCOME_GMAIL@2x) 2x); +} + +.youtube { + content: image-set( + url(chrome://theme/IDS_WELCOME_YOUTUBE@1x) 1x, + url(chrome://theme/IDS_WELCOME_YOUTUBE@2x) 2x); +} + +.maps { + content: image-set( + url(chrome://theme/IDS_WELCOME_MAPS@1x) 1x, + url(chrome://theme/IDS_WELCOME_MAPS@2x) 2x); +} + +.translate { + content: image-set( + url(chrome://theme/IDS_WELCOME_TRANSLATE@1x) 1x, + url(chrome://theme/IDS_WELCOME_TRANSLATE@2x) 2x); +} + +.news { + content: image-set( + url(chrome://theme/IDS_WELCOME_NEWS@1x) 1x, + url(chrome://theme/IDS_WELCOME_NEWS@2x) 2x); +} + +.search { + content: image-set( + url(chrome://theme/IDS_WELCOME_SEARCH@1x) 1x, + url(chrome://theme/IDS_WELCOME_SEARCH@2x) 2x); +}
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.html b/chrome/browser/resources/welcome/google_apps/nux_google_apps.html index 51287fd..863abcb 100644 --- a/chrome/browser/resources/welcome/google_apps/nux_google_apps.html +++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.html
@@ -1,183 +1,30 @@ -<style include="animations chooser-shared"> - .apps-ask { - text-align: center; - } - - .chrome-logo { - background-image: url(../images/module_icons/add_bookmarks.svg); - background-position: center bottom; - background-size: 170px 170px; - height: 146px; - margin: auto; - margin-bottom: 32px; - width: 170px; - } - - h1 { - color: var(--cr-primary-text-color); - font-size: 1.5rem; - font-weight: 500; - margin: 0; - margin-bottom: 48px; - outline: none; - } - - #appChooser { - display: block; - white-space: nowrap; - } - - .button-bar { - margin-top: 4rem; - } - - .option { - -webkit-appearance: none; - align-items: center; - border-radius: 8px; - box-sizing: border-box; - display: inline-flex; - font-family: inherit; - height: 7.5rem; - justify-content: center; - outline: 0; - position: relative; - transition-duration: 500ms; - transition-property: box-shadow; - vertical-align: bottom; - width: 6.25rem; - } - - .option:not(:first-of-type) { - margin-inline-start: 1.5rem; - } - - .option[active] { - border: 1px solid var(--cr-checked-color); - color: var(--cr-checked-color); - font-weight: 500; - } - - .option.keyboard-focused:focus { - outline: var(--navi-keyboard-focus-color) solid 3px; - } - - .option-name { - flex-grow: 0; - line-height: 1.25rem; - text-align: center; - white-space: normal; - } - - .option-icon { - background-position: center; - background-repeat: no-repeat; - background-size: contain; - height: 2rem; - margin: auto; - width: 2rem; - } - - .option-icon-shadow { - background-color: var(--navi-option-icon-shadow-color); - border-radius: 50%; - display: flex; - height: 3rem; - margin-bottom: .25rem; - width: 3rem; - } - - .option iron-icon { - --iron-icon-fill-color: var(--cr-card-background-color); - background: var(--navi-check-icon-color); - border-radius: 50%; - display: none; - height: .75rem; - margin: 0; - position: absolute; - right: .375rem; - top: .375rem; - width: .75rem; - } - - :host-context([dir=rtl]) .option iron-icon { - left: .375rem; - right: unset; - } - - .option.keyboard-focused:focus iron-icon[icon='cr:check'], - .option:hover iron-icon[icon='cr:check'], - .option[active] iron-icon[icon='cr:check'] { - display: block; - } - - .option[active] iron-icon[icon='cr:check'] { - background: var(--cr-checked-color); - } - - /* App Icons */ - .gmail { - content: image-set( - url(chrome://theme/IDS_WELCOME_GMAIL@1x) 1x, - url(chrome://theme/IDS_WELCOME_GMAIL@2x) 2x); - } - - .youtube { - content: image-set( - url(chrome://theme/IDS_WELCOME_YOUTUBE@1x) 1x, - url(chrome://theme/IDS_WELCOME_YOUTUBE@2x) 2x); - } - - .maps { - content: image-set( - url(chrome://theme/IDS_WELCOME_MAPS@1x) 1x, - url(chrome://theme/IDS_WELCOME_MAPS@2x) 2x); - } - - .translate { - content: image-set( - url(chrome://theme/IDS_WELCOME_TRANSLATE@1x) 1x, - url(chrome://theme/IDS_WELCOME_TRANSLATE@2x) 2x); - } - - .news { - content: image-set( - url(chrome://theme/IDS_WELCOME_NEWS@1x) 1x, - url(chrome://theme/IDS_WELCOME_NEWS@2x) 2x); - } - - .search { - content: image-set( - url(chrome://theme/IDS_WELCOME_SEARCH@1x) 1x, - url(chrome://theme/IDS_WELCOME_SEARCH@2x) 2x); - } -</style> <div class="apps-ask"> <div class="chrome-logo" aria-hidden="true"></div> - <h1 tabindex="-1">[[subtitle]]</h1> + <h1 tabindex="-1">${this.subtitle}</h1> <div id="appChooser"> <div class="slide-in"> - <template is="dom-repeat" items="[[appList_]]"> - <button active$="[[item.selected]]" - aria-pressed$="[[getAriaPressed_(item.selected)]]" - on-click="onAppClick_" on-pointerdown="onAppPointerDown_" - on-keyup="onAppKeyUp_" class="option"> + ${this.appList_.map((item, index) => html` + <button ?active="${item.selected}" + aria-pressed="${item.selected}" + data-index="${index}" @click="${this.onAppClick_}" + @pointerdown="${this.onAppPointerDown_}" + @keyup="${this.onAppKeyUp_}" class="option"> <div class="option-icon-shadow"> - <div class$="[[item.icon]] option-icon"></div> + <div class="${item.icon} option-icon"></div> </div> - <div class="option-name">[[item.name]]</div> + <div class="option-name">${item.name}</div> <iron-icon icon="cr:check"></iron-icon> </button> - </template> + `)} </div> <div class="button-bar"> - <cr-button id="noThanksButton" on-click="onNoThanksClicked_"> + <cr-button id="noThanksButton" @click="${this.onNoThanksClicked_}"> $i18n{skip} </cr-button> - <step-indicator model="[[indicatorModel]]"></step-indicator> - <cr-button class="action-button" disabled$="[[!hasAppsSelected_]]" - on-click="onNextClicked_"> + <step-indicator .model="${this.indicatorModel}"></step-indicator> + <cr-button class="action-button" ?disabled="${!this.hasAppsSelected_}" + @click="${this.onNextClicked_}"> $i18n{next} <iron-icon icon="cr:chevron-right"></iron-icon> </cr-button>
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts b/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts index 8389e918..e2ec9e7f 100644 --- a/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts +++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.ts
@@ -4,20 +4,17 @@ import 'chrome://resources/cr_elements/cr_button/cr_button.js'; import 'chrome://resources/cr_elements/icons.html.js'; -import 'chrome://resources/cr_elements/cr_shared_vars.css.js'; import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; -import '../shared/animations.css.js'; -import '../shared/chooser_shared.css.js'; import '../shared/step_indicator.js'; import '../strings.m.js'; import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js'; -import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js'; +import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import {isRTL} from 'chrome://resources/js/util.js'; -import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; -import {NavigationMixin} from '../navigation_mixin.js'; +import {NavigationMixinLit} from '../navigation_mixin_lit.js'; import {navigateToNextStep} from '../router.js'; import type {BookmarkProxy} from '../shared/bookmark_proxy.js'; import {BookmarkBarManager, BookmarkProxyImpl} from '../shared/bookmark_proxy.js'; @@ -27,7 +24,8 @@ import type {GoogleAppProxy} from './google_app_proxy.js'; import {GoogleAppProxyImpl} from './google_app_proxy.js'; import {GoogleAppsMetricsProxyImpl} from './google_apps_metrics_proxy.js'; -import {getTemplate} from './nux_google_apps.html.js'; +import {getCss} from './nux_google_apps.css.js'; +import {getHtml} from './nux_google_apps.html.js'; interface AppItem { id: number; @@ -38,11 +36,6 @@ selected: boolean; } -interface AppItemModel { - item: AppItem; - set: (p1: string, p2: boolean) => void; -} - const KEYBOARD_FOCUSED = 'keyboard-focused'; export interface NuxGoogleAppsElement { @@ -51,33 +44,26 @@ }; } -const NuxGoogleAppsElementBase = I18nMixin(NavigationMixin(PolymerElement)); +const NuxGoogleAppsElementBase = I18nMixinLit(NavigationMixinLit(CrLitElement)); -/** @polymer */ export class NuxGoogleAppsElement extends NuxGoogleAppsElementBase { static get is() { return 'nux-google-apps'; } - static get template() { - return getTemplate(); + static override get styles() { + return getCss(); } - static get properties() { + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { return { - indicatorModel: Object, - - appList_: Array, - - hasAppsSelected_: { - type: Boolean, - notify: true, - }, - - subtitle: { - type: String, - value: loadTimeData.getString('googleAppsDescription'), - }, + indicatorModel: {type: Object}, + appList_: {type: Array}, + hasAppsSelected_: {type: Boolean}, }; } @@ -87,13 +73,14 @@ private bookmarkProxy_: BookmarkProxy; private bookmarkBarManager_: BookmarkBarManager; private wasBookmarkBarShownOnInit_: boolean = false; - private appList_: AppItem[]|null = null; - private hasAppsSelected_: boolean = true; + protected appList_: AppItem[] = []; + protected hasAppsSelected_: boolean = true; indicatorModel?: StepIndicatorModel; constructor() { super(); + this.subtitle = loadTimeData.getString('googleAppsDescription'); this.appProxy_ = GoogleAppProxyImpl.getInstance(); this.metricsManager_ = new ModuleMetricsManager(GoogleAppsMetricsProxyImpl.getInstance()); @@ -158,7 +145,7 @@ private cleanUp_() { this.finalized_ = true; - if (!this.appList_) { + if (this.appList_.length === 0) { return; } // No apps to remove. @@ -182,10 +169,12 @@ /** * Handle toggling the apps selected. */ - private onAppClick_(e: {model: AppItemModel}) { - const item = e.model.item; + protected onAppClick_(e: Event) { + const index = Number((e.currentTarget as HTMLElement).dataset['index']); + const item = this.appList_[index]; - e.model.set('item.selected', !item.selected); + item.selected = !item.selected; + this.requestUpdate(); this.updateBookmark_(item); this.updateHasAppsSelected_(); @@ -198,7 +187,7 @@ this.announceA11y_(this.i18n(i18nKey)); } - private onAppKeyUp_(e: KeyboardEvent) { + protected onAppKeyUp_(e: KeyboardEvent) { if (e.key === 'ArrowRight') { this.changeFocus_(e.currentTarget!, 1); } else if (e.key === 'ArrowLeft') { @@ -208,13 +197,13 @@ } } - private onAppPointerDown_(e: Event) { + protected onAppPointerDown_(e: Event) { (e.currentTarget as HTMLElement).classList.remove(KEYBOARD_FOCUSED); } - private onNextClicked_() { + protected onNextClicked_() { this.finalized_ = true; - this.appList_!.forEach(app => { + this.appList_.forEach(app => { if (app.selected) { this.appProxy_.recordProviderSelected(app.id); } @@ -223,7 +212,7 @@ navigateToNextStep(); } - private onNoThanksClicked_() { + protected onNoThanksClicked_() { this.cleanUp_(); this.metricsManager_.recordNoThanks(); navigateToNextStep(); @@ -235,7 +224,7 @@ private populateAllBookmarks_() { this.wasBookmarkBarShownOnInit_ = this.bookmarkBarManager_.getShown(); - if (this.appList_) { + if (this.appList_.length > 0) { this.appList_.forEach(app => this.updateBookmark_(app)); } else { this.appProxy_.getAppList().then(list => { @@ -275,19 +264,11 @@ * Updates the value of hasAppsSelected_. */ private updateHasAppsSelected_() { - this.hasAppsSelected_ = - !!this.appList_ && this.appList_.some(a => a.selected); + this.hasAppsSelected_ = this.appList_.some(a => a.selected); if (!this.hasAppsSelected_) { this.bookmarkBarManager_.setShown(this.wasBookmarkBarShownOnInit_); } } - - /** - * Converts a boolean to a string because aria-pressed needs a string value. - */ - private getAriaPressed_(value: boolean): string { - return value ? 'true' : 'false'; - } } declare global {
diff --git a/chrome/browser/resources/welcome/landing_view.css b/chrome/browser/resources/welcome/landing_view.css index 8cd5e48..2dfa7eaf 100644 --- a/chrome/browser/resources/welcome/landing_view.css +++ b/chrome/browser/resources/welcome/landing_view.css
@@ -7,7 +7,6 @@ * #import=./shared/animations_lit.css.js * #import=./shared/splash_pages_shared_lit.css.js * #import=./shared/action_link_style_lit.css.js - * #scheme=relative * #include=animations-lit action-link-style-lit splash-pages-shared-lit * #css_wrapper_metadata_end */
diff --git a/chrome/browser/resources/welcome/navigation_mixin.ts b/chrome/browser/resources/welcome/navigation_mixin.ts deleted file mode 100644 index c4534e3..0000000 --- a/chrome/browser/resources/welcome/navigation_mixin.ts +++ /dev/null
@@ -1,116 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @fileoverview The NavigationMixin is in charge of manipulating and - * watching window.history.state changes. The page is using the history - * state object to remember state instead of changing the URL directly, - * because the flow requires that users can use browser-back/forward to - * navigate between steps, without being able to go directly or copy an URL - * that points at a specific step. Using history.state object allows adding - * or popping history state without actually changing the path. - */ - -import '../strings.m.js'; - -import {assert} from 'chrome://resources/js/assert.js'; -import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import type {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; -import {afterNextRender, dedupingMixin} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - -import {routeObservers, setCurrentRouteElement} from './router.js'; -import type {Routes} from './router.js'; - -type Constructor<T> = new (...args: any[]) => T; - -/** - * Elements can override onRoute(Change|Enter|Exit) to handle route changes. - * Order of hooks being called: - * 1) onRouteExit() on the old route - * 2) onRouteChange() on all subscribed routes - * 3) onRouteEnter() on the new route - */ -export const NavigationMixin = dedupingMixin( - <T extends Constructor<PolymerElement>>(superClass: T): T& - Constructor<NavigationMixinInterface> => { - class NavigationMixin extends superClass { - static get properties() { - return { - subtitle: String, - }; - } - - subtitle?: string; - - override connectedCallback() { - super.connectedCallback(); - - assert(!routeObservers.has(this)); - routeObservers.add(this); - const route = (history.state.route as Routes); - const step = history.state.step; - - // history state was set when page loaded, so when the element first - // attaches, call the route-change handler to initialize first. - this.onRouteChange(route, step); - - // Modules are only attached to DOM if they're for the current route, - // so as long as the id of an element matches up to the current step, - // it means that element is for the current route. - if (this.id === `step-${step}`) { - setCurrentRouteElement(this); - this.notifyRouteEnter(); - } - } - - /** - * Notifies elements that route was entered and updates the state of the - * app based on the new route. - */ - notifyRouteEnter(): void { - this.onRouteEnter(); - this.updateFocusForA11y(); - this.updateTitle(); - } - - /** Called to update focus when progressing through the modules. */ - updateFocusForA11y(): void { - const header = this.shadowRoot!.querySelector('h1'); - if (header) { - afterNextRender(this, () => header.focus()); - } - } - - updateTitle(): void { - let title = loadTimeData.getString('headerText'); - if (this.subtitle) { - title += ' - ' + this.subtitle; - } - document.title = title; - } - - override disconnectedCallback() { - super.disconnectedCallback(); - assert(routeObservers.delete(this)); - } - - onRouteChange(_route: Routes, _step: number): void {} - onRouteEnter(): void {} - onRouteExit(): void {} - onRouteUnload(): void {} - } - - return NavigationMixin; - }); - -export interface NavigationMixinInterface { - subtitle?: string; - notifyRouteEnter(): void; - updateFocusForA11y(): void; - updateTitle(): void; - onRouteChange(route: Routes, step: number): void; - onRouteEnter(): void; - onRouteExit(): void; - onRouteUnload(): void; -}
diff --git a/chrome/browser/resources/welcome/router.ts b/chrome/browser/resources/welcome/router.ts index 3f249bb..171cbde 100644 --- a/chrome/browser/resources/welcome/router.ts +++ b/chrome/browser/resources/welcome/router.ts
@@ -4,7 +4,7 @@ import {assert} from 'chrome://resources/js/assert.js'; -import type {NavigationMixinInterface} from './navigation_mixin.js'; +import type {NavigationMixinLitInterface} from './navigation_mixin_lit.js'; /** * Valid route pathnames. @@ -15,11 +15,11 @@ RETURNING_USER = 'returning-user' } -export const routeObservers: Set<NavigationMixinInterface> = new Set(); +export const routeObservers: Set<NavigationMixinLitInterface> = new Set(); -let currentRouteElement: NavigationMixinInterface|null; +let currentRouteElement: NavigationMixinLitInterface|null; -export function setCurrentRouteElement(element: NavigationMixinInterface) { +export function setCurrentRouteElement(element: NavigationMixinLitInterface) { currentRouteElement = element; } @@ -44,7 +44,7 @@ // If currentRouteElement is not null, it means there was a new route. if (currentRouteElement) { - (currentRouteElement as NavigationMixinInterface).notifyRouteEnter(); + (currentRouteElement as NavigationMixinLitInterface).notifyRouteEnter(); } }
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.css b/chrome/browser/resources/welcome/shared/onboarding_background.css index 68b1e67..47f95954 100644 --- a/chrome/browser/resources/welcome/shared/onboarding_background.css +++ b/chrome/browser/resources/welcome/shared/onboarding_background.css
@@ -4,7 +4,6 @@ /* #css_wrapper_metadata_start * #type=style-lit - * #scheme=relative * #css_wrapper_metadata_end */ :host {
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.css b/chrome/browser/resources/welcome/shared/step_indicator.css index 03098537..806f523 100644 --- a/chrome/browser/resources/welcome/shared/step_indicator.css +++ b/chrome/browser/resources/welcome/shared/step_indicator.css
@@ -5,7 +5,6 @@ /* #css_wrapper_metadata_start * #type=style-lit * #import=./navi_colors_lit.css.js - * #scheme=relative * #include=navi-colors-lit * #css_wrapper_metadata_end */
diff --git a/chrome/browser/resources/welcome/welcome_app.css b/chrome/browser/resources/welcome/welcome_app.css new file mode 100644 index 0000000..509a821 --- /dev/null +++ b/chrome/browser/resources/welcome/welcome_app.css
@@ -0,0 +1,31 @@ +/* Copyright 2024 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * #import=chrome://resources/cr_elements/cr_hidden_style_lit.css.js + * #include=cr-hidden-style-lit + * #css_wrapper_metadata_end */ + +#viewManager { + display: flex; + font-size: 100%; + margin: 0; + min-height: 100vh; +} + +#viewManager :-webkit-any(nux-google-apps, nux-ntp-background, + nux-set-as-default) { + /* Override cr-view-manager's default styling for view. */ + bottom: initial; + left: initial; + margin: auto; + position: unset; + right: initial; + top: initial; +} + +cr-toast { + min-width: initial; +}
diff --git a/chrome/browser/resources/welcome/welcome_app.html b/chrome/browser/resources/welcome/welcome_app.html index 21e0477..48383bf 100644 --- a/chrome/browser/resources/welcome/welcome_app.html +++ b/chrome/browser/resources/welcome/welcome_app.html
@@ -1,27 +1,5 @@ -<style include="cr-hidden-style"> - #viewManager { - display: flex; - font-size: 100%; - margin: 0; - min-height: 100vh; - } - #viewManager :-webkit-any(nux-google-apps, nux-ntp-background, - nux-set-as-default) { - /* Override cr-view-manager's default styling for view. */ - bottom: initial; - left: initial; - margin: auto; - position: unset; - right: initial; - top: initial; - } - - cr-toast { - min-width: initial; - } -</style> -<cr-view-manager id="viewManager" hidden="[[!modulesInitialized_]]"> +<cr-view-manager id="viewManager" ?hidden="${!this.modulesInitialized_}"> <landing-view id="step-landing" slot="view" class="active"></landing-view> </cr-view-manager> <cr-toast duration="3000">
diff --git a/chrome/browser/resources/welcome/welcome_app.ts b/chrome/browser/resources/welcome/welcome_app.ts index 6a0d4f6..4bfbb561 100644 --- a/chrome/browser/resources/welcome/welcome_app.ts +++ b/chrome/browser/resources/welcome/welcome_app.ts
@@ -4,7 +4,6 @@ import 'chrome://resources/cr_elements/cr_toast/cr_toast.js'; import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js'; -import 'chrome://resources/cr_elements/cr_hidden_style.css.js'; import './google_apps/nux_google_apps.js'; import './landing_view.js'; import './ntp_background/nux_ntp_background.js'; @@ -16,16 +15,17 @@ import {assert} from 'chrome://resources/js/assert.js'; import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; import type {NuxGoogleAppsElement} from './google_apps/nux_google_apps.js'; -import {NavigationMixin} from './navigation_mixin.js'; +import {NavigationMixinLit} from './navigation_mixin_lit.js'; import type {NuxNtpBackgroundElement} from './ntp_background/nux_ntp_background.js'; import {Routes} from './router.js'; import type {NuxSetAsDefaultElement} from './set_as_default/nux_set_as_default.js'; import {NuxSetAsDefaultProxyImpl} from './set_as_default/nux_set_as_default_proxy.js'; import {BookmarkBarManager} from './shared/bookmark_proxy.js'; -import {getTemplate} from './welcome_app.html.js'; +import {getCss} from './welcome_app.css.js'; +import {getHtml} from './welcome_app.html.js'; import {WelcomeBrowserProxyImpl} from './welcome_browser_proxy.js'; /** @@ -60,7 +60,7 @@ }; } -const WelcomeAppElementBase = NavigationMixin(PolymerElement); +const WelcomeAppElementBase = NavigationMixinLit(CrLitElement); /** @polymer */ export class WelcomeAppElement extends WelcomeAppElementBase { @@ -68,13 +68,17 @@ return 'welcome-app'; } - static get template() { - return getTemplate(); + static override get styles() { + return getCss(); } - static get properties() { + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { return { - modulesInitialized_: Boolean, + modulesInitialized_: {type: Boolean}, }; } private currentRoute_: Routes|null = null; @@ -82,7 +86,7 @@ // Default to false so view-manager is hidden until views are // initialized. - private modulesInitialized_: boolean = false; + protected modulesInitialized_: boolean = false; constructor() { super(); @@ -93,8 +97,7 @@ }; } - override ready() { - super.ready(); + override firstUpdated() { this.setAttribute('role', 'main'); this.addEventListener( 'default-browser-change', () => this.onDefaultBrowserChange_()); @@ -187,14 +190,13 @@ let indicatorActiveCount = 0; modules.forEach((elementTagName, index) => { - const element = - document.createElement(elementTagName) as PolymerElement; + const element = document.createElement(elementTagName) as ( + NuxGoogleAppsElement | NuxNtpBackgroundElement | + NuxSetAsDefaultElement); element.id = 'step-' + (index + 1); element.setAttribute('slot', 'view'); if (MODULES_NEEDING_INDICATOR.has(elementTagName)) { - (element as NuxGoogleAppsElement | NuxNtpBackgroundElement | - NuxSetAsDefaultElement) - .indicatorModel = { + element.indicatorModel = { total: indicatorElementCount, active: indicatorActiveCount++, };
diff --git a/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc b/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc index 03bc86f..284c296b 100644 --- a/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc +++ b/chrome/browser/safe_browsing/chrome_ping_manager_factory.cc
@@ -19,11 +19,16 @@ #include "components/safe_browsing/core/browser/ping_manager.h" #include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h" #include "components/safe_browsing/core/browser/sync/sync_utils.h" +#include "components/safe_browsing/core/common/features.h" #include "components/safe_browsing/core/common/safe_browsing_prefs.h" #include "content/public/browser/browser_thread.h" namespace safe_browsing { +namespace { +bool kAllowPingManagerInTests = false; +} // namespace + // static ChromePingManagerFactory* ChromePingManagerFactory::GetInstance() { static base::NoDestructor<ChromePingManagerFactory> instance; @@ -74,7 +79,9 @@ content::GetUIThreadTaskRunner({}), base::BindRepeating(&safe_browsing::GetUserPopulationForProfile, profile), base::BindRepeating(&safe_browsing::GetPageLoadTokenForURL, profile), - std::move(hats_delegate), /*persister_root_path=*/profile->GetPath()); + std::move(hats_delegate), /*persister_root_path=*/profile->GetPath(), + base::BindRepeating(&ChromePingManagerFactory::ShouldSendPersistedReport, + profile)); } // static @@ -87,4 +94,29 @@ safe_browsing::SyncUtils::IsPrimaryAccountSignedIn(identity_manager); } +// static +bool ChromePingManagerFactory::ShouldSendPersistedReport(Profile* profile) { + return !profile->IsOffTheRecord() && + IsExtendedReportingEnabled(*profile->GetPrefs()) && + base::FeatureList::IsEnabled(kDownloadReportWithoutUserDecision); +} + +bool ChromePingManagerFactory::ServiceIsCreatedWithBrowserContext() const { + // When kDownloadReportWithoutUserDecision is enabled, PingManager is created + // at startup to send persisted reports. + return base::FeatureList::IsEnabled(kDownloadReportWithoutUserDecision); +} + +bool ChromePingManagerFactory::ServiceIsNULLWhileTesting() const { + return !kAllowPingManagerInTests; +} + +ChromePingManagerAllowerForTesting::ChromePingManagerAllowerForTesting() { + kAllowPingManagerInTests = true; +} + +ChromePingManagerAllowerForTesting::~ChromePingManagerAllowerForTesting() { + kAllowPingManagerInTests = false; +} + } // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_ping_manager_factory.h b/chrome/browser/safe_browsing/chrome_ping_manager_factory.h index fb09ea7..e9a62bd 100644 --- a/chrome/browser/safe_browsing/chrome_ping_manager_factory.h +++ b/chrome/browser/safe_browsing/chrome_ping_manager_factory.h
@@ -29,8 +29,25 @@ // BrowserContextKeyedServiceFactory override: std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext( content::BrowserContext* context) const override; + bool ServiceIsCreatedWithBrowserContext() const override; + bool ServiceIsNULLWhileTesting() const override; static bool ShouldFetchAccessTokenForReport(Profile* profile); + static bool ShouldSendPersistedReport(Profile* profile); +}; + +// Used only for tests. By default, the factory returns null for tests . To +// override it, create an object of this type and keep it in scope for as long +// as the override should exist. The constructor will set the override, and the +// destructor will clear it. +class ChromePingManagerAllowerForTesting { + public: + ChromePingManagerAllowerForTesting(); + ChromePingManagerAllowerForTesting( + const ChromePingManagerAllowerForTesting&) = delete; + ChromePingManagerAllowerForTesting& operator=( + const ChromePingManagerAllowerForTesting&) = delete; + ~ChromePingManagerAllowerForTesting(); }; } // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/chrome_ping_manager_factory_unittest.cc b/chrome/browser/safe_browsing/chrome_ping_manager_factory_unittest.cc index bf3d295..a9070619 100644 --- a/chrome/browser/safe_browsing/chrome_ping_manager_factory_unittest.cc +++ b/chrome/browser/safe_browsing/chrome_ping_manager_factory_unittest.cc
@@ -43,13 +43,15 @@ bool is_signed_in, bool expect_should_fetch); TestingProfile* SetUpProfile(bool is_enhanced_protection, bool is_signed_in); + bool ShouldSendPersistedReport(Profile* profile); content::BrowserTaskEnvironment task_environment_; std::unique_ptr<TestingProfileManager> profile_manager_; + base::test::ScopedFeatureList feature_list_; private: scoped_refptr<safe_browsing::SafeBrowsingService> sb_service_; - base::test::ScopedFeatureList feature_list_; + ChromePingManagerAllowerForTesting allow_ping_manager_; }; void ChromePingManagerFactoryTest::SetUp() { @@ -136,6 +138,10 @@ PingManager::ReportThreatDetailsResult::SUCCESS); } +bool ChromePingManagerFactoryTest::ShouldSendPersistedReport(Profile* profile) { + return ChromePingManagerFactory::ShouldSendPersistedReport(profile); +} + TEST_F(ChromePingManagerFactoryTest, ReportThreatDetails) { RunReportThreatDetailsTest(); } @@ -156,6 +162,39 @@ /*is_signed_in=*/true, /*expect_should_fetch=*/false); } + +TEST_F(ChromePingManagerFactoryTest, ShouldSendPersistedReport_Yes) { + feature_list_.InitAndEnableFeature(kDownloadReportWithoutUserDecision); + TestingProfile* profile = + SetUpProfile(/*is_enhanced_protection=*/true, /*is_signed_in=*/false); + EXPECT_EQ(ShouldSendPersistedReport(profile), true); +} + +TEST_F(ChromePingManagerFactoryTest, + ShouldSendPersistedReport_NotEnhancedProtection) { + feature_list_.InitAndEnableFeature(kDownloadReportWithoutUserDecision); + TestingProfile* profile = + SetUpProfile(/*is_enhanced_protection=*/false, /*is_signed_in=*/false); + EXPECT_EQ(ShouldSendPersistedReport(profile), false); +} + +TEST_F(ChromePingManagerFactoryTest, ShouldSendPersistedReport_Incognito) { + feature_list_.InitAndEnableFeature(kDownloadReportWithoutUserDecision); + TestingProfile* profile = + SetUpProfile(/*is_enhanced_protection=*/true, /*is_signed_in=*/false); + EXPECT_EQ(ShouldSendPersistedReport( + TestingProfile::Builder().BuildIncognito(profile)), + false); +} + +TEST_F(ChromePingManagerFactoryTest, + ShouldSendPersistedReport_FeatureDisabled) { + feature_list_.InitAndDisableFeature(kDownloadReportWithoutUserDecision); + TestingProfile* profile = + SetUpProfile(/*is_enhanced_protection=*/true, /*is_signed_in=*/false); + EXPECT_EQ(ShouldSendPersistedReport(profile), false); +} + TEST_F(ChromePingManagerFactoryTest, NoPingManagerForIncognito) { TestingProfile* profile = TestingProfile::Builder().BuildIncognito( profile_manager_->CreateTestingProfile("testing_profile"));
diff --git a/chrome/browser/safe_browsing/phishy_interaction_tracker_unittest.cc b/chrome/browser/safe_browsing/phishy_interaction_tracker_unittest.cc index 9dd8648a..f256ca4d 100644 --- a/chrome/browser/safe_browsing/phishy_interaction_tracker_unittest.cc +++ b/chrome/browser/safe_browsing/phishy_interaction_tracker_unittest.cc
@@ -214,6 +214,7 @@ scoped_refptr<safe_browsing::SafeBrowsingService> sb_service_; std::unique_ptr<PhishyInteractionTracker> phishy_interaction_tracker_; scoped_refptr<MockSafeBrowsingUIManager> ui_manager_; + safe_browsing::ChromePingManagerAllowerForTesting allow_ping_manager_; }; TEST_F(PhishyInteractionTrackerTest, CheckHistogramCountsOnPhishyUserEvents) {
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc index 4ba282c..149f3398 100644 --- a/chrome/browser/safe_browsing/safe_browsing_service.cc +++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -277,6 +277,10 @@ SafeBrowsingService::GetURLLoaderFactory( content::BrowserContext* browser_context) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (url_loader_factory_for_testing_) { + return url_loader_factory_for_testing_; + } + NetworkContextService* service = NetworkContextServiceFactory::GetForBrowserContext(browser_context); if (!service) { @@ -551,9 +555,13 @@ show_download_in_folder); Profile* profile = Profile::FromBrowserContext( content::DownloadItemUtils::GetBrowserContext(download)); - return ChromePingManagerFactory::GetForBrowserContext(profile) - ->PersistThreatDetailsAndReportOnNextStartup(std::move(report)) == - PingManager::PersistThreatDetailsResult::kPersistTaskPosted; + PingManager::PersistThreatDetailsResult result = + ChromePingManagerFactory::GetForBrowserContext(profile) + ->PersistThreatDetailsAndReportOnNextStartup(std::move(report)); + base::UmaHistogramEnumeration( + "SafeBrowsing.ClientSafeBrowsingReport.PersistDownloadReportResult", + result); + return result == PingManager::PersistThreatDetailsResult::kPersistTaskPosted; } bool SafeBrowsingService::SendPhishyInteractionsReport(
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.h b/chrome/browser/safe_browsing/safe_browsing_service.h index 76ffb70..3a91b47 100644 --- a/chrome/browser/safe_browsing/safe_browsing_service.h +++ b/chrome/browser/safe_browsing/safe_browsing_service.h
@@ -145,6 +145,11 @@ void FlushNetworkInterfaceForTesting( content::BrowserContext* browser_context); + void SetURLLoaderFactoryForTesting( + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { + url_loader_factory_for_testing_ = url_loader_factory; + } + const scoped_refptr<SafeBrowsingUIManager>& ui_manager() const; virtual const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager() @@ -361,6 +366,9 @@ std::unique_ptr<TriggerManager> trigger_manager_; bool url_is_allowlisted_for_testing_ = false; + + scoped_refptr<network::SharedURLLoaderFactory> + url_loader_factory_for_testing_; }; SafeBrowsingServiceFactory* GetSafeBrowsingServiceFactory();
diff --git a/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc index 4eb901e..f7ce20f 100644 --- a/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc +++ b/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "chrome/browser/safe_browsing/safe_browsing_service.h" + #include <memory> #include "base/test/bind.h" @@ -59,6 +60,10 @@ // the interface in components/safe_browsing, and remove this cast. sb_service_ = static_cast<SafeBrowsingService*>( safe_browsing::SafeBrowsingService::CreateSafeBrowsingService()); + auto ref_counted_url_loader_factory = + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory_); + sb_service_->SetURLLoaderFactoryForTesting(ref_counted_url_loader_factory); browser_process_->SetSafeBrowsingService(sb_service_.get()); sb_service_->Initialize(); base::RunLoop().RunUntilIdle(); @@ -72,6 +77,7 @@ } void TearDown() override { + sb_service_->SetURLLoaderFactoryForTesting(nullptr); browser_process_->safe_browsing_service()->ShutDown(); browser_process_->SetSafeBrowsingService(nullptr); safe_browsing::SafeBrowsingServiceInterface::RegisterFactory(nullptr); @@ -194,7 +200,9 @@ GURL download_url_ = GURL(kTestDownloadUrl); private: + network::TestURLLoaderFactory test_url_loader_factory_; base::test::ScopedFeatureList feature_list_; + ChromePingManagerAllowerForTesting allow_ping_manager_; }; TEST_F(SafeBrowsingServiceTest, SendDownloadReport_Success) {
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java index 8b84967..2e54b11 100644 --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java
@@ -187,6 +187,10 @@ protected static <T extends PersistedTabData> void from( Tab tab, Supplier<T> supplier, Class<T> clazz, Callback<T> callback) { ThreadUtils.assertOnUiThread(); + if (!tab.isInitialized() || tab.isDestroyed() || tab.isCustomTab()) { + onInvalidTab(callback); + return; + } T userData = getUserData(tab, clazz); // {@link PersistedTabData} already attached to {@link Tab} if (userData != null) { @@ -209,6 +213,10 @@ tab.getId(), config.getId(), (data) -> { + if (tab.isDestroyed()) { + onInvalidTab(callback); + return; + } // No stored {@link PersistedTabData} found, return null. if (data == null || data.limit() == 0) { PostTask.postTask( @@ -224,6 +232,10 @@ PostTask.postTask( TaskTraits.USER_BLOCKING_MAY_BLOCK, () -> { + if (tab.isDestroyed()) { + onInvalidTab(callback); + return; + } persistedTabData.deserializeAndLog(data); // Post result back to UI thread. PostTask.postTask( @@ -237,6 +249,14 @@ }); } + private static <T extends PersistedTabData> void onInvalidTab(Callback<T> callback) { + PostTask.postTask( + TaskTraits.UI_DEFAULT, + () -> { + callback.onResult(null); + }); + } + private static <T extends PersistedTabData> void onPersistedTabDataRetrieved( ByteBuffer data, PersistedTabDataConfiguration config,
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index dda5bce..f6c9c27d 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -2807,6 +2807,8 @@ "ash/network/network_state_notifier.h", "ash/network/tether_notification_presenter.cc", "ash/network/tether_notification_presenter.h", + "ash/picker/picker_lacros_omnibox_search_provider.cc", + "ash/picker/picker_lacros_omnibox_search_provider.h", "ash/picker/picker_client_impl.cc", "ash/picker/picker_client_impl.h", "ash/picker/picker_file_suggester.cc",
diff --git a/chrome/browser/ui/ash/picker/picker_client_impl.cc b/chrome/browser/ui/ash/picker/picker_client_impl.cc index d7c8bfa..b44ef7bf 100644 --- a/chrome/browser/ui/ash/picker/picker_client_impl.cc +++ b/chrome/browser/ui/ash/picker/picker_client_impl.cc
@@ -34,12 +34,12 @@ #include "chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h" #include "chrome/browser/ash/app_list/search/search_engine.h" #include "chrome/browser/ash/crosapi/browser_util.h" -#include "chrome/browser/ash/crosapi/crosapi_manager.h" #include "chrome/browser/ash/file_manager/fileapi_util.h" #include "chrome/browser/ash/input_method/editor_mediator_factory.h" #include "chrome/browser/chromeos/launcher_search/search_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/ash/picker/picker_file_suggester.h" +#include "chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h" #include "chrome/browser/ui/webui/ash/emoji/emoji_picker.mojom-forward.h" #include "chrome/browser/ui/webui/ash/emoji/emoji_picker.mojom-shared.h" #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h" @@ -378,11 +378,10 @@ bool history, bool open_tabs) { if (crosapi::browser_util::IsLacrosEnabled()) { - // TODO: b/326147929 - Add autocomplete provider types for the Lacros - // provider. return std::make_unique<app_list::OmniboxLacrosProvider>( profile_, &app_list_controller_delegate_, - app_list::OmniboxLacrosProvider::GetSingletonControllerCallback()); + PickerLacrosOmniboxSearchProvider::CreateControllerCallback( + bookmarks, history, open_tabs)); } else { return std::make_unique<app_list::OmniboxProvider>( profile_, &app_list_controller_delegate_,
diff --git a/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.cc b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.cc new file mode 100644 index 0000000..77c5990 --- /dev/null +++ b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.cc
@@ -0,0 +1,65 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h" + +#include <memory> +#include <utility> + +#include "base/check_deref.h" +#include "base/functional/bind.h" +#include "chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h" +#include "chrome/browser/ash/crosapi/crosapi_ash.h" +#include "chrome/browser/ash/crosapi/crosapi_manager.h" +#include "chrome/browser/ash/crosapi/search_controller_ash.h" +#include "chrome/browser/ash/crosapi/search_controller_factory_ash.h" + +PickerLacrosOmniboxSearchProvider::PickerLacrosOmniboxSearchProvider( + crosapi::SearchControllerFactoryAsh* factory, + bool bookmarks, + bool history, + bool open_tabs) + : bookmarks_(bookmarks), + history_(history), + open_tabs_(open_tabs), + factory_(CHECK_DEREF(factory)) { + if (factory_->IsBound()) { + controller_ = factory_->CreateSearchControllerPicker(bookmarks_, history_, + open_tabs_); + } +} + +PickerLacrosOmniboxSearchProvider::~PickerLacrosOmniboxSearchProvider() = + default; + +crosapi::SearchControllerAsh* +PickerLacrosOmniboxSearchProvider::GetController() { + if (!controller_ && factory_->IsBound()) { + controller_ = factory_->CreateSearchControllerPicker(bookmarks_, history_, + open_tabs_); + } + return controller_.get(); +} + +app_list::OmniboxLacrosProvider::SearchControllerCallback +PickerLacrosOmniboxSearchProvider::CreateControllerCallback(bool bookmarks, + bool history, + bool open_tabs) { + // The following dereferences are safe, because `CrosapiManager::Get()` + // `DCHECK`s the returned pointer, and both `CrosapiManager::crosapi_ash()` + // and `CrosapiAsh::search_controller_factory_ash()` return a pointer to a + // `std::unique_ptr`, which are initialised when the classes are constructed + // and never reset. + crosapi::SearchControllerFactoryAsh* factory = + crosapi::CrosapiManager::Get() + ->crosapi_ash() + ->search_controller_factory_ash(); + auto provider = std::make_unique<PickerLacrosOmniboxSearchProvider>( + factory, bookmarks, history, open_tabs); + return base::BindRepeating( + [](const std::unique_ptr<PickerLacrosOmniboxSearchProvider>& provider) { + return provider->GetController(); + }, + std::move(provider)); +}
diff --git a/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h new file mode 100644 index 0000000..74d6ddc9 --- /dev/null +++ b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h
@@ -0,0 +1,54 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_ASH_PICKER_PICKER_LACROS_OMNIBOX_SEARCH_PROVIDER_H_ +#define CHROME_BROWSER_UI_ASH_PICKER_PICKER_LACROS_OMNIBOX_SEARCH_PROVIDER_H_ + +#include <memory> + +#include "base/memory/raw_ref.h" +#include "chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h" + +namespace crosapi { +class SearchControllerAsh; +class SearchControllerFactoryAsh; +} + +// Manages a dedicated Picker `crosapi::SearchControllerAsh` obtained from a +// given `crosapi::SearchControllerFactoryAsh`. +// Intended to be used to construct a `app_list::OmniboxLacrosProvider` - see +// the `CreateControllerCallback` static method below. +class PickerLacrosOmniboxSearchProvider { + public: + explicit PickerLacrosOmniboxSearchProvider( + crosapi::SearchControllerFactoryAsh* factory, + bool bookmarks, + bool history, + bool open_tabs); + PickerLacrosOmniboxSearchProvider(const PickerLacrosOmniboxSearchProvider&) = + delete; + PickerLacrosOmniboxSearchProvider& operator=( + const PickerLacrosOmniboxSearchProvider&) = delete; + ~PickerLacrosOmniboxSearchProvider(); + + crosapi::SearchControllerAsh* GetController(); + + // Returns a `SearchControllerCallback` for use with + // `app_list::OmniboxLacrosProvider` which uses the singleton + // `crosapi::SearchControllerFactoryAsh` to create a dedicated search + // controller for Picker. + static app_list::OmniboxLacrosProvider::SearchControllerCallback + CreateControllerCallback(bool bookmarks, bool history, bool open_tabs); + + private: + bool bookmarks_; + bool history_; + bool open_tabs_; + + std::unique_ptr<crosapi::SearchControllerAsh> controller_; + + base::raw_ref<crosapi::SearchControllerFactoryAsh> factory_; +}; + +#endif // CHROME_BROWSER_UI_ASH_PICKER_PICKER_LACROS_OMNIBOX_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider_unittest.cc b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider_unittest.cc new file mode 100644 index 0000000..41459b4b --- /dev/null +++ b/chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider_unittest.cc
@@ -0,0 +1,298 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h" + +#include <memory> +#include <string> +#include <utility> + +#include "base/auto_reset.h" +#include "base/check.h" +#include "base/functional/callback_forward.h" +#include "base/functional/callback_helpers.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "chrome/browser/ash/crosapi/search_controller_ash.h" +#include "chrome/browser/ash/crosapi/search_controller_factory_ash.h" +#include "chromeos/crosapi/mojom/launcher_search.mojom-forward.h" +#include "chromeos/crosapi/mojom/launcher_search.mojom.h" +#include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// TODO: b/326147929 - Share this code with `crosapi::SearchControllerAsh` and +// `crosapi::SearchControllerFactoryAsh` unit tests. +class TestMojomSearchController : public crosapi::mojom::SearchController { + public: + explicit TestMojomSearchController(bool bookmarks, + bool history, + bool open_tabs) + : bookmarks_(bookmarks), history_(history), open_tabs_(open_tabs) {} + ~TestMojomSearchController() override = default; + + void Bind(mojo::PendingReceiver<crosapi::mojom::SearchController> receiver) { + receiver_.Bind(std::move(receiver)); + } + + void RunUntilSearch() { + base::RunLoop loop; + base::AutoReset<base::RepeatingClosure> quit_loop(&search_callback_, + loop.QuitClosure()); + loop.Run(); + } + + const std::u16string& last_query() { return last_query_; } + bool bookmarks() const { return bookmarks_; } + bool history() const { return history_; } + bool open_tabs() const { return open_tabs_; } + + private: + void Search(const std::u16string& query, SearchCallback callback) override { + last_query_ = query; + // We are not interested in the search callback in the class under test, but + // we still need to run the callback with a valid receiver. + std::move(callback).Run( + mojo::AssociatedRemote<crosapi::mojom::SearchResultsPublisher>() + .BindNewEndpointAndPassReceiver()); + + search_callback_.Run(); + } + + base::RepeatingClosure search_callback_ = base::DoNothing(); + + mojo::Receiver<crosapi::mojom::SearchController> receiver_{this}; + bool bookmarks_; + bool history_; + bool open_tabs_; + std::u16string last_query_; +}; + +class TestMojomSearchControllerFactory + : public crosapi::mojom::SearchControllerFactory { + public: + TestMojomSearchControllerFactory() = default; + ~TestMojomSearchControllerFactory() override = default; + + mojo::PendingRemote<crosapi::mojom::SearchControllerFactory> BindToRemote() { + return receiver_.BindNewPipeAndPassRemote(); + } + + void RunUntilCreateSearchController() { + base::RunLoop loop; + base::AutoReset<base::RepeatingClosure> quit_loop( + &create_search_controller_callback_, loop.QuitClosure()); + loop.Run(); + } + + std::unique_ptr<TestMojomSearchController> TakeLastTestController() { + return std::move(last_test_controller_); + } + + // cam::SearchControllerFactory overrides: + void CreateSearchControllerPicker( + mojo::PendingReceiver<crosapi::mojom::SearchController> controller, + bool bookmark, + bool history, + bool open_tab) override { + CHECK(!last_test_controller_); + last_test_controller_ = std::make_unique<TestMojomSearchController>( + bookmark, history, open_tab); + last_test_controller_->Bind(std::move(controller)); + + create_search_controller_callback_.Run(); + } + + private: + base::RepeatingClosure create_search_controller_callback_ = base::DoNothing(); + + std::unique_ptr<TestMojomSearchController> last_test_controller_; + mojo::Receiver<crosapi::mojom::SearchControllerFactory> receiver_{this}; +}; + +using PickerLacrosOmniboxSearchProviderTest = ::testing::Test; + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + ControllerIsNullptrWhenNotBoundOnConstruction) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + ASSERT_FALSE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + crosapi::SearchControllerAsh* controller = provider.GetController(); + + EXPECT_FALSE(controller); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + ControllerIsNonNullWhenBoundOnConstruction) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + ASSERT_TRUE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + crosapi::SearchControllerAsh* controller = provider.GetController(); + + EXPECT_TRUE(controller); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + ControllerIsNonNullWhenBoundAfterConstruction) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + ASSERT_FALSE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + ASSERT_TRUE(factory.IsBound()); + crosapi::SearchControllerAsh* controller = provider.GetController(); + + EXPECT_TRUE(controller); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + CallsFactoryOnConstructionIfBound) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + ASSERT_TRUE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + mojom_factory.RunUntilCreateSearchController(); + + EXPECT_TRUE(mojom_factory.TakeLastTestController()); +} + +// The three tests below show the UNWANTED behaviour of the provider. +// The provider SHOULD re-call the factory when it is bound, not when +// `GetController` is called. +TEST_F(PickerLacrosOmniboxSearchProviderTest, + CallsFactoryAfterConstructionOnGetController) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + ASSERT_FALSE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + ASSERT_TRUE(factory.IsBound()); + (void)provider.GetController(); + mojom_factory.RunUntilCreateSearchController(); + + EXPECT_TRUE(mojom_factory.TakeLastTestController()); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + DoesNotCallFactoryAfterConstructionWhenBound) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + ASSERT_FALSE(factory.IsBound()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + ASSERT_TRUE(factory.IsBound()); + // Ensure that the factory is NOT called. + // There is no way to have a callback for "function is not called", so we need + // to use `RunUntilIdle` here. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(mojom_factory.TakeLastTestController()); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, DoesNotCallFactoryWhenRebound) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + { + // Connect a remote factory... + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + (void)provider.GetController(); + mojom_factory.RunUntilCreateSearchController(); + ASSERT_TRUE(mojom_factory.TakeLastTestController()); + // ...then disconnect it. + } + // Ensure that the factory receives the disconnection so it can be rebound. + // TODO: b/326147929 - Use a `QuitClosure` for this. + base::RunLoop().RunUntilIdle(); + + // Rebind another remote factory. + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + (void)provider.GetController(); + // Ensure that the factory is NOT called. + // There is no way to have a callback for "function is not called", so we need + // to use `RunUntilIdle` here. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(mojom_factory.TakeLastTestController()); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, DoesNotCallFactoryMultipleTimes) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + mojom_factory.RunUntilCreateSearchController(); + ASSERT_TRUE(mojom_factory.TakeLastTestController()); + (void)provider.GetController(); + (void)provider.GetController(); + // Ensure that the factory is NOT called. + // There is no way to have a callback for "function is not called", so we need + // to use `RunUntilIdle` here. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(mojom_factory.TakeLastTestController()); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + ControllerSendsToRemoteControllerFromFactory) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + PickerLacrosOmniboxSearchProvider provider(&factory, false, false, false); + crosapi::SearchControllerAsh* controller = provider.GetController(); + ASSERT_TRUE(controller); + mojom_factory.RunUntilCreateSearchController(); + std::unique_ptr<TestMojomSearchController> mojom_controller = + mojom_factory.TakeLastTestController(); + ASSERT_TRUE(mojom_controller); + + controller->Search(u"cat", base::DoNothing()); + mojom_controller->RunUntilSearch(); + + EXPECT_EQ(mojom_controller->last_query(), u"cat"); +} + +TEST_F(PickerLacrosOmniboxSearchProviderTest, + RemoteControllerHasCorrectProviderTypes) { + base::test::SingleThreadTaskEnvironment environment; + crosapi::SearchControllerFactoryAsh factory; + TestMojomSearchControllerFactory mojom_factory; + factory.BindRemote(mojom_factory.BindToRemote()); + + PickerLacrosOmniboxSearchProvider provider( + &factory, /*bookmarks=*/true, /*history=*/false, /*open_tabs=*/true); + mojom_factory.RunUntilCreateSearchController(); + + std::unique_ptr<TestMojomSearchController> mojom_controller = + mojom_factory.TakeLastTestController(); + ASSERT_TRUE(mojom_controller); + EXPECT_TRUE(mojom_controller->bookmarks()); + EXPECT_FALSE(mojom_controller->history()); + EXPECT_TRUE(mojom_controller->open_tabs()); +} + +} // namespace
diff --git a/chrome/browser/ui/omnibox/omnibox_theme.h b/chrome/browser/ui/omnibox/omnibox_theme.h index dd3babf2..8791f8c 100644 --- a/chrome/browser/ui/omnibox/omnibox_theme.h +++ b/chrome/browser/ui/omnibox/omnibox_theme.h
@@ -7,18 +7,17 @@ #include "chrome/browser/ui/color/chrome_color_id.h" -enum class OmniboxPartState { - NORMAL, - HOVERED, - SELECTED, -}; +enum class OmniboxPartState { NORMAL, HOVERED, SELECTED, IPH }; constexpr float kOmniboxOpacityHovered = 0.10f; constexpr float kOmniboxOpacitySelected = 0.16f; inline ui::ColorId GetOmniboxBackgroundColorId(OmniboxPartState state) { + // TODO(crbug.com/333762301): Update the background color for the IPH + // suggestion. constexpr ui::ColorId kIds[] = {kColorOmniboxResultsBackground, kColorOmniboxResultsBackgroundHovered, + kColorOmniboxResultsBackgroundSelected, kColorOmniboxResultsBackgroundSelected}; return kIds[static_cast<size_t>(state)]; }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc index 1b51d17..1e439fb7 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -318,6 +318,13 @@ if (part_state == OmniboxPartState::NORMAL && !prefers_contrast) return nullptr; + if (OmniboxFieldTrial::IsStarterPackIPHEnabled() && + part_state == OmniboxPartState::IPH) { + return views::CreateThemedRoundedRectBackground( + GetOmniboxBackgroundColorId(part_state), /*radius=*/8, + /*for_border_thickness=*/0); + } + if (OmniboxFieldTrial::IsChromeRefreshSuggestHoverFillShapeEnabled()) { gfx::RoundedCornersF radii = {0, static_cast<float>(view->height()), static_cast<float>(view->height()), 0}; @@ -482,8 +489,18 @@ // NULL_RESULT_MESSAGE matches are no-op suggestions that only deliver a // message. The selected and hovered states imply an action can be taken from // that suggestion, so do not allow those states for this result. - if (match_.type == AutocompleteMatchType::NULL_RESULT_MESSAGE) - return OmniboxPartState::NORMAL; + // + // IPH messages originate from the Featured Search Provider and show a + // different theme state (colored background). + // TODO(crbug.com/333762301): Probably makes sense to find a more sustainable + // way to differentiate IPH from the "No Results Found" suggestion. Maybe a + // different autocomplete match type. + if (match_.type == AutocompleteMatchType::NULL_RESULT_MESSAGE) { + bool is_iph = OmniboxFieldTrial::IsStarterPackIPHEnabled() && + match_.provider->type() == + AutocompleteProvider::Type::TYPE_FEATURED_SEARCH; + return is_iph ? OmniboxPartState::IPH : OmniboxPartState::NORMAL; + } if (GetMatchSelected()) return OmniboxPartState::SELECTED;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_row_view.cc b/chrome/browser/ui/views/omnibox/omnibox_row_view.cc index c77357a..84a3fea 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_row_view.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_row_view.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ui/views/omnibox/omnibox_row_view.h" #include "chrome/browser/ui/color/chrome_color_id.h" +#include "chrome/browser/ui/omnibox/omnibox_theme.h" #include "chrome/browser/ui/views/omnibox/omnibox_header_view.h" #include "chrome/browser/ui/views/omnibox/omnibox_popup_view_views.h" #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h" @@ -116,6 +117,10 @@ !OmniboxFieldTrial::IsChromeRefreshSuggestIconsEnabled()) { return gfx::Insets::TLBR(4, 0, 0, right_inset); } + if (OmniboxFieldTrial::IsStarterPackIPHEnabled() && + result_view_->GetThemeState() == OmniboxPartState::IPH) { + return gfx::Insets::TLBR(8, 8, 8, 16); + } return gfx::Insets::TLBR(0, 0, 0, right_inset); }
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc index ae89b76..b0e1af6 100644 --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1832,7 +1832,8 @@ {"chromeCertificatesDescription", IDS_SETTINGS_CHROME_CERTIFICATES_DESCRIPTION}, #endif - }; + {"safeBrowsingEnhancedLearnMoreLabel", + IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL}}; html_source->AddLocalizedStrings(kLocalizedStrings); html_source->AddString("cookiesSettingsHelpCenterURL", @@ -1857,12 +1858,6 @@ ? chrome::kSafeBrowsingHelpCenterUpdatedURL : chrome::kSafeBrowsingHelpCenterURL); - html_source->AddString( - "enhancedProtectionLearnMoreLabel", - l10n_util::GetStringFUTF16( - IDS_SETTINGS_SAFEBROWSING_ENHANCED_LEARN_MORE_LABEL, - chrome::kSafeBrowsingInChromeHelpCenterURL)); - html_source->AddString("syncAndGoogleServicesLearnMoreURL", chrome::kSyncAndGoogleServicesLearnMoreURL); @@ -1879,6 +1874,8 @@ html_source->AddBoolean("driveSuggestNoSyncRequirement", base::FeatureList::IsEnabled( omnibox::kDocumentProviderNoSyncRequirement)); + html_source->AddString("enhancedProtectionHelpCenterURL", + chrome::kSafeBrowsingInChromeHelpCenterURL); bool show_secure_dns = IsSecureDnsAvailable(); bool link_secure_dns = ShouldLinkSecureDnsOsSettings();
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt index 41e8864d..64cb2a65 100644 --- a/chrome/build/android-arm32.pgo.txt +++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@ -chrome-android32-main-1712944793-6c2b3c82ec4dda250ae5820cf42a6c07a16bef25-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-android32-main-1712987449-7dd5b46742c0695a911e8e4512443f1f540ca842-6f3514f173e1d1e27a5fef4995511f7f41516460.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index b201ffa..630fd040 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1712944793-32d6b78f973749f8ef53f71e0e6b7653cb6a73a9-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-android64-main-1712972859-5d48efe501c3566fba5dd99b73d8d5f0e21ab716-d93ead5578559e1260a4457b13176e1b2c632f88.profdata
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt index 0c3bf1f..c761a90caa 100644 --- a/chrome/build/lacros64.pgo.txt +++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@ -chrome-chromeos-amd64-generic-main-1712923045-5b33955d2f6555f4386bb10e322b3d9658eda86c-78fcd7bd9bbf8d0b9b0f5bd7c7348461ea0362fe.profdata +chrome-chromeos-amd64-generic-main-1712966818-62e4d31fe423af8c1e7aba25312838795d06b02f-e081eabaf23f8bc38c820e8bf452b35ee71e0c51.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 54edf91..d440de2 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1712944793-2483263ad14e801b606f823fa79298cf632f9f78-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-linux-main-1712987449-4ce950049b56e22fced0548fa25f8bb726ce273b-6f3514f173e1d1e27a5fef4995511f7f41516460.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 77b22a5..e947ef57 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1712959143-b058c2d51303c73da8b4e47020bfa3699b92bb82-941c34d78fc4a31fb6a5d87aa661115dc9ea640c.profdata +chrome-mac-arm-main-1713001580-586b194c248fe14c5880691a09d53aa2bcc40f27-87853405a6894e2d3eca1e678a07e60d3becfdcb.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 10452f0..343278a 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1712944793-e0a9b989e7df4252f86291566b45c6cb090d0f5e-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-mac-main-1712966302-c77b4f8ebb0229604d6a3498094f297b82b6a3b0-df16b1e5e37e908a9e59e72f15f7a5ae2c12ac71.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt index 806a8a3..a78faa6 100644 --- a/chrome/build/win-arm64.pgo.txt +++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@ -chrome-win-arm64-main-1712944793-2f7fbc6d118513c0a169e7b4ed549939068ce790-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-win-arm64-main-1712987449-75ba781fabbf1f127475d9b5f171cbd91146ad72-6f3514f173e1d1e27a5fef4995511f7f41516460.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index ba0cc469..27087979 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1712944793-be04f890082e93ea0a1bc0a74b914213b2c947d6-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-win32-main-1712987449-8188c22e8e406da9ddf98a1f67b118b95bc5420e-6f3514f173e1d1e27a5fef4995511f7f41516460.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 2c71afa..5989e33 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1712944793-410e7e14f7a3cccaa5c0fb3a1c6510528eb59950-bf0f3b907a9a0e3acc1e4d0ef924d586e4a2f9c5.profdata +chrome-win64-main-1712987449-ac9bddae1b4ca71a0d7253d4a7d7f47411119f53-6f3514f173e1d1e27a5fef4995511f7f41516460.profdata
diff --git a/chrome/common/chromeos/extensions/api/_permission_features.json b/chrome/common/chromeos/extensions/api/_permission_features.json index bdd1a21..3f4821a 100644 --- a/chrome/common/chromeos/extensions/api/_permission_features.json +++ b/chrome/common/chromeos/extensions/api/_permission_features.json
@@ -29,6 +29,13 @@ ], "dependencies": [ "manifest:chromeos_system_extension" ] }, + "os.diagnostics.network_info_mlab": { + "channel": "stable", + "extension_types": [ + "chromeos_system_extension" + ], + "dependencies": [ "manifest:chromeos_system_extension" ] + }, "os.events": { "channel": "stable", "extension_types": [
diff --git a/chrome/common/chromeos/extensions/chromeos_system_extensions_api_permissions.cc b/chrome/common/chromeos/extensions/chromeos_system_extensions_api_permissions.cc index cf16517..eaede715e 100644 --- a/chrome/common/chromeos/extensions/chromeos_system_extensions_api_permissions.cc +++ b/chrome/common/chromeos/extensions/chromeos_system_extensions_api_permissions.cc
@@ -26,6 +26,8 @@ {APIPermissionID::kChromeOSBluetoothPeripheralsInfo, "os.bluetooth_peripherals_info"}, {APIPermissionID::kChromeOSDiagnostics, "os.diagnostics"}, + {APIPermissionID::kChromeOSDiagnosticsNetworkInfoForMlab, + "os.diagnostics.network_info_mlab"}, {APIPermissionID::kChromeOSEvents, "os.events"}, {APIPermissionID::kChromeOSManagementAudio, "os.management.audio"}, {APIPermissionID::kChromeOSTelemetry, "os.telemetry"},
diff --git a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc index ede23107..7b47aa1 100644 --- a/chrome/common/extensions/permissions/chrome_permission_message_rules.cc +++ b/chrome/common/extensions/permissions/chrome_permission_message_rules.cc
@@ -752,6 +752,9 @@ {IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS, {APIPermissionID::kChromeOSDiagnostics}, {}}, + {IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS_NETWORK_INFO_FOR_MLAB, + {APIPermissionID::kChromeOSDiagnosticsNetworkInfoForMlab}, + {}}, {IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_EVENTS, {APIPermissionID::kChromeOSEvents}, {}},
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index e8b749e..6478728 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -6163,6 +6163,7 @@ "../browser/data_sharing/data_sharing_service_factory_unittest.cc", "../browser/download/chrome_download_manager_delegate_unittest.cc", "../browser/download/deferred_client_wrapper_unittest.cc", + "../browser/download/download_core_service_impl_unittest.cc", "../browser/download/download_history_unittest.cc", "../browser/download/download_item_model_unittest.cc", "../browser/download/download_item_warning_data_unittest.cc", @@ -8579,6 +8580,7 @@ "../browser/ui/ash/network/network_state_notifier_unittest.cc", "../browser/ui/ash/network/tether_notification_presenter_unittest.cc", "../browser/ui/ash/picker/picker_client_impl_unittest.cc", + "../browser/ui/ash/picker/picker_lacros_omnibox_search_provider_unittest.cc", "../browser/ui/ash/projector/projector_client_impl_unittest.cc", "../browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc", "../browser/ui/ash/projector/projector_utils_unittest.cc",
diff --git a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_asus.json b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_asus.json index ee289cb6..12cb2cd 100644 --- a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_asus.json +++ b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_asus.json
@@ -13,6 +13,7 @@ ], "optional_permissions": [ "os.bluetooth_peripherals_info", + "os.diagnostics.network_info_mlab", "os.management.audio" ], "chromeos_system_extension": {},
diff --git a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_google.json b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_google.json index 6f2e872..62c728ad 100644 --- a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_google.json +++ b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_google.json
@@ -16,6 +16,7 @@ "os.telemetry.serial_number", "os.telemetry.network_info", "os.bluetooth_peripherals_info", + "os.diagnostics.network_info_mlab", "os.management.audio" ], "chromeos_system_extension": {},
diff --git a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_hp.json b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_hp.json index 297e796..886a6c3 100644 --- a/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_hp.json +++ b/chrome/test/data/extensions/manifest_tests/chromeos_system_extension_hp.json
@@ -13,6 +13,7 @@ ], "optional_permissions": [ "os.bluetooth_peripherals_info", + "os.diagnostics.network_info_mlab", "os.management.audio" ], "chromeos_system_extension": {},
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn b/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn index d4bf5ce..95c5041 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn +++ b/chrome/test/data/webui/chromeos/shimless_rma/BUILD.gn
@@ -30,8 +30,8 @@ "onboarding_wait_for_manual_wp_disable_page_test.ts", "onboarding_wp_disable_complete_page_test.ts", "reboot_page_test.ts", - "reimaging_calibration_failed_page_test.js", - "reimaging_calibration_run_page_test.js", + "reimaging_calibration_failed_page_test.ts", + "reimaging_calibration_run_page_test.ts", "reimaging_calibration_setup_page_test.js", "reimaging_device_information_page_test.js", "reimaging_firmware_update_page_test.js",
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts index b5f70087..65953f5 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts +++ b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.ts
@@ -787,7 +787,7 @@ assert(service); service.setStates(states); - const result = await service.startCalibration(); + const result = await service.startCalibration(/* components= */[]); assertEquals(State.kChooseDestination, result.stateResult.state); assertEquals(RmadErrorCode.kOk, result.stateResult.error); });
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js deleted file mode 100644 index eb1cba6..0000000 --- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.js +++ /dev/null
@@ -1,322 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js'; -import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js'; -import {fakeCalibrationComponentsWithFails, fakeCalibrationComponentsWithoutFails} from 'chrome://shimless-rma/fake_data.js'; -import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js'; -import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js'; -import {ReimagingCalibrationFailedPage} from 'chrome://shimless-rma/reimaging_calibration_failed_page.js'; -import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js'; -import {CalibrationComponentStatus, CalibrationStatus, ComponentType} from 'chrome://shimless-rma/shimless_rma.mojom-webui.js'; -import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertNotReached, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; -import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; - -// TODO(crbug/1296829): Add a non-flaky test for keyboard navigation. -suite('reimagingCalibrationFailedPageTest', function() { - /** - * ShimlessRma is needed to handle the 'transition-state' event used - * when handling calibration overall progress signals. - * @type {?ShimlessRma} - */ - let shimlessRmaComponent = null; - - /** @type {?ReimagingCalibrationFailedPage} */ - let component = null; - - /** @type {?FakeShimlessRmaService} */ - let service = null; - - setup(() => { - document.body.innerHTML = trustedTypes.emptyHTML; - service = new FakeShimlessRmaService(); - setShimlessRmaServiceForTesting(service); - }); - - teardown(() => { - component.remove(); - component = null; - shimlessRmaComponent.remove(); - shimlessRmaComponent = null; - service.reset(); - }); - - /** - * @return {!Promise} - * @param {!Array<!CalibrationComponentStatus>} calibrationComponents - */ - function initializeCalibrationPage(calibrationComponents) { - assertFalse(!!component); - - shimlessRmaComponent = - /** @type {!ShimlessRma} */ (document.createElement('shimless-rma')); - assertTrue(!!shimlessRmaComponent); - document.body.appendChild(shimlessRmaComponent); - - // Initialize the fake data. - service.setGetCalibrationComponentListResult(calibrationComponents); - - component = /** @type {!ReimagingCalibrationFailedPage} */ ( - document.createElement('reimaging-calibration-failed-page')); - assertTrue(!!component); - document.body.appendChild(component); - - return flushTasks(); - } - - /** - * @return {!Promise} - */ - function clickComponentCameraToggle() { - const cameraComponent = - component.shadowRoot.querySelector('#componentCamera'); - cameraComponent.click(); - return flushTasks(); - } - - /** - * Get getComponentsList private member for testing. - * @suppress {visibility} // access private member - * @return {!Array<!CalibrationComponentStatus>} - */ - function getComponentsList() { - return component.getComponentsList(); - } - - - test('Initializes', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - const cameraComponent = - component.shadowRoot.querySelector('#componentCamera'); - const batteryComponent = - component.shadowRoot.querySelector('#componentBattery'); - const baseAccelerometerComponent = - component.shadowRoot.querySelector('#componentBaseAccelerometer'); - const lidAccelerometerComponent = - component.shadowRoot.querySelector('#componentLidAccelerometer'); - const touchpadComponent = - component.shadowRoot.querySelector('#componentTouchpad'); - assertEquals('Camera', cameraComponent.componentName); - assertFalse(cameraComponent.checked); - assertTrue(cameraComponent.failed); - assertFalse(cameraComponent.disabled); - assertEquals('Battery', batteryComponent.componentName); - assertFalse(batteryComponent.checked); - assertFalse(batteryComponent.failed); - assertTrue(batteryComponent.disabled); - assertEquals( - 'Base Accelerometer', baseAccelerometerComponent.componentName); - assertFalse(baseAccelerometerComponent.checked); - assertFalse(baseAccelerometerComponent.failed); - assertTrue(baseAccelerometerComponent.disabled); - assertEquals('Lid Accelerometer', lidAccelerometerComponent.componentName); - assertFalse(lidAccelerometerComponent.checked); - assertFalse(lidAccelerometerComponent.failed); - assertTrue(lidAccelerometerComponent.disabled); - assertEquals('Touchpad', touchpadComponent.componentName); - assertFalse(touchpadComponent.checked); - assertFalse(touchpadComponent.failed); - assertTrue(touchpadComponent.disabled); - }); - - test('ToggleComponent', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - const componentList = getComponentsList(); - assertEquals( - 3, - componentList - .filter( - component => - component.status === CalibrationStatus.kCalibrationSkip) - .length); - assertEquals( - 4, - componentList - .filter( - component => - component.status === CalibrationStatus.kCalibrationComplete) - .length); - - // Click the camera button to check it. - await clickComponentCameraToggle(); - // Camera should be the first entry in the list. - assertEquals( - CalibrationStatus.kCalibrationWaiting, getComponentsList()[0].status); - - // Click the camera button to uncheck it. - await clickComponentCameraToggle(); - // Camera should be the first entry in the list. - assertEquals( - CalibrationStatus.kCalibrationSkip, getComponentsList()[0].status); - }); - - test('ExitButtonTriggersCalibrationComplete', async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationPage(fakeCalibrationComponentsWithoutFails); - let startCalibrationCalls = 0; - service.startCalibration = (components) => { - assertEquals(5, components.length); - components.forEach( - component => assertEquals( - CalibrationStatus.kCalibrationComplete, component.status)); - startCalibrationCalls++; - return resolver.promise; - }; - await flushTasks(); - - const expectedResult = {foo: 'bar'}; - let savedResult; - component.onExitButtonClick().then((result) => savedResult = result); - // Resolve to a distinct result to confirm it was not modified. - resolver.resolve(expectedResult); - await flushTasks(); - - assertEquals(1, startCalibrationCalls); - assertDeepEquals(savedResult, expectedResult); - }); - - test('NextButtonTriggersCalibration', async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - await clickComponentCameraToggle(); - - let startCalibrationCalls = 0; - service.startCalibration = (components) => { - assertEquals(7, components.length); - components.forEach(component => { - let expectedStatus; - if (component.component === ComponentType.kCamera) { - expectedStatus = CalibrationStatus.kCalibrationWaiting; - } else if ( - component.component === ComponentType.kScreen || - component.component === ComponentType.kBaseGyroscope) { - expectedStatus = CalibrationStatus.kCalibrationSkip; - } else { - expectedStatus = CalibrationStatus.kCalibrationComplete; - } - assertEquals(expectedStatus, component.status); - }); - startCalibrationCalls++; - return resolver.promise; - }; - - const expectedResult = {foo: 'bar'}; - let savedResult; - component.onNextButtonClick().then((result) => savedResult = result); - // Resolve to a distinct result to confirm it was not modified. - resolver.resolve(expectedResult); - await flushTasks(); - - assertEquals(1, startCalibrationCalls); - assertDeepEquals(savedResult, expectedResult); - }); - - test('ComponentChipAllButtonsDisabled', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - // Base Gyroscope is a failed component so it starts off not disabled. - const baseGyroscopeComponent = - component.shadowRoot.querySelector('#componentBaseGyroscope'); - assertFalse(baseGyroscopeComponent.disabled); - component.allButtonsDisabled = true; - assertTrue(baseGyroscopeComponent.disabled); - }); - - test('SkipCalibrationWithFailedComponents', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - let wasPromiseRejected = false; - component.onExitButtonClick() - .then(() => assertNotReached('Do not proceed with failed components')) - .catch(() => { - wasPromiseRejected = true; - }); - - await flushTasks(); - assertTrue(wasPromiseRejected); - }); - - test('FailedComponentsDialogSkipButton', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - const resolver = new PromiseResolver(); - let startCalibrationCalls = 0; - service.startCalibration = (components) => { - startCalibrationCalls++; - return resolver.promise; - }; - - component.onExitButtonClick().catch(() => {}); - - await flushTasks(); - assertEquals(0, startCalibrationCalls); - assertTrue( - component.shadowRoot.querySelector('#failedComponentsDialog').open); - component.shadowRoot.querySelector('#dialogSkipButton').click(); - - await flushTasks(); - assertEquals(1, startCalibrationCalls); - assertFalse( - component.shadowRoot.querySelector('#failedComponentsDialog').open); - }); - - test('FailedComponentsDialogRetryButton', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - const resolver = new PromiseResolver(); - let startCalibrationCalls = 0; - service.startCalibration = (components) => { - startCalibrationCalls++; - return resolver.promise; - }; - - component.onExitButtonClick().catch(() => {}); - - await flushTasks(); - assertEquals(0, startCalibrationCalls); - assertTrue( - component.shadowRoot.querySelector('#failedComponentsDialog').open); - component.shadowRoot.querySelector('#dialogRetryButton').click(); - - await flushTasks(); - assertEquals(0, startCalibrationCalls); - assertFalse( - component.shadowRoot.querySelector('#failedComponentsDialog').open); - }); - - test('NextButtonIsOnlyEnabledIfAtLeastOneComponentIsSelected', async () => { - await initializeCalibrationPage(fakeCalibrationComponentsWithFails); - - let disableNextButtonEventFired = false; - let disableNextButton = false; - - const componentBaseGyroscopeButton = - component.shadowRoot.querySelector('#componentBaseGyroscope') - .shadowRoot.querySelector('#componentButton'); - - const disableHandler = (event) => { - disableNextButtonEventFired = true; - disableNextButton = event.detail; - }; - - component.addEventListener('disable-next-button', disableHandler); - - // If a component is selected, enable the next button. - componentBaseGyroscopeButton.click(); - await flushTasks(); - assertTrue(disableNextButtonEventFired); - assertFalse(disableNextButton); - - // If no components are selected, disable the next button. - disableNextButtonEventFired = false; - componentBaseGyroscopeButton.click(); - await flushTasks(); - assertTrue(disableNextButtonEventFired); - assertTrue(disableNextButton); - - component.removeEventListener('disable-next-button', disableHandler); - }); -});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.ts new file mode 100644 index 0000000..6d7c55b --- /dev/null +++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_failed_page_test.ts
@@ -0,0 +1,295 @@ +// Copyright 2021 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://shimless-rma/shimless_rma.js'; + +import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js'; +import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js'; +import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js'; +import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js'; +import {assert} from 'chrome://resources/js/assert.js'; +import {CalibrationComponentChipElement} from 'chrome://shimless-rma/calibration_component_chip.js'; +import {DISABLE_NEXT_BUTTON} from 'chrome://shimless-rma/events.js'; +import {fakeCalibrationComponentsWithFails, fakeCalibrationComponentsWithoutFails} from 'chrome://shimless-rma/fake_data.js'; +import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js'; +import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js'; +import {ReimagingCalibrationFailedPage} from 'chrome://shimless-rma/reimaging_calibration_failed_page.js'; +import {CalibrationComponentStatus, CalibrationStatus, ComponentType, StateResult} from 'chrome://shimless-rma/shimless_rma.mojom-webui.js'; +import {assertEquals, assertFalse, assertNotReached, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; +import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; +import {eventToPromise} from 'chrome://webui-test/test_util.js'; + +// TODO(crbug/1296829): Add a non-flaky test for keyboard navigation. +suite('reimagingCalibrationFailedPageTest', function() { + let component: ReimagingCalibrationFailedPage|null = null; + + const service: FakeShimlessRmaService = new FakeShimlessRmaService(); + + const cameraSelector = '#componentCamera'; + const baseGyroscopeSelector = '#componentBaseGyroscope'; + const failedComponentsDialogSelector = '#failedComponentsDialog'; + + setup(() => { + document.body.innerHTML = window.trustedTypes!.emptyHTML; + setShimlessRmaServiceForTesting(service); + }); + + teardown(() => { + component?.remove(); + component = null; + }); + + function initializeCalibrationPage( + calibrationComponents: CalibrationComponentStatus[]): Promise<void> { + // Initialize the fake data. + assert(service); + service.setGetCalibrationComponentListResult(calibrationComponents); + + assert(!component); + component = document.createElement(ReimagingCalibrationFailedPage.is); + assert(component); + document.body.appendChild(component); + + return flushTasks(); + } + + function clickComponentCameraToggle(): Promise<void> { + assert(component); + strictQuery( + cameraSelector, component.shadowRoot, CalibrationComponentChipElement) + .click(); + return flushTasks(); + } + + function getComponentsList(): CalibrationComponentStatus[] { + assert(component); + return component.getComponentsListForTesting(); + } + + // Verify the page initializes with component chips in the expected state. + test('Initializes', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + assert(component); + const cameraComponent = strictQuery( + cameraSelector, component.shadowRoot, CalibrationComponentChipElement); + assertEquals('Camera', cameraComponent.componentName); + assertFalse(cameraComponent.checked); + assertTrue(cameraComponent.failed); + assertFalse(cameraComponent.disabled); + + const batteryComponent = strictQuery( + '#componentBattery', component.shadowRoot, + CalibrationComponentChipElement); + assertEquals('Battery', batteryComponent.componentName); + assertFalse(batteryComponent.checked); + assertFalse(batteryComponent.failed); + assertTrue(batteryComponent.disabled); + + const baseAccelerometerComponent = strictQuery( + '#componentBaseAccelerometer', component.shadowRoot, + CalibrationComponentChipElement); + assertEquals( + 'Base Accelerometer', baseAccelerometerComponent.componentName); + assertFalse(baseAccelerometerComponent.checked); + assertFalse(baseAccelerometerComponent.failed); + assertTrue(baseAccelerometerComponent.disabled); + + const lidAccelerometerComponent = strictQuery( + '#componentLidAccelerometer', component.shadowRoot, + CalibrationComponentChipElement); + assertEquals('Lid Accelerometer', lidAccelerometerComponent.componentName); + assertFalse(lidAccelerometerComponent.checked); + assertFalse(lidAccelerometerComponent.failed); + assertTrue(lidAccelerometerComponent.disabled); + + const touchpadComponent = strictQuery( + '#componentTouchpad', component.shadowRoot, + CalibrationComponentChipElement); + assertEquals('Touchpad', touchpadComponent.componentName); + assertFalse(touchpadComponent.checked); + assertFalse(touchpadComponent.failed); + assertTrue(touchpadComponent.disabled); + }); + + // Verify clicking the Camera chip toggles it between states. + test('ToggleComponent', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + // Click the camera button to check it. + await clickComponentCameraToggle(); + let cameraComponent = getComponentsList().find( + (calibrationComponent: CalibrationComponentStatus) => + calibrationComponent.component === ComponentType.kCamera); + assert(cameraComponent); + assertEquals(CalibrationStatus.kCalibrationWaiting, cameraComponent.status); + + // Click the camera button again to uncheck it. + await clickComponentCameraToggle(); + cameraComponent = getComponentsList().find( + (calibrationComponent: CalibrationComponentStatus) => + calibrationComponent.component === ComponentType.kCamera); + assert(cameraComponent); + assertEquals(CalibrationStatus.kCalibrationSkip, cameraComponent.status); + }); + + // Verify clicking the exit button triggers the calibration complete signal. + test('ExitButtonTriggersCalibrationComplete', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithoutFails); + + const resolver = new PromiseResolver<{stateResult: StateResult}>(); + let startCalibrationCalls = 0; + assert(service); + service.startCalibration = (components: CalibrationComponentStatus[]) => { + const expectedComponents = 5; + assertEquals(expectedComponents, components.length); + components.forEach( + (component: CalibrationComponentStatus) => assertEquals( + CalibrationStatus.kCalibrationComplete, component.status)); + ++startCalibrationCalls; + return resolver.promise; + }; + + assert(component); + component.onExitButtonClick(); + + const expectedCalls = 1; + assertEquals(expectedCalls, startCalibrationCalls); + }); + + // Verify clicking the next button triggers a new calibration. + test('NextButtonTriggersCalibration', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + await clickComponentCameraToggle(); + + const resolver = new PromiseResolver<{stateResult: StateResult}>(); + let startCalibrationCalls = 0; + assert(service); + service.startCalibration = (components: CalibrationComponentStatus[]) => { + const expectedCompnents = 7; + assertEquals(expectedCompnents, components.length); + + components.forEach((component: CalibrationComponentStatus) => { + let expectedStatus; + if (component.component === ComponentType.kCamera) { + expectedStatus = CalibrationStatus.kCalibrationWaiting; + } else if ( + component.component === ComponentType.kScreen || + component.component === ComponentType.kBaseGyroscope) { + expectedStatus = CalibrationStatus.kCalibrationSkip; + } else { + expectedStatus = CalibrationStatus.kCalibrationComplete; + } + assertEquals(expectedStatus, component.status); + }); + ++startCalibrationCalls; + return resolver.promise; + }; + + assert(component); + component.onNextButtonClick(); + + const expectedCalls = 1; + assertEquals(expectedCalls, startCalibrationCalls); + }); + + // Verify when `allButtonsDisabled` is set all component chips are disabled. + test('ComponentChipAllButtonsDisabled', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + // Base Gyroscope is a failed component so it starts off not disabled. + assert(component); + const baseGyroscopeComponent = strictQuery( + baseGyroscopeSelector, component.shadowRoot, + CalibrationComponentChipElement); + assertFalse(baseGyroscopeComponent.disabled); + component.allButtonsDisabled = true; + assertTrue(baseGyroscopeComponent.disabled); + }); + + // Verify attempting to skip a calibration with failed components is rejected + // and opens a dialog. + test('SkipCalibrationWithFailedComponents', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + // Click the skip/exit button and expect the request to be rejected and open + // the confirmation dialog. + assert(component); + let wasPromiseRejected = false; + try { + await component.onExitButtonClick(); + assertNotReached('Do not proceed with failed components'); + } catch (error: unknown) { + wasPromiseRejected = true; + } + assertTrue(wasPromiseRejected); + const failedComponentsDialog = strictQuery( + failedComponentsDialogSelector, component.shadowRoot, CrDialogElement); + assertTrue(failedComponentsDialog.open); + + // Click the skip button and expect the dialog to close. + strictQuery('#dialogSkipButton', component.shadowRoot, CrButtonElement) + .click(); + await flushTasks(); + assertFalse(failedComponentsDialog.open); + }); + + // Verify clicking the dialog retry button restarts calibration. + test('FailedComponentsDialogRetryButton', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + const resolver = new PromiseResolver<{stateResult: StateResult}>(); + let startCalibrationCalls = 0; + assert(service); + service.startCalibration = (/* components= */[]) => { + ++startCalibrationCalls; + return resolver.promise; + }; + + // Click the skip/exit button and expect the request to be rejected and open + // the confirmation dialog. + assert(component); + try { + await component.onExitButtonClick(); + assertNotReached('Do not proceed with failed components'); + } catch (error: unknown) { + } + + // Click the retry button and expect the dialog to close without starting a + // calibration. + strictQuery('#dialogRetryButton', component.shadowRoot, CrButtonElement) + .click(); + assertEquals(0, startCalibrationCalls); + assertFalse(strictQuery( + failedComponentsDialogSelector, component.shadowRoot, + CrDialogElement) + .open); + }); + + // Verify that the next button is only enabled if at least one component is + // selected. + test('NextButtonOnlyEnabledIfComponentIsSelected', async () => { + await initializeCalibrationPage(fakeCalibrationComponentsWithFails); + + assert(component); + const disableNextButtonEvent = + eventToPromise(DISABLE_NEXT_BUTTON, component); + + // Select a component and expect the next button to be enabled. + assert(component); + const componentBaseGyroscopeButton = strictQuery( + baseGyroscopeSelector, component.shadowRoot, + CalibrationComponentChipElement); + componentBaseGyroscopeButton.click(); + const enableNextButtonResponse = await disableNextButtonEvent; + assertFalse(enableNextButtonResponse.detail); + + // Select the component again to uncheck it so no components are selected + // and expect the next button to be disabled. + componentBaseGyroscopeButton.click(); + const disableNextButtonResponse = await disableNextButtonEvent; + assertFalse(disableNextButtonResponse.detail); + }); +});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.js deleted file mode 100644 index deab53a..0000000 --- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.js +++ /dev/null
@@ -1,176 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js'; -import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js'; -import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js'; -import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js'; -import {ReimagingCalibrationRunPage} from 'chrome://shimless-rma/reimaging_calibration_run_page.js'; -import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js'; -import {CalibrationOverallStatus, CalibrationStatus, ComponentType} from 'chrome://shimless-rma/shimless_rma.mojom-webui.js'; -import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; -import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; - -suite('reimagingCalibrationRunPageTest', function() { - /** - * ShimlessRma is needed to handle the 'transition-state' event used - * when handling calibration overall progress signals. - * @type {?ShimlessRma} - */ - let shimlessRmaComponent = null; - - /** @type {?ReimagingCalibrationRunPage} */ - let component = null; - - /** @type {?FakeShimlessRmaService} */ - let service = null; - - setup(() => { - document.body.innerHTML = trustedTypes.emptyHTML; - service = new FakeShimlessRmaService(); - setShimlessRmaServiceForTesting(service); - }); - - teardown(() => { - component.remove(); - component = null; - shimlessRmaComponent.remove(); - shimlessRmaComponent = null; - service.reset(); - }); - - /** - * @return {!Promise} - */ - function initializeCalibrationRunPage() { - assertFalse(!!component); - - shimlessRmaComponent = - /** @type {!ShimlessRma} */ (document.createElement('shimless-rma')); - assertTrue(!!shimlessRmaComponent); - document.body.appendChild(shimlessRmaComponent); - - component = /** @type {!ReimagingCalibrationRunPage} */ ( - document.createElement('reimaging-calibration-run-page')); - assertTrue(!!component); - document.body.appendChild(component); - - return flushTasks(); - } - - test('NextButtonBeforeCalibrationCompleteFails', async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationRunPage(); - let calibrationCompleteCalls = 0; - service.calibrationComplete = () => { - calibrationCompleteCalls++; - return resolver.promise; - }; - - let savedResult; - let savedError; - component.onNextButtonClick() - .then((result) => savedResult = result) - .catch((error) => savedError = error); - await flushTasks(); - - assertEquals(0, calibrationCompleteCalls); - assertTrue(savedError instanceof Error); - assertEquals(savedError.message, 'Calibration is not complete.'); - assertEquals(savedResult, undefined); - }); - - test('NextButtonAfterCalibrationCompleteTriggersContinue', async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationRunPage(); - - const calibrationTitle = component.shadowRoot.querySelector('h1'); - const progressSpinner = - component.shadowRoot.querySelector('paper-spinner-lite'); - const completeIllustration = component.shadowRoot.querySelector('img'); - - assertEquals( - loadTimeData.getString('runCalibrationTitleText'), - calibrationTitle.textContent.trim()); - assertFalse(progressSpinner.hidden); - assertTrue(completeIllustration.hidden); - - let calibrationCompleteCalls = 0; - service.calibrationComplete = () => { - calibrationCompleteCalls++; - return resolver.promise; - }; - service.triggerCalibrationOverallObserver( - CalibrationOverallStatus.kCalibrationOverallComplete, 0); - await flushTasks(); - - const expectedResult = {foo: 'bar'}; - let savedResult; - component.onNextButtonClick().then((result) => savedResult = result); - // Resolve to a distinct result to confirm it was not modified. - resolver.resolve(expectedResult); - await flushTasks(); - - assertEquals(1, calibrationCompleteCalls); - assertDeepEquals(savedResult, expectedResult); - assertEquals( - loadTimeData.getString('runCalibrationCompleteTitleText'), - calibrationTitle.textContent.trim()); - assertTrue(progressSpinner.hidden); - assertFalse(completeIllustration.hidden); - }); - - test( - 'CalibrationOverallProgressRoundCompleteCallsContinueCalibration', - async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationRunPage(); - let continueCalibrationCalls = 0; - service.continueCalibration = () => { - continueCalibrationCalls++; - return resolver.promise; - }; - service.triggerCalibrationOverallObserver( - CalibrationOverallStatus.kCalibrationOverallCurrentRoundComplete, - 0); - await flushTasks(); - - assertEquals(1, continueCalibrationCalls); - }); - - test( - 'CalibrationOverallProgressRoundFailedCallsContinueCalibration', - async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationRunPage(); - let continueCalibrationCalls = 0; - service.continueCalibration = () => { - continueCalibrationCalls++; - return resolver.promise; - }; - service.triggerCalibrationOverallObserver( - CalibrationOverallStatus.kCalibrationOverallCurrentRoundFailed, 0); - await flushTasks(); - - assertEquals(1, continueCalibrationCalls); - }); - - test( - 'CalibrationOverallProgressIniitalizationFailedCallsContinueCalibration', - async () => { - const resolver = new PromiseResolver(); - await initializeCalibrationRunPage(); - let continueCalibrationCalls = 0; - service.continueCalibration = () => { - continueCalibrationCalls++; - return resolver.promise; - }; - service.triggerCalibrationOverallObserver( - CalibrationOverallStatus.kCalibrationOverallInitializationFailed, - 0); - await flushTasks(); - - assertEquals(1, continueCalibrationCalls); - }); -});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.ts b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.ts new file mode 100644 index 0000000..eabbd8b --- /dev/null +++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_calibration_run_page_test.ts
@@ -0,0 +1,164 @@ +// Copyright 2021 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'chrome://shimless-rma/shimless_rma.js'; + +import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js'; +import {PromiseResolver} from 'chrome://resources/ash/common/promise_resolver.js'; +import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js'; +import {assert} from 'chrome://resources/js/assert.js'; +import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js'; +import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js'; +import {ReimagingCalibrationRunPage} from 'chrome://shimless-rma/reimaging_calibration_run_page.js'; +import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js'; +import {CalibrationOverallStatus, StateResult} from 'chrome://shimless-rma/shimless_rma.mojom-webui.js'; +import {assertEquals, assertFalse, assertNotReached, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js'; +import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; + +suite('reimagingCalibrationRunPageTest', function() { + // ShimlessRma is needed to handle the 'transition-state' event used when + // handling calibration overall progress signals. + let shimlessRmaComponent: ShimlessRma|null = null; + + let component: ReimagingCalibrationRunPage|null = null; + + const service: FakeShimlessRmaService = new FakeShimlessRmaService(); + + setup(() => { + document.body.innerHTML = window.trustedTypes!.emptyHTML; + setShimlessRmaServiceForTesting(service); + }); + + teardown(() => { + component?.remove(); + component = null; + shimlessRmaComponent?.remove(); + shimlessRmaComponent = null; + }); + + function initializeCalibrationRunPage(): Promise<void> { + assert(!shimlessRmaComponent); + shimlessRmaComponent = document.createElement(ShimlessRma.is); + assert(shimlessRmaComponent); + document.body.appendChild(shimlessRmaComponent); + + assert(!component); + component = document.createElement(ReimagingCalibrationRunPage.is); + assert(component); + document.body.appendChild(component); + + return flushTasks(); + } + + // Verify clicking the next button before a calibration completes is rejected + // but succeeds after calibration. + test('NextButtonBeforeAndAfterCalibration', async () => { + await initializeCalibrationRunPage(); + + assert(service); + const resolver = new PromiseResolver<{stateResult: StateResult}>(); + let calibrationCompleteCalls = 0; + service.calibrationComplete = () => { + ++calibrationCompleteCalls; + return resolver.promise; + }; + + // Click the next button and expect a rejection. + assert(component); + let wasPromiseRejected = false; + try { + component.onNextButtonClick(); + assertNotReached('Do not proceed while calibration running.'); + } catch (error: unknown) { + wasPromiseRejected = true; + } + assertTrue(wasPromiseRejected); + let expectedCalls = 0; + assertEquals(expectedCalls, calibrationCompleteCalls); + + // Trigger the calibration to complete. + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallComplete, /* delayMs= */ 0); + await flushTasks(); + + // Click the next button again expecting it to succeed. + component.onNextButtonClick(); + expectedCalls = 1; + assertEquals(expectedCalls, calibrationCompleteCalls); + }); + + // Verify the calibration complete status updates the UI elements. + test('CalibrationCompleteUpdatesUI', async () => { + await initializeCalibrationRunPage(); + + assert(component); + const calibrationTitle = + strictQuery('h1', component.shadowRoot, HTMLElement); + const progressSpinner = + strictQuery('paper-spinner-lite', component.shadowRoot, HTMLElement); + const completeIllustration = + strictQuery('img', component.shadowRoot, HTMLElement); + + assertEquals( + loadTimeData.getString('runCalibrationTitleText'), + calibrationTitle.textContent!.trim()); + assertFalse(progressSpinner.hidden); + assertTrue(completeIllustration.hidden); + + // Trigger the calibration to complete. + assert(service); + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallComplete, /* delayMs= */ 0); + await flushTasks(); + + // The UI should update to be in calibration complete mode. + assertEquals( + loadTimeData.getString('runCalibrationCompleteTitleText'), + calibrationTitle.textContent!.trim()); + assertTrue(progressSpinner.hidden); + assertFalse(completeIllustration.hidden); + }); + + // Verify continue calibration is invoked for the correct calibration + // statuses. + test('CalibrationRoundCompleteContinueCalibration', async () => { + await initializeCalibrationRunPage(); + + assert(service); + const resolver = new PromiseResolver<{stateResult: StateResult}>(); + let continueCalibrationCalls = 0; + service.continueCalibration = () => { + ++continueCalibrationCalls; + return resolver.promise; + }; + + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallCurrentRoundComplete, + /* delayMs= */ 0); + await flushTasks(); + let expectedCalls = 1; + assertEquals(expectedCalls, continueCalibrationCalls); + + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallCurrentRoundFailed, + /* delayMs= */ 0); + await flushTasks(); + expectedCalls = 2; + assertEquals(expectedCalls, continueCalibrationCalls); + + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallInitializationFailed, + /* delayMs= */ 0); + await flushTasks(); + expectedCalls = 3; + assertEquals(expectedCalls, continueCalibrationCalls); + + // `kCalibrationOverallComplete` is not expected to continue + // calibration. + service.triggerCalibrationOverallObserver( + CalibrationOverallStatus.kCalibrationOverallComplete, /* delayMs= */ 0); + await flushTasks(); + assertEquals(expectedCalls, continueCalibrationCalls); + }); +});
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc index 553cc84..4ed1c7cf 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc +++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.cc
@@ -115,6 +115,16 @@ RunTest("chromeos/shimless_rma/reboot_page_test.js", "mocha.run()"); } +IN_PROC_BROWSER_TEST_F(ShimlessRmaBrowserTest, CalibrationFailedPage) { + RunTest("chromeos/shimless_rma/reimaging_calibration_failed_page_test.js", + "mocha.run()"); +} + +IN_PROC_BROWSER_TEST_F(ShimlessRmaBrowserTest, CalibrationRunPage) { + RunTest("chromeos/shimless_rma/reimaging_calibration_run_page_test.js", + "mocha.run()"); +} + } // namespace } // namespace ash
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js index fe336e2..8c1004a 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js +++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_browsertest.js
@@ -34,11 +34,6 @@ const tests = [ [ - 'ReimagingCalibrationFailedPageTest', - 'reimaging_calibration_failed_page_test.js' - ], - ['ReimagingCalibrationRunPageTest', 'reimaging_calibration_run_page_test.js'], - [ 'ReimagingCalibrationSetupPageTest', 'reimaging_calibration_setup_page_test.js' ],
diff --git a/chrome/test/data/webui/lens/overlay/region_selection_test.ts b/chrome/test/data/webui/lens/overlay/region_selection_test.ts index c9c89fb3..27d07ac3 100644 --- a/chrome/test/data/webui/lens/overlay/region_selection_test.ts +++ b/chrome/test/data/webui/lens/overlay/region_selection_test.ts
@@ -30,20 +30,20 @@ BrowserProxyImpl.setInstance(testBrowserProxy); selectionOverlayElement = document.createElement('lens-selection-overlay'); - // Position absolutely so we can handle logic of drag ending off this - // element. - selectionOverlayElement.style.position = 'absolute'; - selectionOverlayElement.style.height = 'calc(100% - 100px)'; - selectionOverlayElement.style.width = 'calc(100% - 100px)'; - selectionOverlayElement.style.top = '50px'; - selectionOverlayElement.style.left = '50px'; document.body.appendChild(selectionOverlayElement); + + // Set image to be less than fullscreen so we can handle logic of drag + // ending off this element. + selectionOverlayElement.$.backgroundImage.style.height = + 'calc(100vh - 100px)'; + selectionOverlayElement.$.backgroundImage.style.width = + 'calc(100vw - 100px)'; return waitAfterNextRender(selectionOverlayElement); }); // Normalizes the given values to the size of selection overlay. function normalizedBox(box: RectF): RectF { - const boundingRect = selectionOverlayElement.getBoundingClientRect(); + const boundingRect = getImageBoundingRect(); return { x: box.x / boundingRect.width, y: box.y / boundingRect.height, @@ -52,6 +52,10 @@ }; } + function getImageBoundingRect() { + return selectionOverlayElement.$.backgroundImage.getBoundingClientRect(); + } + // Does a drag and verifies that expectedRect is sent via mojo. async function assertDragGestureSendsRequest( fromPoint: Point, toPoint: Point, expectedRect: CenterRotatedBox) { @@ -68,14 +72,14 @@ `verify that completing a drag within the overlay bounds issues correct lens request via mojo`, async () => { - const overlayRect = selectionOverlayElement.getBoundingClientRect(); + const imageBounds = getImageBoundingRect(); const startPointInsideOverlay = { - x: overlayRect.left + 10, - y: overlayRect.top + 10, + x: imageBounds.left + 10, + y: imageBounds.top + 10, }; const endPointInsideOverlay = { - x: overlayRect.left + 100, - y: overlayRect.top + 100, + x: imageBounds.left + 100, + y: imageBounds.top + 100, }; const expectedRect: CenterRotatedBox = { @@ -90,14 +94,14 @@ test( 'verify that completing a drag above the selection overlay rounds y to 0', async () => { - const overlayRect = selectionOverlayElement.getBoundingClientRect(); + const imageBounds = getImageBoundingRect(); const startPointInsideOverlay = { - x: overlayRect.left + 10, - y: overlayRect.top + 10, + x: imageBounds.left + 10, + y: imageBounds.top + 10, }; const endPointAboveOverlay = { - x: overlayRect.left + 100, - y: overlayRect.top - 30, + x: imageBounds.left + 100, + y: imageBounds.top - 30, }; const expectedRect: CenterRotatedBox = { @@ -113,19 +117,19 @@ `verify that completing a drag below the selection overlay rounds y to overlay height`, async () => { - const overlayRect = selectionOverlayElement.getBoundingClientRect(); + const imageBounds = getImageBoundingRect(); const startPointInsideOverlay = { - x: overlayRect.left + 10, - y: overlayRect.bottom - 20, + x: imageBounds.left + 10, + y: imageBounds.bottom - 20, }; const endPointBelowOverlay = { - x: overlayRect.left + 100, - y: overlayRect.bottom + 20, + x: imageBounds.left + 100, + y: imageBounds.bottom + 20, }; const expectedRect: CenterRotatedBox = { box: normalizedBox( - {x: 55, y: overlayRect.height - 10, width: 90, height: 20}), + {x: 55, y: imageBounds.height - 10, width: 90, height: 20}), rotation: 0, coordinateType: CenterRotatedBox_CoordinateType.kNormalized, }; @@ -137,14 +141,14 @@ `verify that completing a drag to the left of the selection overlay rounds x to 0`, async () => { - const overlayRect = selectionOverlayElement.getBoundingClientRect(); + const imageBounds = getImageBoundingRect(); const startPointInsideOverlay = { - x: overlayRect.left + 20, - y: overlayRect.top + 10, + x: imageBounds.left + 20, + y: imageBounds.top + 10, }; const endPointLeftOfOverlay = { - x: overlayRect.left - 10, - y: overlayRect.top + 100, + x: imageBounds.left - 10, + y: imageBounds.top + 100, }; const expectedRect: CenterRotatedBox = { @@ -160,19 +164,19 @@ `verify that completing a drag to the right of the selection overlay rounds x to overlay width`, async () => { - const overlayRect = selectionOverlayElement.getBoundingClientRect(); + const imageBounds = getImageBoundingRect(); const startPointInsideOverlay = { - x: overlayRect.right - 20, - y: overlayRect.top + 10, + x: imageBounds.right - 20, + y: imageBounds.top + 10, }; const endPointRightOfOverlay = { - x: overlayRect.right + 10, - y: overlayRect.top + 100, + x: imageBounds.right + 10, + y: imageBounds.top + 100, }; const expectedRect: CenterRotatedBox = { box: normalizedBox( - {x: overlayRect.width - 10, y: 55, width: 20, height: 90}), + {x: imageBounds.width - 10, y: 55, width: 20, height: 90}), rotation: 0, coordinateType: CenterRotatedBox_CoordinateType.kNormalized, };
diff --git a/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts b/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts index badadd5c..c63da15 100644 --- a/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts +++ b/chrome/test/data/webui/lens/overlay/selection_overlay_test.ts
@@ -11,7 +11,7 @@ import type {LensPageRemote} from 'chrome-untrusted://lens/lens.mojom-webui.js'; import type {OverlayObject} from 'chrome-untrusted://lens/overlay_object.mojom-webui.js'; import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js'; -import {assertDeepEquals, assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js'; +import {assertDeepEquals, assertEquals, assertNotEquals, assertNull} from 'chrome-untrusted://webui-test/chai_assert.js'; import {flushTasks, waitAfterNextRender} from 'chrome-untrusted://webui-test/polymer_test_util.js'; import {assertBoxesWithinThreshold, createObject} from '../utils/object_utils.js'; @@ -44,7 +44,7 @@ // viewport. selectionOverlayElement.$.selectionOverlay.style.width = '100%'; selectionOverlayElement.$.selectionOverlay.style.height = '100%'; - return flushTasks(); + return waitAfterNextRender(selectionOverlayElement); }); // Normalizes the given values to the size of selection overlay. @@ -234,4 +234,28 @@ `${expectedLeft}%`, postSelectionStyles.getPropertyValue('--selection-left')); }); + + test('verify that resizing renders image with padding', async () => { + selectionOverlayElement.style.display = 'block'; + selectionOverlayElement.style.width = '50px'; + selectionOverlayElement.style.height = '50px'; + await waitAfterNextRender(selectionOverlayElement); + assertNotEquals(null, selectionOverlayElement.getAttribute('is-resized')); + + // Verify resizing back no longer renders with padding + selectionOverlayElement.style.width = '100%'; + selectionOverlayElement.style.height = '100%'; + await waitAfterNextRender(selectionOverlayElement); + assertNull(selectionOverlayElement.getAttribute('is-resized')); + }); + + test( + 'verify that resizing within threshold does not rerender image', + async () => { + selectionOverlayElement.style.display = 'block'; + selectionOverlayElement.style.width = + `${selectionOverlayElement.getBoundingClientRect().width - 4}px`; + await waitAfterNextRender(selectionOverlayElement); + assertNull(selectionOverlayElement.getAttribute('is-resized')); + }); });
diff --git a/chrome/test/data/webui/settings/security_page_test.ts b/chrome/test/data/webui/settings/security_page_test.ts index e5179721..ea1d455 100644 --- a/chrome/test/data/webui/settings/security_page_test.ts +++ b/chrome/test/data/webui/settings/security_page_test.ts
@@ -9,7 +9,7 @@ import {HttpsFirstModeSetting, SafeBrowsingSetting} from 'chrome://settings/lazy_load.js'; import type {SettingsPrefsElement, SettingsToggleButtonElement} from 'chrome://settings/settings.js'; import {HatsBrowserProxyImpl, CrSettingsPrefs, MetricsBrowserProxyImpl, OpenWindowProxyImpl, PrivacyElementInteractions, PrivacyPageBrowserProxyImpl, Router, routes, SafeBrowsingInteractions, SecureDnsMode, SecurityPageInteraction} from 'chrome://settings/settings.js'; -import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {assertEquals, assertFalse, assertTrue, assertNotEquals} from 'chrome://webui-test/chai_assert.js'; import {isChildVisible, eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js'; import {flushTasks} from 'chrome://webui-test/polymer_test_util.js'; @@ -994,6 +994,36 @@ assertTrue(isChildVisible(page, '#learnMoreLabelContainer')); }); + test('LearnMoreLinkClickableWhenControlledByPolicy', async () => { + page.$.safeBrowsingEnhanced.$.expandButton.click(); + + // Set the page to be enterprise policy enforced. + page.set( + 'prefs.generated.safe_browsing.enforcement', + chrome.settingsPrivate.Enforcement.ENFORCED); + flush(); + + const learnMoreLink = page.shadowRoot!.querySelector<HTMLElement>( + '#enhancedProtectionLearnMoreLink'); + + // Confirm that the learnMoreLink element exists. + assertNotEquals(learnMoreLink, null); + + // Confirm that the pointer-events value is auto when enterprise policy is + // enforced. + assertEquals( + 'auto', + (learnMoreLink!.computedStyleMap()!.get('pointer-events') as + CSSKeywordValue) + .value); + + // Confirm that the correct link was clicked. + learnMoreLink!.click(); + const url = await openWindowProxy.whenCalled('openUrl'); + assertEquals( + url, loadTimeData.getString('enhancedProtectionHelpCenterURL')); + }); + // <if expr="_google_chrome"> test('StandardProtectionDropdownWithProxyString', async () => { loadTimeData.overrideValues({
diff --git a/chrome/test/data/webui/welcome/BUILD.gn b/chrome/test/data/webui/welcome/BUILD.gn index 8d4055a..037b2c7 100644 --- a/chrome/test/data/webui/welcome/BUILD.gn +++ b/chrome/test/data/webui/welcome/BUILD.gn
@@ -36,7 +36,7 @@ ] ts_deps = [ "//chrome/browser/resources/welcome:build_ts", - "//third_party/polymer/v3_0:library", + "//third_party/lit/v3_0:build_ts", "//ui/webui/resources/cr_elements:build_ts", "//ui/webui/resources/js:build_ts", ]
diff --git a/chrome/test/data/webui/welcome/navigation_mixin_test.ts b/chrome/test/data/webui/welcome/navigation_mixin_test.ts index 07fb21b9..1d62675 100644 --- a/chrome/test/data/webui/welcome/navigation_mixin_test.ts +++ b/chrome/test/data/webui/welcome/navigation_mixin_test.ts
@@ -3,26 +3,22 @@ // found in the LICENSE file. import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; import {eventToPromise} from 'chrome://webui-test/test_util.js'; -import {NavigationMixin} from 'chrome://welcome/navigation_mixin.js'; +import {NavigationMixinLit} from 'chrome://welcome/navigation_mixin_lit.js'; import {navigateTo, navigateToNextStep, Routes} from 'chrome://welcome/router.js'; -suite('NavigationBehaviorTest', function() { - class TestElement extends NavigationMixin - (PolymerElement) { +suite('NavigationMixinTest', function() { + const TestElementBase = NavigationMixinLit(CrLitElement); + class TestElement extends TestElementBase { static get is() { return 'test-element'; } - static get template() { - return html``; - } - - static get properties() { + static override get properties() { return { - subtitle: String, + subtitle: {type: String}, }; } @@ -31,8 +27,7 @@ changeCalled: boolean = false; exitCalled: boolean = false; - override ready() { - super.ready(); + override firstUpdated() { this.reset(); }
diff --git a/chrome/updater/device_management/dm_storage.h b/chrome/updater/device_management/dm_storage.h index daa9246..10898b5 100644 --- a/chrome/updater/device_management/dm_storage.h +++ b/chrome/updater/device_management/dm_storage.h
@@ -64,6 +64,7 @@ // 3) DM policies. class DMStorage : public base::RefCountedThreadSafe<DMStorage> { public: + static constexpr size_t kMaxDmTokenLength = 4096; #if BUILDFLAG(IS_WIN) explicit DMStorage(const base::FilePath& policy_cache_root); #else
diff --git a/chrome/updater/device_management/dm_storage_unittest.cc b/chrome/updater/device_management/dm_storage_unittest.cc index d8c0b48..c5c07f35 100644 --- a/chrome/updater/device_management/dm_storage_unittest.cc +++ b/chrome/updater/device_management/dm_storage_unittest.cc
@@ -133,8 +133,10 @@ legacy_key.Create(HKEY_LOCAL_MACHINE, kRegKeyCompanyLegacyCloudManagement, Wow6432(KEY_WRITE)), ERROR_SUCCESS); - EXPECT_EQ(legacy_key.WriteValue(kRegValueCloudManagementEnrollmentToken, - L"legacy_test_enrollment_token"), + constexpr char kLegacyEnrollmentToken[] = "legacy_test_enrollment_token"; + EXPECT_EQ(legacy_key.WriteValue( + kRegValueCloudManagementEnrollmentToken, kLegacyEnrollmentToken, + sizeof(kLegacyEnrollmentToken) - 1, REG_BINARY), ERROR_SUCCESS); EXPECT_EQ(storage->GetEnrollmentToken(), "legacy_test_enrollment_token"); @@ -142,7 +144,9 @@ EXPECT_EQ(key.Create(HKEY_LOCAL_MACHINE, kRegKeyCompanyCloudManagement, Wow6432(KEY_WRITE)), ERROR_SUCCESS); - EXPECT_EQ(key.WriteValue(kRegValueEnrollmentToken, L"test_enrollment_token"), + constexpr char kEnrollmentToken[] = "test_enrollment_token"; + EXPECT_EQ(key.WriteValue(kRegValueEnrollmentToken, kEnrollmentToken, + sizeof(kEnrollmentToken) - 1, REG_BINARY), ERROR_SUCCESS); EXPECT_EQ(storage->GetEnrollmentToken(), "test_enrollment_token"); }
diff --git a/chrome/updater/device_management/dm_storage_win.cc b/chrome/updater/device_management/dm_storage_win.cc index a513f6a2..1b75d56a 100644 --- a/chrome/updater/device_management/dm_storage_win.cc +++ b/chrome/updater/device_management/dm_storage_win.cc
@@ -5,6 +5,7 @@ #include "chrome/updater/device_management/dm_storage.h" #include <string> +#include <vector> #include "base/base_paths_win.h" #include "base/files/file_path.h" @@ -18,7 +19,6 @@ #include "chrome/updater/win/win_constants.h" namespace updater { - namespace { // Registry for device ID. @@ -26,6 +26,68 @@ L"SOFTWARE\\Microsoft\\Cryptography\\"; constexpr wchar_t kRegValueMachineGuid[] = L"MachineGuid"; +bool ReadTokenBinary(const base::win::RegKey& key, + const wchar_t* name, + std::string& token) { + VLOG(2) << __func__; + DWORD size = 0; + DWORD type = 0; + LONG error = key.ReadValue(name, nullptr, &size, &type); + if (error != ERROR_SUCCESS) { + VLOG(2) << "ReadValue failed: " << error; + return false; + } + if (size > DMStorage::kMaxDmTokenLength) { + VLOG(2) << "Value is too large: " << size; + return false; + } + std::vector<char> value(size); + error = key.ReadValue(name, &value.front(), &size, &type); + if (error != ERROR_SUCCESS) { + VLOG(2) << "ReadValue failed: " << error; + return false; + } + token.assign(value.begin(), value.end()); + return true; +} + +bool WriteTokenBinary(base::win::RegKey& key, + const wchar_t* name, + const std::string& token) { + VLOG(2) << __func__; + if (token.size() > DMStorage::kMaxDmTokenLength) { + VLOG(2) << "Value is too large: " << token.size(); + return false; + } + const LONG error = + key.WriteValue(name, token.data(), token.size(), REG_BINARY); + if (error != ERROR_SUCCESS) { + VLOG(2) << "WriteValue failed: " << error; + return false; + } + return true; +} + +// Set `name` in `root`\`key` as a binary `value`. +bool SetRegistryKeyBinary(HKEY root, + const std::wstring& key, + const std::wstring& name, + const std::string& value) { + base::win::RegKey rkey; + LONG error = rkey.Create(root, key.c_str(), Wow6432(KEY_WRITE)); + if (error != ERROR_SUCCESS) { + VLOG(1) << "Failed to open (" << root << ") " << key << ": " << error; + return false; + } + error = rkey.WriteValue(name.c_str(), value.data(), value.size(), REG_BINARY); + if (error != ERROR_SUCCESS) { + VLOG(1) << "Failed to write (" << root << ") " << key << " @ " << name + << " (binary): " << error; + return false; + } + return error == ERROR_SUCCESS; +} + class TokenService : public TokenServiceInterface { public: TokenService() = default; @@ -67,9 +129,8 @@ bool TokenService::StoreEnrollmentToken(const std::string& token) { const bool result = - SetRegistryKey(HKEY_LOCAL_MACHINE, kRegKeyCompanyCloudManagement, - kRegValueEnrollmentToken, base::SysUTF8ToWide(token)); - + SetRegistryKeyBinary(HKEY_LOCAL_MACHINE, kRegKeyCompanyCloudManagement, + kRegValueEnrollmentToken, token); VLOG(1) << "Update enrollment token to: [" << token << "], bool result=" << result; return result; @@ -84,41 +145,35 @@ } std::string TokenService::GetEnrollmentToken() const { - std::wstring token; + std::string token; if (base::win::RegKey key; key.Open(HKEY_LOCAL_MACHINE, kRegKeyCompanyCloudManagement, Wow6432(KEY_READ)) == ERROR_SUCCESS && - key.ReadValue(kRegValueEnrollmentToken, &token) == ERROR_SUCCESS) { - return base::SysWideToUTF8(token); + ReadTokenBinary(key, kRegValueEnrollmentToken, token)) { + return token; } - if (base::win::RegKey key; key.Open(HKEY_LOCAL_MACHINE, kRegKeyCompanyLegacyCloudManagement, Wow6432(KEY_READ)) == ERROR_SUCCESS && - key.ReadValue(kRegValueCloudManagementEnrollmentToken, &token) == - ERROR_SUCCESS) { - return base::SysWideToUTF8(token); + ReadTokenBinary(key, kRegValueCloudManagementEnrollmentToken, token)) { + return token; } return {}; } bool TokenService::StoreDmToken(const std::string& token) { - const std::wstring dm_token(base::SysUTF8ToWide(token)); - if (!SetRegistryKey(HKEY_LOCAL_MACHINE, kRegKeyCompanyEnrollment, - kRegValueDmToken, dm_token)) { + if (!SetRegistryKeyBinary(HKEY_LOCAL_MACHINE, kRegKeyCompanyEnrollment, + kRegValueDmToken, token)) { VLOG(1) << "Failed to write DM token."; return false; } - base::win::RegKey legacy_key; if (legacy_key.Create(HKEY_LOCAL_MACHINE, kRegKeyCompanyLegacyEnrollment, KEY_WOW64_64KEY | KEY_WRITE) != ERROR_SUCCESS || - legacy_key.WriteValue(kRegValueDmToken, dm_token.c_str()) != - ERROR_SUCCESS) { + !WriteTokenBinary(legacy_key, kRegValueDmToken, token)) { VLOG(1) << "Failed to write DM token at the legacy place."; return false; } - VLOG(1) << "Updated DM token to: [" << token << "]"; return true; } @@ -146,19 +201,18 @@ } std::string TokenService::GetDmToken() const { - std::wstring token; + std::string token; if (base::win::RegKey key; key.Open(HKEY_LOCAL_MACHINE, kRegKeyCompanyEnrollment, Wow6432(KEY_READ)) == ERROR_SUCCESS && - key.ReadValue(kRegValueDmToken, &token) == ERROR_SUCCESS) { - return base::SysWideToUTF8(token); + ReadTokenBinary(key, kRegValueDmToken, token)) { + return token; } - if (base::win::RegKey key; key.Open(HKEY_LOCAL_MACHINE, kRegKeyCompanyLegacyEnrollment, KEY_WOW64_64KEY | KEY_READ) == ERROR_SUCCESS && - key.ReadValue(kRegValueDmToken, &token) == ERROR_SUCCESS) { - return base::SysWideToUTF8(token); + ReadTokenBinary(key, kRegValueDmToken, token)) { + return token; } return {}; }
diff --git a/chromeos/ash/components/dbus/debug_daemon/BUILD.gn b/chromeos/ash/components/dbus/debug_daemon/BUILD.gn index 134fa7f..aa9e336 100644 --- a/chromeos/ash/components/dbus/debug_daemon/BUILD.gn +++ b/chromeos/ash/components/dbus/debug_daemon/BUILD.gn
@@ -18,6 +18,8 @@ ] sources = [ + "binary_log_files_reader.cc", + "binary_log_files_reader.h", "debug_daemon_client.cc", "debug_daemon_client.h", "debug_daemon_client_provider.cc",
diff --git a/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.cc b/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.cc new file mode 100644 index 0000000..99536dbd --- /dev/null +++ b/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.cc
@@ -0,0 +1,84 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.h" + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "base/files/scoped_file.h" +#include "base/functional/bind.h" +#include "base/logging.h" +#include "base/task/thread_pool.h" +#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h" +#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h" +#include "chromeos/dbus/common/pipe_reader.h" +#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" + +namespace feedback { + +BinaryLogFilesReader::BinaryLogFilesReader() = default; +BinaryLogFilesReader::~BinaryLogFilesReader() = default; + +void BinaryLogFilesReader::GetFeedbackBinaryLogs( + const cryptohome::AccountIdentifier& id, + debugd::FeedbackBinaryLogType log_type, + GetFeedbackBinaryLogsCallback callback) { + CHECK(callback); + const auto task_runner = base::ThreadPool::CreateTaskRunner( + {base::MayBlock(), base::TaskPriority::USER_VISIBLE, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); + auto pipe_reader = std::make_unique<chromeos::PipeReader>(task_runner); + + // Sets up stream for data collection and returns the write end of the pipe if + // stream was setup correctly. The write end will be passed to debugd which + // will write data to it. On completion or any failure, OnIOComplete will be + // called. + base::ScopedFD pipe_write_end = pipe_reader->StartIO(base::BindOnce( + &BinaryLogFilesReader::OnIOComplete, weak_ptr_factory_.GetWeakPtr(), + log_type, std::move(pipe_reader), std::move(callback))); + + // Current implementation only fetches one log file. + std::map<debugd::FeedbackBinaryLogType, base::ScopedFD> log_fd_map; + log_fd_map[log_type] = std::move(pipe_write_end); + // Pass the write end of the pipe to debugd to collect data. + // OnGetFeedbackBinaryLogsCompleted will be called after debugd has started + // writing logs to the pipe. The purpose of the callback is merely for + // logging. The debugd method GetFeedbackBinaryLogs is async and will return + // without waiting for IO completion. Once debugd finishes writing to the + // pipe, it will close its write end. The read end of pipe will receive data + // through the OnIOComplete callback. In case of timeout or other failures, + // the write end will be closed and OnIOComplete will be called with empty + // data. + ash::DebugDaemonClient::Get()->GetFeedbackBinaryLogs( + id, log_fd_map, + base::BindOnce(&BinaryLogFilesReader::OnGetFeedbackBinaryLogsCompleted, + weak_ptr_factory_.GetWeakPtr())); +} + +void BinaryLogFilesReader::OnIOComplete( + debugd::FeedbackBinaryLogType log_type, + std::unique_ptr<chromeos::PipeReader> pipe_reader, + GetFeedbackBinaryLogsCallback callback, + std::optional<std::string> data) { + CHECK(callback); + // Shut down data collection. + pipe_reader.reset(); + // Current implementation supports only one log type at a time. Therefore, it + // is ok to run the callback here. + BinaryLogsResponse response = + std::make_unique<std::map<FeedbackBinaryLogType, std::string>>(); + response->emplace(log_type, data.value_or(std::string())); + std::move(callback).Run(std::move(response)); +} + +void BinaryLogFilesReader::OnGetFeedbackBinaryLogsCompleted(bool succeeded) { + if (!succeeded) { + LOG(ERROR) << "GetFeedbackBinaryLogs failed."; + } +} + +} // namespace feedback
diff --git a/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.h b/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.h new file mode 100644 index 0000000..6ea2f75 --- /dev/null +++ b/chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.h
@@ -0,0 +1,58 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMEOS_ASH_COMPONENTS_DBUS_DEBUG_DAEMON_BINARY_LOG_FILES_READER_H_ +#define CHROMEOS_ASH_COMPONENTS_DBUS_DEBUG_DAEMON_BINARY_LOG_FILES_READER_H_ + +#include <map> +#include <memory> + +#include "base/component_export.h" +#include "base/memory/weak_ptr.h" +#include "chromeos/dbus/common/pipe_reader.h" +#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" + +namespace cryptohome { +class AccountIdentifier; +} + +namespace feedback { + +using debugd::FeedbackBinaryLogType; + +// Helper class to fetch binary log files over dbus using debugd's +// GetFeedbackBinaryLogs method. The method can fetch multiple logs, one for +// each log types in one trip. Since currently there is one log type only, this +// class only supports fetching one log for now. It can be expanded when needed. +class COMPONENT_EXPORT(DEBUG_DAEMON) BinaryLogFilesReader { + public: + BinaryLogFilesReader(); + BinaryLogFilesReader(const BinaryLogFilesReader&) = delete; + BinaryLogFilesReader& operator=(const BinaryLogFilesReader&) = delete; + ~BinaryLogFilesReader(); + + using BinaryLogsResponse = + std::unique_ptr<std::map<FeedbackBinaryLogType, std::string>>; + // Callback type for GetFeedbackBinaryLogs(); + using GetFeedbackBinaryLogsCallback = + base::OnceCallback<void(BinaryLogsResponse logs_response)>; + // Start calling debugd's GetFeedbackBinaryLogs method to fetch log files. The + // callback will be invoked once fetching is completed. + void GetFeedbackBinaryLogs(const cryptohome::AccountIdentifier& id, + debugd::FeedbackBinaryLogType log_type, + GetFeedbackBinaryLogsCallback callback); + + private: + void OnIOComplete(debugd::FeedbackBinaryLogType log_type, + std::unique_ptr<chromeos::PipeReader> pipe_reader, + GetFeedbackBinaryLogsCallback callback, + std::optional<std::string> data); + void OnGetFeedbackBinaryLogsCompleted(bool succeeded); + + base::WeakPtrFactory<BinaryLogFilesReader> weak_ptr_factory_{this}; +}; + +} // namespace feedback + +#endif // CHROMEOS_ASH_COMPONENTS_DBUS_DEBUG_DAEMON_BINARY_LOG_FILES_READER_H_
diff --git a/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.cc b/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.cc index 241a2003..584e077 100644 --- a/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.cc +++ b/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.cc
@@ -16,6 +16,7 @@ #include <vector> #include "base/files/file_path.h" +#include "base/files/scoped_file.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/json/json_string_value_serializer.h" @@ -38,6 +39,7 @@ #include "dbus/message.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" +#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" namespace ash { @@ -282,6 +284,35 @@ pipe_reader->AsWeakPtr())); } + void GetFeedbackBinaryLogs( + const cryptohome::AccountIdentifier& id, + const std::map<debugd::FeedbackBinaryLogType, base::ScopedFD>& + log_type_fds, + chromeos::VoidDBusMethodCallback callback) override { + dbus::MethodCall method_call(debugd::kDebugdInterface, + debugd::kGetFeedbackBinaryLogs); + dbus::MessageWriter writer(&method_call); + writer.AppendString(id.account_id()); + + dbus::MessageWriter array_writer(nullptr); + // Write map of log_type and fd. + writer.OpenArray("{ih}", &array_writer); + for (const auto& log_type : log_type_fds) { + dbus::MessageWriter dict_entry_writer(nullptr); + array_writer.OpenDictEntry(&dict_entry_writer); + dict_entry_writer.AppendInt32(log_type.first); + dict_entry_writer.AppendFileDescriptor(log_type.second.get()); + array_writer.CloseContainer(&dict_entry_writer); + } + writer.CloseContainer(&array_writer); + + DVLOG(1) << "Requesting feedback binary logs"; + debugdaemon_proxy_->CallMethodWithErrorResponse( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::BindOnce(&DebugDaemonClientImpl::OnFeedbackBinaryLogsResponse, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); + } + void BackupArcBugReport(const cryptohome::AccountIdentifier& id, chromeos::VoidDBusMethodCallback callback) override { dbus::MethodCall method_call(debugd::kDebugdInterface, @@ -749,6 +780,17 @@ } } + void OnFeedbackBinaryLogsResponse(chromeos::VoidDBusMethodCallback callback, + dbus::Response* response, + dbus::ErrorResponse* err_response) { + bool succeeded = !err_response; + if (!succeeded) { + LOG(ERROR) << "Failed to GetFeedbackBinaryLogs. Error: " + << err_response->GetErrorName(); + } + std::move(callback).Run(succeeded); + } + // Called when a response for a simple start is received. void OnStartMethod(dbus::Response* response) { if (!response) {
diff --git a/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h b/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h index 9477cd8..697399d 100644 --- a/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h +++ b/chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h
@@ -23,6 +23,7 @@ #include "chromeos/dbus/common/dbus_client.h" #include "chromeos/dbus/common/dbus_method_call_status.h" #include "dbus/message.h" +#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace cryptohome { @@ -142,6 +143,16 @@ const std::vector<debugd::FeedbackLogType>& requested_logs, GetLogsCallback callback) = 0; + // Gets feedback binary logs from debugd. + // |id|: Cryptohome Account identifier for the user to get logs for. + // |log_type_fds|: The map of FeedbackBinaryLogType and its FD pair. + // |callback|: The callback to be invoked once the debugd method is completed. + virtual void GetFeedbackBinaryLogs( + const cryptohome::AccountIdentifier& id, + const std::map<debugd::FeedbackBinaryLogType, base::ScopedFD>& + log_type_fds, + chromeos::VoidDBusMethodCallback callback) = 0; + // Retrieves the ARC bug report for user identified by |userhash| // and saves it in debugd daemon store. // If a backup already exists, it is overwritten.
diff --git a/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.cc b/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.cc index 376e939..bf0cb99c 100644 --- a/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.cc +++ b/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.cc
@@ -15,11 +15,14 @@ #include "base/command_line.h" #include "base/containers/contains.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/location.h" #include "base/strings/string_number_conversions.h" #include "base/task/single_thread_task_runner.h" +#include "base/task/thread_pool.h" #include "chromeos/dbus/constants/dbus_switches.h" namespace { @@ -27,6 +30,11 @@ const char kCrOSTracingAgentName[] = "cros"; const char kCrOSTraceLabel[] = "systemTraceEvents"; +// Writes the |data| to |fd|, then close |fd|. +void WriteData(base::ScopedFD fd, const std::string& data) { + base::WriteFileDescriptor(fd.get(), data); +} + } // namespace namespace ash { @@ -133,6 +141,24 @@ base::BindOnce(std::move(callback), /*succeeded=*/true, sample)); } +void FakeDebugDaemonClient::GetFeedbackBinaryLogs( + const cryptohome::AccountIdentifier& id, + const std::map<debugd::FeedbackBinaryLogType, base::ScopedFD>& log_type_fds, + chromeos::VoidDBusMethodCallback callback) { + constexpr char kTestData[] = "TestData"; + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), /*succeeded=*/true)); + + // Write dummy data to the pipes after callback is invoked to simulate + // potential delay writing bug chunk of data. + for (const auto& item : log_type_fds) { + base::ThreadPool::PostTask( + FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING}, + base::BindOnce(&WriteData, base::ScopedFD(dup(item.second.get())), + kTestData)); + } +} + void FakeDebugDaemonClient::BackupArcBugReport( const cryptohome::AccountIdentifier& id, chromeos::VoidDBusMethodCallback callback) {
diff --git a/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h b/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h index 8a61c57..4b674c0 100644 --- a/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h +++ b/chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h
@@ -65,6 +65,11 @@ const cryptohome::AccountIdentifier& id, const std::vector<debugd::FeedbackLogType>& requested_logs, GetLogsCallback callback) override; + void GetFeedbackBinaryLogs( + const cryptohome::AccountIdentifier& id, + const std::map<debugd::FeedbackBinaryLogType, base::ScopedFD>& + log_type_fds, + chromeos::VoidDBusMethodCallback callback) override; void BackupArcBugReport(const cryptohome::AccountIdentifier& id, chromeos::VoidDBusMethodCallback callback) override; void GetAllLogs(GetLogsCallback callback) override;
diff --git a/chromeos/ash/components/phonehub/BUILD.gn b/chromeos/ash/components/phonehub/BUILD.gn index d2b268a..e5ea928 100644 --- a/chromeos/ash/components/phonehub/BUILD.gn +++ b/chromeos/ash/components/phonehub/BUILD.gn
@@ -141,6 +141,7 @@ deps = [ "//ash/constants", + "//ash/public/cpp:cpp", "//ash/resources/vector_icons", "//ash/webui/eche_app_ui:eche_app_ui_pref", "//ash/webui/eche_app_ui:eche_connection_status", @@ -159,6 +160,7 @@ "//chromeos/dbus/power", "//chromeos/services/network_config/public/cpp", "//components/keyed_service/core", + "//components/metrics/structured:structured_metrics_features", "//components/prefs", "//components/session_manager/core", "//device/bluetooth", @@ -283,6 +285,7 @@ "notification_manager_impl_unittest.cc", "notification_processor_unittest.cc", "onboarding_ui_tracker_impl_unittest.cc", + "phone_hub_structured_metrics_logger_unittest.cc", "phone_hub_ui_readiness_recorder_unittest.cc", "phone_status_model_unittest.cc", "phone_status_processor_unittest.cc", @@ -318,6 +321,7 @@ "//chromeos/ash/services/secure_channel/public/cpp/client:test_support", "//chromeos/ash/services/secure_channel/public/mojom", "//chromeos/dbus/power", + "//components/metrics/structured:structured_metrics_features", "//components/prefs:test_support", "//components/session_manager/core", "//device/bluetooth:mocks",
diff --git a/chromeos/ash/components/phonehub/DEPS b/chromeos/ash/components/phonehub/DEPS index 1b1ffbe..aea2fed8 100644 --- a/chromeos/ash/components/phonehub/DEPS +++ b/chromeos/ash/components/phonehub/DEPS
@@ -1,9 +1,11 @@ include_rules = [ + "+ash/public/cpp", "+ash/resources/vector_icons", "+ash/webui/eche_app_ui", "+chromeos/ash/components/multidevice", "+components/keyed_service/core/keyed_service.h", "+components/session_manager/core", + "+components/metrics/structured", "+device/bluetooth", "+services/data_decoder/public", "+third_party/skia",
diff --git a/chromeos/ash/components/phonehub/feature_status_provider_impl.cc b/chromeos/ash/components/phonehub/feature_status_provider_impl.cc index 61bc1c1..dbf045a 100644 --- a/chromeos/ash/components/phonehub/feature_status_provider_impl.cc +++ b/chromeos/ash/components/phonehub/feature_status_provider_impl.cc
@@ -144,12 +144,15 @@ multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client, secure_channel::ConnectionManager* connection_manager, session_manager::SessionManager* session_manager, - chromeos::PowerManagerClient* power_manager_client) + chromeos::PowerManagerClient* power_manager_client, + PhoneHubStructuredMetricsLogger* phone_hub_structured_metrics_logger) : device_sync_client_(device_sync_client), multidevice_setup_client_(multidevice_setup_client), connection_manager_(connection_manager), session_manager_(session_manager), - power_manager_client_(power_manager_client) { + power_manager_client_(power_manager_client), + phone_hub_structured_metrics_logger_( + phone_hub_structured_metrics_logger) { DCHECK(session_manager_); DCHECK(power_manager_client_); device_sync_client_->AddObserver(this); @@ -250,6 +253,22 @@ PA_LOG(INFO) << "Phone Hub feature status: " << *status_ << " => " << computed_status; *status_ = computed_status; + switch (status_.value()) { + case FeatureStatus::kDisabled: + case FeatureStatus::kLockOrSuspended: + phone_hub_structured_metrics_logger_->ResetSessionId(); + break; + case FeatureStatus::kEligiblePhoneButNotSetUp: + case FeatureStatus::kNotEligibleForFeature: + case FeatureStatus::kPhoneSelectedAndPendingSetup: + phone_hub_structured_metrics_logger_->ResetCachedInformation(); + break; + case FeatureStatus::kEnabledAndConnecting: + case FeatureStatus::kEnabledAndConnected: + case FeatureStatus::kUnavailableBluetoothOff: + case FeatureStatus::kEnabledButDisconnected: + break; + } NotifyStatusChanged(); UMA_HISTOGRAM_ENUMERATION("PhoneHub.Adoption.FeatureStatusChangesSinceLogin",
diff --git a/chromeos/ash/components/phonehub/feature_status_provider_impl.h b/chromeos/ash/components/phonehub/feature_status_provider_impl.h index 8ca0e79..d7a6ed1 100644 --- a/chromeos/ash/components/phonehub/feature_status_provider_impl.h +++ b/chromeos/ash/components/phonehub/feature_status_provider_impl.h
@@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "chromeos/ash/components/phonehub/feature_status_provider.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/services/device_sync/public/cpp/device_sync_client.h" #include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h" #include "chromeos/ash/services/secure_channel/public/cpp/client/connection_manager.h" @@ -35,7 +36,8 @@ multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client, secure_channel::ConnectionManager* connection_manager, session_manager::SessionManager* session_manager, - chromeos::PowerManagerClient* power_manager_client); + chromeos::PowerManagerClient* power_manager_client, + PhoneHubStructuredMetricsLogger* phone_hub_structured_metrics_logger); ~FeatureStatusProviderImpl() override; private: @@ -85,6 +87,7 @@ raw_ptr<secure_channel::ConnectionManager> connection_manager_; raw_ptr<session_manager::SessionManager> session_manager_; raw_ptr<chromeos::PowerManagerClient> power_manager_client_; + raw_ptr<PhoneHubStructuredMetricsLogger> phone_hub_structured_metrics_logger_; scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_; std::optional<FeatureStatus> status_;
diff --git a/chromeos/ash/components/phonehub/feature_status_provider_impl_unittest.cc b/chromeos/ash/components/phonehub/feature_status_provider_impl_unittest.cc index c756c69b..048eed51 100644 --- a/chromeos/ash/components/phonehub/feature_status_provider_impl_unittest.cc +++ b/chromeos/ash/components/phonehub/feature_status_provider_impl_unittest.cc
@@ -11,11 +11,13 @@ #include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "chromeos/ash/components/multidevice/remote_device_test_util.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h" #include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h" #include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h" #include "chromeos/dbus/power/fake_power_manager_client.h" #include "chromeos/dbus/power/power_manager_client.h" +#include "components/prefs/testing_pref_service.h" #include "components/session_manager/core/session_manager.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" @@ -140,10 +142,14 @@ session_manager_ = std::make_unique<session_manager::SessionManager>(); fake_power_manager_client_ = std::make_unique<chromeos::FakePowerManagerClient>(); + PhoneHubStructuredMetricsLogger::RegisterPrefs(pref_service_.registry()); + phone_hub_structured_metrics_logger_ = + std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_); provider_ = std::make_unique<FeatureStatusProviderImpl>( &fake_device_sync_client_, &fake_multidevice_setup_client_, &fake_connection_manager_, session_manager_.get(), - fake_power_manager_client_.get()); + fake_power_manager_client_.get(), + phone_hub_structured_metrics_logger_.get()); provider_->AddObserver(&fake_observer_); } @@ -254,6 +260,9 @@ bool is_adapter_present_ = true; bool is_adapter_powered_ = true; + TestingPrefServiceSimple pref_service_; + std::unique_ptr<PhoneHubStructuredMetricsLogger> + phone_hub_structured_metrics_logger_; FakeObserver fake_observer_; std::unique_ptr<session_manager::SessionManager> session_manager_; std::unique_ptr<chromeos::FakePowerManagerClient> fake_power_manager_client_;
diff --git a/chromeos/ash/components/phonehub/message_receiver_unittest.cc b/chromeos/ash/components/phonehub/message_receiver_unittest.cc index d3a57b38..c22891f 100644 --- a/chromeos/ash/components/phonehub/message_receiver_unittest.cc +++ b/chromeos/ash/components/phonehub/message_receiver_unittest.cc
@@ -2,17 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chromeos/ash/components/phonehub/message_receiver_impl.h" - #include <netinet/in.h> + #include <memory> #include "ash/constants/ash_features.h" #include "base/strings/strcat.h" #include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "chromeos/ash/components/phonehub/message_receiver_impl.h" #include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" #include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h" +#include "components/prefs/testing_pref_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace ash::phonehub { @@ -192,14 +194,14 @@ protected: MessageReceiverImplTest() : fake_connection_manager_( - std::make_unique<secure_channel::FakeConnectionManager>()), - phone_hub_structured_metrics_logger_( - std::make_unique<PhoneHubStructuredMetricsLogger>()) {} + std::make_unique<secure_channel::FakeConnectionManager>()) {} MessageReceiverImplTest(const MessageReceiverImplTest&) = delete; MessageReceiverImplTest& operator=(const MessageReceiverImplTest&) = delete; ~MessageReceiverImplTest() override = default; void SetUp() override { + phone_hub_structured_metrics_logger_ = + std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_); message_receiver_ = std::make_unique<MessageReceiverImpl>( fake_connection_manager_.get(), phone_hub_structured_metrics_logger_.get()); @@ -280,6 +282,8 @@ return fake_observer_.last_app_list_incremental_update(); } + base::test::TaskEnvironment task_environment_; + TestingPrefServiceSimple pref_service_; FakeObserver fake_observer_; std::unique_ptr<secure_channel::FakeConnectionManager> fake_connection_manager_;
diff --git a/chromeos/ash/components/phonehub/message_sender_impl.cc b/chromeos/ash/components/phonehub/message_sender_impl.cc index 44c6684e..2c53aad 100644 --- a/chromeos/ash/components/phonehub/message_sender_impl.cc +++ b/chromeos/ash/components/phonehub/message_sender_impl.cc
@@ -59,6 +59,7 @@ proto::CrosState request; request.set_notification_setting(is_notification_enabled); request.set_camera_roll_setting(is_camera_roll_enabled); + phone_hub_structured_metrics_logger_->SetChromebookInfo(request); if (attestation_certs != nullptr) { proto::AttestationData* attestation_data =
diff --git a/chromeos/ash/components/phonehub/message_sender_unittest.cc b/chromeos/ash/components/phonehub/message_sender_unittest.cc index 73fbc82..967b57cb 100644 --- a/chromeos/ash/components/phonehub/message_sender_unittest.cc +++ b/chromeos/ash/components/phonehub/message_sender_unittest.cc
@@ -2,21 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chromeos/ash/components/phonehub/message_sender_impl.h" - #include <netinet/in.h> #include <stdint.h> + #include <memory> #include <string> #include "ash/constants/ash_features.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/task_environment.h" #include "chromeos/ash/components/phonehub/fake_feature_status_provider.h" +#include "chromeos/ash/components/phonehub/message_sender_impl.h" #include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/phone_hub_ui_readiness_recorder.h" #include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" #include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_manager.h" +#include "components/prefs/testing_pref_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace ash { @@ -38,8 +40,9 @@ std::make_unique<PhoneHubUiReadinessRecorder>( fake_feature_status_provider_.get(), fake_connection_manager_.get()); + PhoneHubStructuredMetricsLogger::RegisterPrefs(pref_service_.registry()); phone_hub_structured_metrics_logger_ = - std::make_unique<PhoneHubStructuredMetricsLogger>(); + std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_); message_sender_ = std::make_unique<MessageSenderImpl>( fake_connection_manager_.get(), phone_hub_ui_readiness_recorder_.get(), phone_hub_structured_metrics_logger_.get()); @@ -68,6 +71,8 @@ EXPECT_EQ(expected_proto_message, actual_proto_message); } + base::test::TaskEnvironment task_environment_; + TestingPrefServiceSimple pref_service_; std::unique_ptr<secure_channel::FakeConnectionManager> fake_connection_manager_; std::unique_ptr<FakeFeatureStatusProvider> fake_feature_status_provider_; @@ -84,6 +89,7 @@ request.set_camera_roll_setting(proto::CameraRollSetting::CAMERA_ROLL_OFF); request.set_allocated_attestation_data(nullptr); request.set_should_provide_eche_status(true); + phone_hub_structured_metrics_logger_->SetChromebookInfo(request); message_sender_->SendCrosState(/*notification_enabled=*/true, /*camera_roll_enabled=*/false, /*certs=*/nullptr); @@ -100,6 +106,7 @@ request.mutable_attestation_data()->set_type( proto::AttestationData::CROS_SOFT_BIND_CERT_CHAIN); request.mutable_attestation_data()->add_certificates("certificate"); + phone_hub_structured_metrics_logger_->SetChromebookInfo(request); std::vector<std::string> certificates = {"certificate"};
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc index a1aa627..4b1240d 100644 --- a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc +++ b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
@@ -70,7 +70,7 @@ attestation_certificate_generator) : icon_decoder_(std::make_unique<IconDecoderImpl>()), phone_hub_structured_metrics_logger_( - std::make_unique<PhoneHubStructuredMetricsLogger>()), + std::make_unique<PhoneHubStructuredMetricsLogger>(pref_service)), connection_manager_( std::make_unique<secure_channel::ConnectionManagerImpl>( multidevice_setup_client, @@ -84,7 +84,8 @@ multidevice_setup_client, connection_manager_.get(), session_manager::SessionManager::Get(), - chromeos::PowerManagerClient::Get())), + chromeos::PowerManagerClient::Get(), + phone_hub_structured_metrics_logger_.get())), user_action_recorder_(std::make_unique<UserActionRecorderImpl>( feature_status_provider_.get())), phone_hub_ui_readiness_recorder_( @@ -166,7 +167,8 @@ app_stream_manager_.get(), app_stream_launcher_data_model_.get(), icon_decoder_.get(), - phone_hub_ui_readiness_recorder_.get())), + phone_hub_ui_readiness_recorder_.get(), + phone_hub_structured_metrics_logger_.get())), tether_controller_( std::make_unique<TetherControllerImpl>(phone_model_.get(), user_action_recorder_.get(),
diff --git a/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.cc b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.cc index 201fad7..736b464 100644 --- a/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.cc +++ b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.cc
@@ -4,44 +4,301 @@ #include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" -#include "base/notreached.h" +#include <string> + +#include "ash/public/cpp/network_config_service.h" +#include "base/feature_list.h" +#include "base/functional/bind.h" +#include "base/i18n/rtl.h" +#include "base/system/sys_info.h" +#include "base/uuid.h" +#include "chromeos/ash/components/phonehub/pref_names.h" +#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" +#include "components/metrics/structured/structured_metrics_features.h" +#include "crypto/sha2.h" +#include "device/bluetooth/floss/floss_features.h" namespace ash::phonehub { -PhoneHubStructuredMetricsLogger::PhoneHubStructuredMetricsLogger() = default; +void PhoneHubStructuredMetricsLogger::RegisterPrefs( + PrefRegistrySimple* registry) { + registry->RegisterStringPref(prefs::kPhoneManufacturer, std::string()); + registry->RegisterStringPref(prefs::kPhoneModel, std::string()); + registry->RegisterStringPref(prefs::kPhoneLocale, std::string()); + registry->RegisterStringPref(prefs::kPhonePseudonymousId, std::string()); + registry->RegisterInt64Pref(prefs::kPhoneAmbientApkVersion, 0); + registry->RegisterInt64Pref(prefs::kPhoneGmsCoreVersion, 0); + registry->RegisterIntegerPref(prefs::kPhoneAndroidVersion, 0); + registry->RegisterIntegerPref( + prefs::kPhoneProfileType, + -1); // Make default value different from normal default profile type 0 + registry->RegisterTimePref(prefs::kPhoneInfoLastUpdatedTime, base::Time()); + registry->RegisterStringPref(prefs::kChromebookPseudonymousId, std::string()); + registry->RegisterTimePref(prefs::kPseudonymousIdRotationDate, base::Time()); +} + +PhoneHubStructuredMetricsLogger::PhoneHubStructuredMetricsLogger( + PrefService* pref_service) + : bluetooth_stack_(floss::features::IsFlossEnabled() + ? BluetoothStack::kFloss + : BluetoothStack::kBlueZ), + chromebook_locale_(base::i18n::GetConfiguredLocale()), + pref_service_(pref_service) { + ash::GetNetworkConfigService( + cros_network_config_.BindNewPipeAndPassReceiver()); +} PhoneHubStructuredMetricsLogger::~PhoneHubStructuredMetricsLogger() = default; void PhoneHubStructuredMetricsLogger::LogPhoneHubDiscoveryStarted( DiscoveryEntryPoint entry_point) { - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); } void PhoneHubStructuredMetricsLogger::LogDiscoveryAttempt( secure_channel::mojom::DiscoveryResult result, std::optional<secure_channel::mojom::DiscoveryErrorCode> error_code) { - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); } void PhoneHubStructuredMetricsLogger::LogNearbyConnectionState( secure_channel::mojom::NearbyConnectionStep step, secure_channel::mojom::NearbyConnectionStepResult result) { - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); } void PhoneHubStructuredMetricsLogger::LogSecureChannelState( secure_channel::mojom::SecureChannelState state) { - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); } void PhoneHubStructuredMetricsLogger::LogPhoneHubMessageEvent( proto::MessageType message_type, PhoneHubMessageDirection message_direction) { - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); } void PhoneHubStructuredMetricsLogger::LogPhoneHubUiStateUpdated( PhoneHubUiState ui_state) { - std::string state; - NOTIMPLEMENTED(); + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + UpdateIdentifiersIfNeeded(); +} + +void PhoneHubStructuredMetricsLogger::ProcessPhoneInformation( + const proto::PhoneProperties& phone_properties) { + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + if (phone_properties.has_pseudonymous_id_next_rotation_date()) { + base::Time pseudonymous_id_rotation_date = + base::Time::FromMillisecondsSinceUnixEpoch( + phone_properties.pseudonymous_id_next_rotation_date()); + if (pref_service_->GetTime(prefs::kPseudonymousIdRotationDate).is_null() || + pseudonymous_id_rotation_date < + pref_service_->GetTime(prefs::kPseudonymousIdRotationDate)) { + pref_service_->SetTime(prefs::kPseudonymousIdRotationDate, + pseudonymous_id_rotation_date); + } + } + if (phone_properties.has_phone_pseudonymous_id()) { + if (pref_service_->GetString(prefs::kPhonePseudonymousId).empty() || + pref_service_->GetString(prefs::kPhonePseudonymousId) != + phone_properties.phone_pseudonymous_id()) { + pref_service_->SetString(prefs::kPhonePseudonymousId, + phone_properties.phone_pseudonymous_id()); + } + } + if (phone_properties.has_phone_manufacturer()) { + if (pref_service_->GetString(prefs::kPhoneManufacturer).empty() || + pref_service_->GetString(prefs::kPhoneManufacturer) != + phone_properties.phone_manufacturer()) { + pref_service_->SetString(prefs::kPhoneManufacturer, + phone_properties.phone_manufacturer()); + } + } + if (phone_properties.has_phone_model()) { + if (pref_service_->GetString(prefs::kPhoneModel).empty() || + pref_service_->GetString(prefs::kPhoneModel) != + phone_properties.phone_model()) { + pref_service_->SetString(prefs::kPhoneModel, + phone_properties.phone_model()); + } + } + + if (pref_service_->GetInt64(prefs::kPhoneGmsCoreVersion) != + phone_properties.gmscore_version()) { + pref_service_->SetInt64(prefs::kPhoneGmsCoreVersion, + phone_properties.gmscore_version()); + } + + if (pref_service_->GetInteger(prefs::kPhoneAndroidVersion) != + phone_properties.android_version()) { + pref_service_->SetInteger(prefs::kPhoneAndroidVersion, + phone_properties.android_version()); + } + + if (phone_properties.has_ambient_version() && + pref_service_->GetInt64(prefs::kPhoneAmbientApkVersion) != + phone_properties.ambient_version()) { + pref_service_->SetInt64(prefs::kPhoneAmbientApkVersion, + phone_properties.ambient_version()); + } + + pref_service_->SetTime(prefs::kPhoneInfoLastUpdatedTime, + base::Time::NowFromSystemTime()); + + if (phone_properties.has_network_status()) { + phone_network_status_ = phone_properties.network_status(); + if (phone_network_status_ == proto::NetworkStatus::CELLULAR) { + network_state_ = NetworkState::kPhoneOnCellular; + } else if (phone_network_status_ == proto::NetworkStatus::WIFI) { + if (phone_properties.has_ssid()) { + phone_network_ssid_ = phone_properties.ssid(); + } + cros_network_config_->GetNetworkStateList( + chromeos::network_config::mojom::NetworkFilter::New( + chromeos::network_config::mojom::FilterType::kActive, + chromeos::network_config::mojom::NetworkType::kWiFi, + chromeos::network_config::mojom::kNoLimit), + base::BindOnce( + &PhoneHubStructuredMetricsLogger::OnNetworkStateListFetched, + base::Unretained(this))); + } else { + network_state_ = NetworkState::kDifferentNetwork; + } + } + + if (pref_service_->GetInteger(prefs::kPhoneProfileType) != + phone_properties.profile_type()) { + pref_service_->SetInteger(prefs::kPhoneProfileType, + phone_properties.profile_type()); + } + + if (phone_properties.has_locale()) { + if (pref_service_->GetString(prefs::kPhoneLocale).empty() || + pref_service_->GetString(prefs::kPhoneLocale) != + phone_properties.locale()) { + pref_service_->SetString(prefs::kPhoneLocale, phone_properties.locale()); + } + } +} + +void PhoneHubStructuredMetricsLogger::UpdateIdentifiersIfNeeded() { + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + if (pref_service_->GetTime(prefs::kPseudonymousIdRotationDate).is_null() || + pref_service_->GetTime(prefs::kPseudonymousIdRotationDate) <= + base::Time::NowFromSystemTime()) { + ResetCachedInformation(); + } + if (pref_service_->GetString(prefs::kChromebookPseudonymousId).empty()) { + pref_service_->SetString( + prefs::kChromebookPseudonymousId, + base::Uuid::GenerateRandomV4().AsLowercaseString()); + } + if (pref_service_->GetTime(prefs::kPseudonymousIdRotationDate).is_null() || + pref_service_->GetTime(prefs::kPseudonymousIdRotationDate) > + (base::Time::NowFromSystemTime() + + kMaxStructuredMetricsPseudonymousIdDays)) { + pref_service_->SetTime(prefs::kPseudonymousIdRotationDate, + base::Time::NowFromSystemTime() + + kMaxStructuredMetricsPseudonymousIdDays); + } + if (phone_hub_session_id_.empty()) { + phone_hub_session_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString(); + } +} + +void PhoneHubStructuredMetricsLogger::ResetCachedInformation() { + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + phone_network_status_ = std::nullopt; + pref_service_->SetInteger(prefs::kPhoneProfileType, -1); + phone_network_ssid_ = std::nullopt; + pref_service_->SetInt64(prefs::kPhoneGmsCoreVersion, 0); + pref_service_->SetInteger(prefs::kPhoneAndroidVersion, 0); + pref_service_->SetInt64(prefs::kPhoneAmbientApkVersion, 0); + pref_service_->SetTime(prefs::kPhoneInfoLastUpdatedTime, base::Time()); + pref_service_->SetString(prefs::kPhonePseudonymousId, std::string()); + pref_service_->SetString(prefs::kPhoneManufacturer, std::string()); + pref_service_->SetString(prefs::kPhoneModel, std::string()); + pref_service_->SetString(prefs::kPhoneLocale, std::string()); + + network_state_ = NetworkState::kUnknown; + pref_service_->SetString(prefs::kChromebookPseudonymousId, std::string()); + pref_service_->SetTime(prefs::kPseudonymousIdRotationDate, base::Time()); + + ResetSessionId(); +} + +void PhoneHubStructuredMetricsLogger::ResetSessionId() { + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + phone_hub_session_id_ = std::string(); +} + +void PhoneHubStructuredMetricsLogger::SetChromebookInfo( + proto::CrosState& cros_state_message) { + if (!base::FeatureList::IsEnabled( + metrics::structured::kPhoneHubStructuredMetrics)) { + return; + } + + if (!phone_hub_session_id_.empty()) { + cros_state_message.set_phone_hub_session_id(phone_hub_session_id_); + } + if (!pref_service_->GetTime(prefs::kPseudonymousIdRotationDate).is_null()) { + cros_state_message.set_pseudonymous_id_next_rotation_date( + pref_service_->GetTime(prefs::kPseudonymousIdRotationDate) + .InMillisecondsSinceUnixEpoch()); + } +} + +void PhoneHubStructuredMetricsLogger::OnNetworkStateListFetched( + std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr> + networks) { + for (const auto& network : networks) { + if (network->type == chromeos::network_config::mojom::NetworkType::kWiFi) { + std::string hashed_wifi_ssid = + crypto::SHA256HashString(network->type_state->get_wifi()->ssid); + if (phone_network_ssid_.has_value() && + phone_network_ssid_.value() == hashed_wifi_ssid) { + network_state_ = NetworkState::kSameNetwork; + } else { + network_state_ = NetworkState::kDifferentNetwork; + } + return; + } + } + network_state_ = NetworkState::kDifferentNetwork; } } // namespace ash::phonehub
diff --git a/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h index 1e9dd116..dfa182c 100644 --- a/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h +++ b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h
@@ -7,13 +7,23 @@ #include <optional> +#include "base/gtest_prod_util.h" +#include "base/memory/raw_ptr.h" +#include "base/time/time.h" #include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_structured_metrics_logger.h" #include "chromeos/ash/services/secure_channel/public/mojom/nearby_connector.mojom-shared.h" #include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom-shared.h" #include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom.h" +#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" #include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +namespace { +base::TimeDelta kMaxStructuredMetricsPseudonymousIdDays = base::Days(90); +} namespace ash::phonehub { enum class DiscoveryEntryPoint { @@ -42,10 +52,21 @@ kConnected = 2 }; +enum class BluetoothStack { kBlueZ = 0, kFloss = 1 }; + +enum class NetworkState { + kUnknown = 0, + kSameNetwork = 1, + kDifferentNetwork = 2, + kPhoneOnCellular = 3 +}; + class PhoneHubStructuredMetricsLogger : public ash::secure_channel::SecureChannelStructuredMetricsLogger { public: - PhoneHubStructuredMetricsLogger(); + static void RegisterPrefs(PrefRegistrySimple* registry); + + explicit PhoneHubStructuredMetricsLogger(PrefService* pref_service); ~PhoneHubStructuredMetricsLogger() override; PhoneHubStructuredMetricsLogger(const PhoneHubStructuredMetricsLogger&) = @@ -69,6 +90,41 @@ void LogPhoneHubMessageEvent(proto::MessageType message_type, PhoneHubMessageDirection message_direction); void LogPhoneHubUiStateUpdated(PhoneHubUiState ui_state); + + void ProcessPhoneInformation(const proto::PhoneProperties& phone_properties); + + void ResetCachedInformation(); + + void ResetSessionId(); + + void SetChromebookInfo(proto::CrosState& cros_state_message); + + private: + FRIEND_TEST_ALL_PREFIXES(PhoneHubStructuredMetricsLoggerTest, + ProcessPhoneInformation_MissingFields); + FRIEND_TEST_ALL_PREFIXES(PhoneHubStructuredMetricsLoggerTest, + ProcessPhoneInformation_AllFields); + FRIEND_TEST_ALL_PREFIXES(PhoneHubStructuredMetricsLoggerTest, LogEvents); + + void UpdateIdentifiersIfNeeded(); + void OnNetworkStateListFetched( + std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr> + networks); + + // Phone information + std::optional<proto::NetworkStatus> phone_network_status_; + std::optional<std::string> phone_network_ssid_; + + // Chromebook information + BluetoothStack bluetooth_stack_; + NetworkState network_state_ = NetworkState::kUnknown; + std::string chromebook_locale_; + + std::string phone_hub_session_id_; + + mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig> + cros_network_config_; + raw_ptr<PrefService> pref_service_; }; } // namespace ash::phonehub
diff --git a/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger_unittest.cc b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger_unittest.cc new file mode 100644 index 0000000..9959b4b --- /dev/null +++ b/chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger_unittest.cc
@@ -0,0 +1,241 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" + +#include <memory> +#include <optional> + +#include "base/run_loop.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "base/time/time.h" +#include "chromeos/ash/components/phonehub/pref_names.h" +#include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" +#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h" +#include "chromeos/ash/services/secure_channel/public/mojom/nearby_connector.mojom-shared.h" +#include "components/metrics/structured/structured_metrics_features.h" +#include "components/prefs/testing_pref_service.h" +#include "crypto/sha2.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ash::phonehub { + +class PhoneHubStructuredMetricsLoggerTest : public testing::Test { + protected: + PhoneHubStructuredMetricsLoggerTest() = default; + PhoneHubStructuredMetricsLoggerTest( + const PhoneHubStructuredMetricsLoggerTest&) = delete; + PhoneHubStructuredMetricsLoggerTest& operator=( + const PhoneHubStructuredMetricsLoggerTest&) = delete; + ~PhoneHubStructuredMetricsLoggerTest() override = default; + + void SetUp() override { + PhoneHubStructuredMetricsLogger::RegisterPrefs(pref_service_.registry()); + feature_list_.InitWithFeatures( + {metrics::structured::kPhoneHubStructuredMetrics}, {}); + logger_ = std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_); + } + + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + std::unique_ptr<PhoneHubStructuredMetricsLogger> logger_; + TestingPrefServiceSimple pref_service_; + network_config::CrosNetworkConfigTestHelper network_config_test_helper_; + base::test::ScopedFeatureList feature_list_; +}; + +TEST_F(PhoneHubStructuredMetricsLoggerTest, + ProcessPhoneInformation_MissingFields) { + auto phone_property = proto::PhoneProperties(); + phone_property.set_android_version(32); + phone_property.set_gmscore_version(11111111); + phone_property.set_profile_type(proto::ProfileType::DEFAULT_PROFILE); + + logger_->ProcessPhoneInformation(phone_property); + + EXPECT_TRUE( + pref_service_.GetTime(prefs::kPseudonymousIdRotationDate).is_null()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhonePseudonymousId).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneManufacturer).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneModel).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneLocale).empty()); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneGmsCoreVersion), 11111111); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneAndroidVersion), 32); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneProfileType), + proto::ProfileType::DEFAULT_PROFILE); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneAmbientApkVersion), 0); + EXPECT_EQ(logger_->network_state_, NetworkState::kUnknown); +} + +TEST_F(PhoneHubStructuredMetricsLoggerTest, ProcessPhoneInformation_AllFields) { + int id_rotation_time_in_milliseconds = 123456789; + int phone_android_version = 32; + int gms_version = 11111111; + int ambient_version = 1234567; + + auto phone_property = proto::PhoneProperties(); + phone_property.set_android_version(phone_android_version); + phone_property.set_gmscore_version(gms_version); + phone_property.set_profile_type(proto::ProfileType::DEFAULT_PROFILE); + phone_property.set_ambient_version(ambient_version); + phone_property.set_phone_pseudonymous_id("test_uuid"); + phone_property.set_pseudonymous_id_next_rotation_date( + id_rotation_time_in_milliseconds); + phone_property.set_locale("US"); + phone_property.set_phone_manufacturer("google"); + phone_property.set_phone_model("pixle 7"); + phone_property.set_network_status(proto::NetworkStatus::CELLULAR); + + logger_->ProcessPhoneInformation(phone_property); + + EXPECT_EQ(pref_service_.GetTime(prefs::kPseudonymousIdRotationDate), + base::Time::FromMillisecondsSinceUnixEpoch( + id_rotation_time_in_milliseconds)); + EXPECT_EQ(pref_service_.GetString(prefs::kPhonePseudonymousId), "test_uuid"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneManufacturer), "google"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneModel), "pixle 7"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneLocale), "US"); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneGmsCoreVersion), gms_version); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneAndroidVersion), + phone_android_version); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneProfileType), + proto::ProfileType::DEFAULT_PROFILE); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneAmbientApkVersion), + ambient_version); + EXPECT_EQ(logger_->network_state_, NetworkState::kPhoneOnCellular); + + // Simulate phone info update + int update_id_rotation_time_in_milliseconds = 12345678; + int update_phone_android_version = 34; + int update_gms_version = 111111112; + int update_ambient_version = 123456777; + + phone_property.set_android_version(update_phone_android_version); + phone_property.set_gmscore_version(update_gms_version); + phone_property.set_profile_type(proto::ProfileType::DEFAULT_PROFILE); + phone_property.set_ambient_version(update_ambient_version); + phone_property.set_phone_pseudonymous_id("test_uuid_2"); + phone_property.set_pseudonymous_id_next_rotation_date( + update_id_rotation_time_in_milliseconds); + phone_property.set_locale("us"); + phone_property.set_phone_manufacturer("Google"); + phone_property.set_phone_model("Pixle 7"); + phone_property.set_network_status(proto::NetworkStatus::WIFI); + phone_property.set_ssid(crypto::SHA256HashString("WIFI1")); + phone_property.set_profile_type(proto::ProfileType::WORK_PROFILE); + + auto wifi_path = + network_config_test_helper_.network_state_helper().ConfigureService( + R"({"GUID": "WIFI1_guid", "Type": "wifi", "SSID": "WIFI1", + "State": "ready", "Strength": 100, + "Connectable": true})"); + base::RunLoop().RunUntilIdle(); + logger_->ProcessPhoneInformation(phone_property); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(pref_service_.GetTime(prefs::kPseudonymousIdRotationDate), + base::Time::FromMillisecondsSinceUnixEpoch( + update_id_rotation_time_in_milliseconds)); + EXPECT_EQ(pref_service_.GetString(prefs::kPhonePseudonymousId), + "test_uuid_2"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneManufacturer), "Google"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneModel), "Pixle 7"); + EXPECT_EQ(pref_service_.GetString(prefs::kPhoneLocale), "us"); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneGmsCoreVersion), + update_gms_version); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneAndroidVersion), + update_phone_android_version); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneProfileType), + proto::ProfileType::WORK_PROFILE); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneAmbientApkVersion), + update_ambient_version); + EXPECT_EQ(logger_->network_state_, NetworkState::kSameNetwork); + + // Simulate phone wifi change + phone_property.set_ssid(crypto::SHA256HashString("WIFI2")); + logger_->ProcessPhoneInformation(phone_property); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(logger_->network_state_, NetworkState::kDifferentNetwork); +} + +TEST_F(PhoneHubStructuredMetricsLoggerTest, LogEvents) { + logger_->LogPhoneHubDiscoveryStarted( + DiscoveryEntryPoint::kPhoneHubBubbleOpen); + + int id_rotation_time_in_milliseconds = + base::Time::Now().InMillisecondsSinceUnixEpoch() + + 5 * 24 * 60 * 60 * 1000; + int phone_android_version = 32; + int gms_version = 11111111; + int ambient_version = 1234567; + auto phone_property = proto::PhoneProperties(); + phone_property.set_android_version(phone_android_version); + phone_property.set_gmscore_version(gms_version); + phone_property.set_profile_type(proto::ProfileType::DEFAULT_PROFILE); + phone_property.set_ambient_version(ambient_version); + phone_property.set_phone_pseudonymous_id("test_uuid"); + phone_property.set_pseudonymous_id_next_rotation_date( + id_rotation_time_in_milliseconds); + phone_property.set_locale("US"); + phone_property.set_phone_manufacturer("google"); + phone_property.set_phone_model("pixle 7"); + phone_property.set_network_status(proto::NetworkStatus::CELLULAR); + + logger_->ProcessPhoneInformation(phone_property); + + std::string chromebook_pseudonymouse_id = + pref_service_.GetString(prefs::kChromebookPseudonymousId); + EXPECT_FALSE(chromebook_pseudonymouse_id.empty()); + std::string phone_hub_session_id = logger_->phone_hub_session_id_; + EXPECT_FALSE(phone_hub_session_id.empty()); + + logger_->LogDiscoveryAttempt(secure_channel::mojom::DiscoveryResult::kSuccess, + std::nullopt); + EXPECT_EQ(chromebook_pseudonymouse_id, + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_EQ(phone_hub_session_id, logger_->phone_hub_session_id_); + + logger_->LogNearbyConnectionState( + secure_channel::mojom::NearbyConnectionStep::kDisconnectionStarted, + secure_channel::mojom::NearbyConnectionStepResult::kSuccess); + EXPECT_EQ(chromebook_pseudonymouse_id, + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_EQ(phone_hub_session_id, logger_->phone_hub_session_id_); + + logger_->LogSecureChannelState( + secure_channel::mojom::SecureChannelState::kAuthenticationSuccess); + EXPECT_EQ(chromebook_pseudonymouse_id, + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_EQ(phone_hub_session_id, logger_->phone_hub_session_id_); + + logger_->LogPhoneHubUiStateUpdated(PhoneHubUiState::kConnected); + EXPECT_EQ(chromebook_pseudonymouse_id, + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_EQ(phone_hub_session_id, logger_->phone_hub_session_id_); + + logger_->LogPhoneHubMessageEvent( + proto::MessageType::PHONE_STATUS_SNAPSHOT, + PhoneHubMessageDirection::kPhoneToChromebook); + EXPECT_EQ(chromebook_pseudonymouse_id, + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_EQ(phone_hub_session_id, logger_->phone_hub_session_id_); + + task_environment_.FastForwardBy(base::Days(6)); + logger_->LogDiscoveryAttempt(secure_channel::mojom::DiscoveryResult::kSuccess, + std::nullopt); + EXPECT_FALSE(chromebook_pseudonymouse_id == + pref_service_.GetString(prefs::kChromebookPseudonymousId)); + EXPECT_FALSE(phone_hub_session_id == logger_->phone_hub_session_id_); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhonePseudonymousId).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneManufacturer).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneModel).empty()); + EXPECT_TRUE(pref_service_.GetString(prefs::kPhoneLocale).empty()); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneGmsCoreVersion), 0); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneAndroidVersion), 0); + EXPECT_EQ(pref_service_.GetInteger(prefs::kPhoneProfileType), -1); + EXPECT_EQ(pref_service_.GetInt64(prefs::kPhoneAmbientApkVersion), 0); + EXPECT_EQ(logger_->network_state_, NetworkState::kUnknown); +} + +} // namespace ash::phonehub
diff --git a/chromeos/ash/components/phonehub/phone_status_processor.cc b/chromeos/ash/components/phonehub/phone_status_processor.cc index c3f7432..daf4a55 100644 --- a/chromeos/ash/components/phonehub/phone_status_processor.cc +++ b/chromeos/ash/components/phonehub/phone_status_processor.cc
@@ -23,6 +23,7 @@ #include "chromeos/ash/components/phonehub/multidevice_feature_access_manager.h" #include "chromeos/ash/components/phonehub/mutable_phone_model.h" #include "chromeos/ash/components/phonehub/notification_processor.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/phone_hub_ui_readiness_recorder.h" #include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" #include "chromeos/ash/components/phonehub/recent_apps_interaction_handler.h" @@ -231,7 +232,8 @@ AppStreamManager* app_stream_manager, AppStreamLauncherDataModel* app_stream_launcher_data_model, IconDecoder* icon_decoder, - PhoneHubUiReadinessRecorder* phone_hub_ui_readiness_recorder) + PhoneHubUiReadinessRecorder* phone_hub_ui_readiness_recorder, + PhoneHubStructuredMetricsLogger* phone_hub_structured_metrics_logger) : do_not_disturb_controller_(do_not_disturb_controller), feature_status_provider_(feature_status_provider), message_receiver_(message_receiver), @@ -246,7 +248,9 @@ app_stream_manager_(app_stream_manager), app_stream_launcher_data_model_(app_stream_launcher_data_model), icon_decoder_(icon_decoder), - phone_hub_ui_readiness_recorder_(phone_hub_ui_readiness_recorder) { + phone_hub_ui_readiness_recorder_(phone_hub_ui_readiness_recorder), + phone_hub_structured_metrics_logger_( + phone_hub_structured_metrics_logger) { DCHECK(do_not_disturb_controller_); DCHECK(feature_status_provider_); DCHECK(message_receiver_); @@ -259,6 +263,7 @@ DCHECK(app_stream_manager_); DCHECK(icon_decoder_); DCHECK(phone_hub_ui_readiness_recorder_); + DCHECK(phone_hub_structured_metrics_logger_); message_receiver_->AddObserver(this); feature_status_provider_->AddObserver(this); @@ -304,6 +309,8 @@ void PhoneStatusProcessor::SetReceivedPhoneStatusModelStates( const proto::PhoneProperties& phone_properties) { + phone_hub_structured_metrics_logger_->ProcessPhoneInformation( + phone_properties); phone_model_->SetPhoneStatusModel(CreatePhoneStatusModel(phone_properties)); do_not_disturb_controller_->SetDoNotDisturbStateInternal(
diff --git a/chromeos/ash/components/phonehub/phone_status_processor.h b/chromeos/ash/components/phonehub/phone_status_processor.h index 70dc4ec..5358807 100644 --- a/chromeos/ash/components/phonehub/phone_status_processor.h +++ b/chromeos/ash/components/phonehub/phone_status_processor.h
@@ -13,6 +13,7 @@ #include "chromeos/ash/components/phonehub/feature_status_provider.h" #include "chromeos/ash/components/phonehub/icon_decoder.h" #include "chromeos/ash/components/phonehub/message_receiver.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/proto/phonehub_api.pb.h" #include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client.h" @@ -62,7 +63,8 @@ AppStreamManager* app_stream_manager, AppStreamLauncherDataModel* app_stream_launcher_data_model, IconDecoder* icon_decoder_, - PhoneHubUiReadinessRecorder* phone_hub_ui_readiness_recorder); + PhoneHubUiReadinessRecorder* phone_hub_ui_readiness_recorder, + PhoneHubStructuredMetricsLogger* phone_hub_structured_metrics_logger); ~PhoneStatusProcessor() override; PhoneStatusProcessor(const PhoneStatusProcessor&) = delete; @@ -127,6 +129,7 @@ raw_ptr<AppStreamLauncherDataModel> app_stream_launcher_data_model_; raw_ptr<IconDecoder> icon_decoder_; raw_ptr<PhoneHubUiReadinessRecorder> phone_hub_ui_readiness_recorder_; + raw_ptr<PhoneHubStructuredMetricsLogger> phone_hub_structured_metrics_logger_; base::TimeTicks connection_initialized_timestamp_ = base::TimeTicks(); bool has_received_first_app_list_update_ = false;
diff --git a/chromeos/ash/components/phonehub/phone_status_processor_unittest.cc b/chromeos/ash/components/phonehub/phone_status_processor_unittest.cc index 14246f59..ba04f59 100644 --- a/chromeos/ash/components/phonehub/phone_status_processor_unittest.cc +++ b/chromeos/ash/components/phonehub/phone_status_processor_unittest.cc
@@ -33,6 +33,7 @@ #include "chromeos/ash/components/phonehub/mutable_phone_model.h" #include "chromeos/ash/components/phonehub/notification_manager.h" #include "chromeos/ash/components/phonehub/notification_processor.h" +#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h" #include "chromeos/ash/components/phonehub/phone_hub_ui_readiness_recorder.h" #include "chromeos/ash/components/phonehub/phone_model_test_util.h" #include "chromeos/ash/components/phonehub/phone_status_model.h" @@ -175,6 +176,8 @@ std::make_unique<PhoneHubUiReadinessRecorder>( fake_feature_status_provider_.get(), fake_connection_manager_.get()); + phone_hub_structured_metrics_logger_ = + std::make_unique<PhoneHubStructuredMetricsLogger>(&pref_service_); multidevice_setup::RegisterFeaturePrefs(pref_service_.registry()); } @@ -189,7 +192,8 @@ fake_multidevice_setup_client_.get(), mutable_phone_model_.get(), fake_recent_apps_interaction_handler_.get(), &pref_service_, &app_stream_manager_, app_stream_launcher_data_model_.get(), - icon_decoder_.get(), phone_hub_ui_readiness_recorder_.get()); + icon_decoder_.get(), phone_hub_ui_readiness_recorder_.get(), + phone_hub_structured_metrics_logger_.get()); } void InitializeNotificationProto(proto::Notification* notification, @@ -246,6 +250,8 @@ std::unique_ptr<secure_channel::FakeConnectionManager> fake_connection_manager_; std::unique_ptr<PhoneHubUiReadinessRecorder> phone_hub_ui_readiness_recorder_; + std::unique_ptr<PhoneHubStructuredMetricsLogger> + phone_hub_structured_metrics_logger_; raw_ptr<TestDecoderDelegate> decoder_delegate_; TestingPrefServiceSimple pref_service_; AppStreamManager app_stream_manager_;
diff --git a/chromeos/ash/components/phonehub/pref_names.cc b/chromeos/ash/components/phonehub/pref_names.cc index 1fa97be..222434d 100644 --- a/chromeos/ash/components/phonehub/pref_names.cc +++ b/chromeos/ash/components/phonehub/pref_names.cc
@@ -70,6 +70,22 @@ const char kFeatureSetupRequestSupported[] = "cros.phonehub.feature_setup_request_supported"; +const char kPhoneManufacturer[] = "cros.phonehub.phone_manufacturer"; +const char kPhoneModel[] = "cros.phonehub.phone_model"; +const char kPhoneLocale[] = "cros.phonehub.phone_locale"; +const char kPhonePseudonymousId[] = "cros.phonehub.phone_pseudonymous_id"; +const char kPhoneAndroidVersion[] = "cros.phonehub.phone_android_version"; +const char kPhoneGmsCoreVersion[] = "cros.phonehub.phone_gms_core_version"; +const char kPhoneAmbientApkVersion[] = + "cros.phonehub.phone_ambient_apk_version"; +const char kPhoneProfileType[] = "cros.phonehub.phone_profile_type"; +const char kPhoneInfoLastUpdatedTime[] = + "cros.phonehub.phone_info_last_updated_time"; +const char kChromebookPseudonymousId[] = + "cros.phonehub.chromebook_pseudonymous_id"; +const char kPseudonymousIdRotationDate[] = + "cros.phonehub.pseudonymous_id_rotation_date"; + } // namespace prefs } // namespace phonehub } // namespace ash
diff --git a/chromeos/ash/components/phonehub/pref_names.h b/chromeos/ash/components/phonehub/pref_names.h index 341645af..94006773 100644 --- a/chromeos/ash/components/phonehub/pref_names.h +++ b/chromeos/ash/components/phonehub/pref_names.h
@@ -20,6 +20,19 @@ extern const char kRecentAppsHistory[]; extern const char kFeatureSetupRequestSupported[]; +// Connected phone information used by Phone Hub Structured Metrics +extern const char kPhoneManufacturer[]; +extern const char kPhoneModel[]; +extern const char kPhoneLocale[]; +extern const char kPhonePseudonymousId[]; +extern const char kPhoneAndroidVersion[]; +extern const char kPhoneGmsCoreVersion[]; +extern const char kPhoneAmbientApkVersion[]; +extern const char kPhoneProfileType[]; +extern const char kPhoneInfoLastUpdatedTime[]; +extern const char kChromebookPseudonymousId[]; +extern const char kPseudonymousIdRotationDate[]; + } // namespace prefs } // namespace phonehub } // namespace ash
diff --git a/chromeos/ash/components/phonehub/proto/phonehub_api.proto b/chromeos/ash/components/phonehub/proto/phonehub_api.proto index d77cf00..0ac84862 100644 --- a/chromeos/ash/components/phonehub/proto/phonehub_api.proto +++ b/chromeos/ash/components/phonehub/proto/phonehub_api.proto
@@ -187,6 +187,12 @@ bool ping_capability_supported = 2; } +enum NetworkStatus { + DISCONNECTED = 0; + CELLULAR = 1; + WIFI = 2; +} + message PhoneProperties { int32 battery_percentage = 1; ChargingState charging_state = 2; @@ -224,7 +230,17 @@ FeatureStatus eche_feature_status = 20; - // Next ID: 21 + // Used for Phone Hub Structured Metrics + optional string phone_manufacturer = 21; + optional string phone_model = 22; + optional int64 ambient_version = 23; + optional NetworkStatus network_status = 24; + optional bytes ssid = 25; + optional string locale = 26; + optional string phone_pseudonymous_id = 27; + optional int64 pseudonymous_id_next_rotation_date = 28; + + // Next ID: 29 } message UserState { @@ -274,6 +290,9 @@ // Request Eche feature status info from the phone through Phone hub. bool should_provide_eche_status = 5; + optional int64 pseudonymous_id_next_rotation_date = 6; + optional string phone_hub_session_id = 7; + reserved 3; // deprecated notification_icon_styling field. }
diff --git a/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc b/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc index 4e379da4..a837792 100644 --- a/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc +++ b/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc
@@ -60,6 +60,10 @@ case device::ConnectionFailureReason::kNotConnectable: [[fallthrough]]; case device::ConnectionFailureReason::kInprogress: + [[fallthrough]]; + case device::ConnectionFailureReason::kNotFound: + [[fallthrough]]; + case device::ConnectionFailureReason::kBluetoothDisabled: return mojom::PairingResult::kNonAuthFailure; } } @@ -190,7 +194,8 @@ if (!IsBluetoothEnabled()) { BLUETOOTH_LOG(ERROR) << "Pairing failed due to Bluetooth not being " << "enabled, device identifier: " << device_id; - FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed); + FinishCurrentPairingRequest( + device::ConnectionFailureReason::kBluetoothDisabled); return; }
diff --git a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl.cc b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl.cc index cabf744..33fa77e3 100644 --- a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl.cc +++ b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl.cc
@@ -136,7 +136,7 @@ << "Could not cancel pairing for device to due device no longer being " "found, identifier: " << current_pairing_device_id(); - FinishCurrentPairingRequest(device::ConnectionFailureReason::kAuthFailed); + FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound); return; } @@ -151,7 +151,7 @@ BLUETOOTH_LOG(ERROR) << "OnRequestPinCode failed due to device no longer being " << "found, identifier: " << current_pairing_device_id(); - FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed); + FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound); return; } @@ -166,7 +166,7 @@ BLUETOOTH_LOG(ERROR) << "OnRequestPasskey failed due to device no longer being " << "found, identifier: " << current_pairing_device_id(); - FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed); + FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound); return; } @@ -194,7 +194,7 @@ BLUETOOTH_LOG(ERROR) << "OnConfirmPairing failed due to device no longer being " << "found, identifier: " << current_pairing_device_id(); - FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed); + FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound); return; }
diff --git a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc index 13ce66d..a9ee8a8 100644 --- a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc +++ b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
@@ -415,8 +415,9 @@ CheckPairingHistograms(device::BluetoothTransportType::kInvalid, /*type_count=*/1, /*failure_count=*/1, /*success_count=*/0); - CheckPairingFailureHistogram(device::ConnectionFailureReason::kFailed, - /*failure_count=*/1, /*filtered_count=*/1); + CheckPairingFailureHistogram( + device::ConnectionFailureReason::kBluetoothDisabled, + /*failure_count=*/1, /*filtered_count=*/1); CheckDurationHistogramMetrics(base::Milliseconds(0), /*success_count=*/0, /*failure_count=*/1, /*transport_name=*/"Invalid"); @@ -567,11 +568,11 @@ // CancelPairing() won't be called since the device won't be found. We should // still return with a pairing result. EXPECT_EQ(num_cancel_pairing_calls(), 0u); - EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed); + EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure); CheckPairingHistograms(device::BluetoothTransportType::kInvalid, /*type_count=*/1, /*failure_count=*/1, /*success_count=*/0); - CheckPairingFailureHistogram(device::ConnectionFailureReason::kAuthFailed, + CheckPairingFailureHistogram(device::ConnectionFailureReason::kNotFound, /*failure_count=*/1, /*filtered_count=*/1); CheckDurationHistogramMetrics(kTestDuration, /*success_count=*/0, /*failure_count=*/1, @@ -737,7 +738,7 @@ CheckPairingHistograms(device::BluetoothTransportType::kInvalid, /*type_count=*/1, /*failure_count=*/1, /*success_count=*/0); - CheckPairingFailureHistogram(device::ConnectionFailureReason::kFailed, + CheckPairingFailureHistogram(device::ConnectionFailureReason::kNotFound, /*failure_count=*/1, /*filtered_count=*/1); CheckDurationHistogramMetrics(kTestDuration, /*success_count=*/0, /*failure_count=*/1, @@ -787,7 +788,7 @@ CheckPairingHistograms(device::BluetoothTransportType::kInvalid, /*type_count=*/1, /*failure_count=*/1, /*success_count=*/0); - CheckPairingFailureHistogram(device::ConnectionFailureReason::kFailed, + CheckPairingFailureHistogram(device::ConnectionFailureReason::kNotFound, /*failure_count=*/1, /*filtered_count=*/1); CheckDurationHistogramMetrics(kTestDuration, /*success_count=*/0, /*failure_count=*/1, @@ -1048,7 +1049,7 @@ CheckPairingHistograms(device::BluetoothTransportType::kInvalid, /*type_count=*/1, /*failure_count=*/1, /*success_count=*/0); - CheckPairingFailureHistogram(device::ConnectionFailureReason::kFailed, + CheckPairingFailureHistogram(device::ConnectionFailureReason::kNotFound, /*failure_count=*/1, /*filtered_count=*/1); CheckDurationHistogramMetrics(kTestDuration, /*success_count=*/0, /*failure_count=*/1,
diff --git a/clank b/clank index 29f7853..f022452 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit 29f78538308b953b929c2d0fa89c036d889ad135 +Subproject commit f022452f20012a03b32a2db96eeda986d644287f
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc index 21e7d38b..39c0408 100644 --- a/components/omnibox/browser/autocomplete_match.cc +++ b/components/omnibox/browser/autocomplete_match.cc
@@ -1400,6 +1400,11 @@ shortcut_boosted) { return 1; } + // IPH message always appears at the bottom of the Omnibox, after all other + // suggestions. + if (type == AutocompleteMatchType::NULL_RESULT_MESSAGE) { + return 4; + } return 3; }
diff --git a/components/omnibox/browser/featured_search_provider.cc b/components/omnibox/browser/featured_search_provider.cc index 30c0890..105ecfc 100644 --- a/components/omnibox/browser/featured_search_provider.cc +++ b/components/omnibox/browser/featured_search_provider.cc
@@ -47,6 +47,13 @@ } DoStarterPackAutocompletion(input); + + // TODO(crbug.com/333762301): Implement smarter triggering for the IPH match. + // As is, the IPH message will always be displayed. This might make sense to + // move to the ZPS provider. + if (OmniboxFieldTrial::IsStarterPackIPHEnabled()) { + AddIPHMatch(); + } } FeaturedSearchProvider::~FeaturedSearchProvider() = default; @@ -148,3 +155,19 @@ } matches_.push_back(match); } + +void FeaturedSearchProvider::AddIPHMatch() { + // This value doesn't really matter as this suggestion is grouped after all + // other suggestions. Use an arbitrary constant. + constexpr int kRelevanceScore = 1000; + AutocompleteMatch match(this, kRelevanceScore, /*deletable=*/false, + AutocompleteMatchType::NULL_RESULT_MESSAGE); + + // Use this suggestion's contents field to display a message to the user that + // cannot be acted upon. + match.contents = l10n_util::GetStringUTF16(IDS_OMNIBOX_GEMINI_IPH); + match.contents_class.emplace_back(0, ACMatchClassification::NONE); + match.from_keyword = true; + + matches_.push_back(match); +}
diff --git a/components/omnibox/browser/featured_search_provider.h b/components/omnibox/browser/featured_search_provider.h index d3f8ebc1..30d2204 100644 --- a/components/omnibox/browser/featured_search_provider.h +++ b/components/omnibox/browser/featured_search_provider.h
@@ -40,6 +40,11 @@ void AddStarterPackMatch(const TemplateURL& template_url, const AutocompleteInput& input); + // Constructs a NULL_RESULT_MESSAGE match that is informational only and + // cannot be acted upon. This match delivers an IPH message directing users + // to the starter pack feature. + void AddIPHMatch(); + raw_ptr<AutocompleteProviderClient> client_; raw_ptr<TemplateURLService> template_url_service_; };
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc index d71d6f49..33c54bf8 100644 --- a/components/omnibox/browser/omnibox_field_trial.cc +++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -1133,6 +1133,10 @@ bool IsStarterPackExpansionEnabled() { return base::FeatureList::IsEnabled(omnibox::kStarterPackExpansion); } + +bool IsStarterPackIPHEnabled() { + return base::FeatureList::IsEnabled(omnibox::kStarterPackIPH); +} // <- Site Search Starter Pack // ---------------------------------------------------------
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h index b3a4a4f..25a3d984 100644 --- a/components/omnibox/browser/omnibox_field_trial.h +++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -779,6 +779,11 @@ // Whether the expansion pack for the site search starter pack is enabled. bool IsStarterPackExpansionEnabled(); + +// When true, enables an informational IPH message at the bottom of the Omnibox +// directing users to certain starter pack engines. +bool IsStarterPackIPHEnabled(); + // <- Site Search Starter Pack // ---------------------------------------------------------
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc index 00aabe1..eed29101 100644 --- a/components/omnibox/common/omnibox_features.cc +++ b/components/omnibox/common/omnibox_features.cc
@@ -551,6 +551,12 @@ "StarterPackExpansion", base::FEATURE_DISABLED_BY_DEFAULT); +// Enables an informational IPH message at the bottom of the Omnibox directing +// users to certain starter pack engines. +BASE_FEATURE(kStarterPackIPH, + "StarterPackIPH", + base::FEATURE_DISABLED_BY_DEFAULT); + // If enabled, |SearchProvider| will not function in Zero Suggest. BASE_FEATURE(kAblateSearchProviderWarmup, "AblateSearchProviderWarmup",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h index 6e12f6b..32b55e9 100644 --- a/components/omnibox/common/omnibox_features.h +++ b/components/omnibox/common/omnibox_features.h
@@ -164,6 +164,7 @@ BASE_DECLARE_FEATURE(kSiteSearchSettingsPolicy); BASE_DECLARE_FEATURE(kPolicyIndicationForManagedDefaultSearch); BASE_DECLARE_FEATURE(kStarterPackExpansion); +BASE_DECLARE_FEATURE(kStarterPackIPH); // Search and Suggest requests and params. BASE_DECLARE_FEATURE(kAblateSearchProviderWarmup);
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp index 3e34a9a..91b25b8 100644 --- a/components/omnibox_strings.grdp +++ b/components/omnibox_strings.grdp
@@ -300,6 +300,10 @@ No results found </message> + <message name="IDS_OMNIBOX_GEMINI_IPH" desc = "The string displayed as the last row in the Omnibox as IPH directing users to the @gemini starter pack."> + Type @gemini to Chat with Gemini + </message> + <message name="IDS_OMNIBOX_ONE_LINE_CALCULATOR_SUGGESTION_TEMPLATE" desc = "The string displayed when a calculator answer is suggested."> <ph name="EXPRESSION">$1</ph> = <ph name="ANSWER">$2</ph> </message>
diff --git a/components/omnibox_strings_grdp/IDS_OMNIBOX_GEMINI_IPH.png.sha1 b/components/omnibox_strings_grdp/IDS_OMNIBOX_GEMINI_IPH.png.sha1 new file mode 100644 index 0000000..6a943c4e7 --- /dev/null +++ b/components/omnibox_strings_grdp/IDS_OMNIBOX_GEMINI_IPH.png.sha1
@@ -0,0 +1 @@ +c4db86b58a21d4d9450080e885e6aef6c41ce7ae \ No newline at end of file
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc index 9c585a4..3323da2 100644 --- a/components/optimization_guide/core/model_execution/session_impl.cc +++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -658,11 +658,11 @@ proto::OnDeviceModelServiceResponse* logged_response = on_device_state_->MutableLoggedResponse(); - std::string current_response = on_device_state_->current_response; - logged_response->set_output_string(current_response); + logged_response->set_output_string(on_device_state_->current_response); + std::string redacted_response = on_device_state_->current_response; auto redact_result = - on_device_state_->opts.adapter->Redact(*last_message_, current_response); + on_device_state_->opts.adapter->Redact(*last_message_, redacted_response); if (redact_result == RedactResult::kReject) { logged_response->set_status( proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED); @@ -699,8 +699,8 @@ } } - auto output = - on_device_state_->opts.adapter->ConstructOutputMetadata(current_response); + auto output = on_device_state_->opts.adapter->ConstructOutputMetadata( + redacted_response); if (!output) { CancelPendingResponse( ExecuteModelResult::kFailedConstructingResponseMessage,
diff --git a/components/safe_browsing/core/browser/ping_manager.cc b/components/safe_browsing/core/browser/ping_manager.cc index 28120c7c..26b68da 100644 --- a/components/safe_browsing/core/browser/ping_manager.cc +++ b/components/safe_browsing/core/browser/ping_manager.cc
@@ -6,11 +6,13 @@ #include <memory> #include <utility> +#include <vector> #include "base/base64url.h" #include "base/check.h" #include "base/containers/contains.h" #include "base/containers/fixed_flat_set.h" +#include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/functional/callback.h" @@ -39,6 +41,11 @@ namespace { +using WriteResult = safe_browsing::PingManager::Persister::WriteResult; + +// Delay before reading persisted reports at startup. +base::TimeDelta kReadPersistedReportsDelay = base::Seconds(15); + GURL GetSanitizedUrl(const GURL& url) { GURL::Replacements replacements; replacements.ClearUsername(); @@ -61,6 +68,10 @@ DANGEROUS_DOWNLOAD_BY_API: case safe_browsing::ClientSafeBrowsingReportRequest:: DANGEROUS_DOWNLOAD_OPENED: + case safe_browsing::ClientSafeBrowsingReportRequest:: + DANGEROUS_DOWNLOAD_AUTO_DELETED: + case safe_browsing::ClientSafeBrowsingReportRequest:: + DANGEROUS_DOWNLOAD_PROFILE_CLOSED: return true; default: return false; @@ -72,6 +83,12 @@ base::RandGenerator(std::numeric_limits<uint64_t>::max())); } +void RecordPersisterWriteResult(WriteResult write_result) { + base::UmaHistogramEnumeration( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterWriteResult", + write_result); +} + const net::NetworkTrafficAnnotationTag kTrafficAnnotation = net::DefineNetworkTrafficAnnotation("safe_browsing_extended_reporting", R"( @@ -122,10 +139,41 @@ void PingManager::Persister::WriteReport(const std::string& serialized_report) { base::File::Error error; if (!base::CreateDirectoryAndGetError(dir_path_, &error)) { + RecordPersisterWriteResult(WriteResult::kFailedCreateDirectory); return; } base::FilePath file_path = dir_path_.AppendASCII((GetRandFileName())); - base::WriteFile(file_path, serialized_report); + bool success = base::WriteFile(file_path, serialized_report); + RecordPersisterWriteResult(success ? WriteResult::kSuccess + : WriteResult::kFailedWriteFile); +} + +std::vector<std::string> PingManager::Persister::ReadAndDeleteReports() { + if (!base::DirectoryExists(dir_path_)) { + return {}; + } + base::FileEnumerator directory_enumerator(dir_path_, + /*recursive=*/false, + base::FileEnumerator::FILES); + std::vector<std::string> persisted_reports; + for (base::FilePath file_name = directory_enumerator.Next(); + !file_name.empty(); file_name = directory_enumerator.Next()) { + std::string persisted_report; + bool success = base::ReadFileToString(file_name, &persisted_report); + base::UmaHistogramBoolean( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterReadReportSuccessful", + success); + if (success) { + persisted_reports.emplace_back(std::move(persisted_report)); + } + } + // Since persisted reports are uncommon, delete the directory so that we don't + // leave an empty directory going forward. + base::DeletePathRecursively(dir_path_); + base::UmaHistogramCounts1000( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterReportCountOnStartup", + persisted_reports.size()); + return persisted_reports; } // SafeBrowsingPingManager implementation ---------------------------------- @@ -143,12 +191,14 @@ base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)> get_page_load_token_callback, std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate, - const base::FilePath& persister_root_path) { + const base::FilePath& persister_root_path, + base::RepeatingCallback<bool()> get_should_send_persisted_report) { return std::make_unique<PingManager>( config, url_loader_factory, std::move(token_fetcher), get_should_fetch_access_token, webui_delegate, ui_task_runner, get_user_population_callback, get_page_load_token_callback, - std::move(hats_delegate), persister_root_path); + std::move(hats_delegate), persister_root_path, + std::move(get_should_send_persisted_report)); } PingManager::PingManager( @@ -163,7 +213,8 @@ base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)> get_page_load_token_callback, std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate, - const base::FilePath& persister_root_path) + const base::FilePath& persister_root_path, + base::RepeatingCallback<bool()> get_should_send_persisted_report) : config_(config), url_loader_factory_(url_loader_factory), token_fetcher_(std::move(token_fetcher)), @@ -172,14 +223,21 @@ ui_task_runner_(ui_task_runner), get_user_population_callback_(get_user_population_callback), get_page_load_token_callback_(get_page_load_token_callback), - hats_delegate_(std::move(hats_delegate)) { + hats_delegate_(std::move(hats_delegate)), + get_should_send_persisted_report_( + std::move(get_should_send_persisted_report)) { persister_ = base::SequenceBound<Persister>( base::ThreadPool::CreateSequencedTaskRunner( {base::MayBlock(), base::TaskPriority::BEST_EFFORT, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}), persister_root_path); - // TODO(crbug.com/329471668): Schedule tasks to read reports from persister - // and send them. + // Post this task with a delay to avoid running right at Chrome startup + // when a lot of other startup tasks are running. + ui_task_runner_->PostDelayedTask( + FROM_HERE, + base::BindOnce(&PingManager::ReadPersistedReports, + weak_factory_.GetWeakPtr()), + kReadPersistedReportsDelay); } PingManager::~PingManager() {} @@ -314,6 +372,29 @@ return PersistThreatDetailsResult::kPersistTaskPosted; } +void PingManager::ReadPersistedReports() { + persister_.AsyncCall(&PingManager::Persister::ReadAndDeleteReports) + .Then(base::BindOnce(&PingManager::OnReadPersistedReportsDone, + weak_factory_.GetWeakPtr())); +} + +void PingManager::OnReadPersistedReportsDone( + std::vector<std::string> serialized_reports) { + CHECK(!get_should_send_persisted_report_.is_null()); + if (!get_should_send_persisted_report_.Run()) { + return; + } + for (const std::string& seralized_report : serialized_reports) { + if (seralized_report.empty()) { + continue; + } + auto report = std::make_unique<ClientSafeBrowsingReportRequest>(); + if (report->ParseFromString(seralized_report)) { + ReportThreatDetails(std::move(report)); + } + } +} + void PingManager::AttachThreatDetailsAndLaunchSurvey( std::unique_ptr<ClientSafeBrowsingReportRequest> report) { // Return early if HaTS survey is disabled by policy.
diff --git a/components/safe_browsing/core/browser/ping_manager.h b/components/safe_browsing/core/browser/ping_manager.h index d8b5341..ab8cf16 100644 --- a/components/safe_browsing/core/browser/ping_manager.h +++ b/components/safe_browsing/core/browser/ping_manager.h
@@ -42,6 +42,8 @@ EMPTY_REPORT = 2, }; + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. enum class PersistThreatDetailsResult { // The task to persist the report has posted. The actual file write // operation may still fail. @@ -50,6 +52,7 @@ kSerializationError = 1, // The report is empty, so it is not sent. kEmptyReport = 2, + kMaxValue = kEmptyReport, }; // Interface via which a client of this class can surface relevant events in @@ -69,6 +72,15 @@ // Helper class to read/write a report on disk. class Persister { public: + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum class WriteResult { + kSuccess = 0, + kFailedCreateDirectory = 1, + kFailedWriteFile = 2, + kMaxValue = kFailedWriteFile, + }; + explicit Persister(const base::FilePath& persister_root_path); Persister(const Persister&) = delete; Persister& operator=(const Persister&) = delete; @@ -78,6 +90,11 @@ // Writes |serialized_report| to a new file in |dir_path_|. void WriteReport(const std::string& serialized_report); + // Reads all persisted reports in |dir_path_|. The reports are deleted + // regardless of whether the read was successful or not. + // Returns a list of string representation of the reports. + std::vector<std::string> ReadAndDeleteReports(); + private: // The directory that the files will be written in. base::FilePath dir_path_; @@ -95,7 +112,8 @@ base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)> get_page_load_token_callback, std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate, - const base::FilePath& persister_root_path); + const base::FilePath& persister_root_path, + base::RepeatingCallback<bool()> get_should_send_persisted_report); PingManager(const PingManager&) = delete; PingManager& operator=(const PingManager&) = delete; @@ -114,7 +132,8 @@ base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)> get_page_load_token_callback, std::unique_ptr<SafeBrowsingHatsDelegate> hats_delegate, - const base::FilePath& persister_root_path); + const base::FilePath& persister_root_path, + base::RepeatingCallback<bool()> get_should_send_persisted_report); void OnURLLoaderComplete(network::SimpleURLLoader* source, std::unique_ptr<std::string> response_body); @@ -187,6 +206,12 @@ void ReportThreatDetailsOnGotAccessToken(const std::string& serialized_report, const std::string& access_token); + // Reads persisted reports from disk. + void ReadPersistedReports(); + + // Sends `serialized_reports` to Safe Browsing. + void OnReadPersistedReportsDone(std::vector<std::string> serialized_reports); + // Track outstanding SafeBrowsing report fetchers for clean up. // We add both "hit" and "detail" fetchers in this set. Reports safebrowsing_reports_; @@ -221,6 +246,9 @@ base::SequenceBound<Persister> persister_; + // Determines whether the user has opted in to send persisted reports. + base::RepeatingCallback<bool()> get_should_send_persisted_report_; + base::WeakPtrFactory<PingManager> weak_factory_{this}; };
diff --git a/components/safe_browsing/core/browser/ping_manager_unittest.cc b/components/safe_browsing/core/browser/ping_manager_unittest.cc index ff4b5d5..5019f4b 100644 --- a/components/safe_browsing/core/browser/ping_manager_unittest.cc +++ b/components/safe_browsing/core/browser/ping_manager_unittest.cc
@@ -76,14 +76,21 @@ get_user_population_callback, std::optional< base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>> - get_page_load_token_callback); + get_page_load_token_callback, + std::optional<base::RepeatingCallback<bool()>> + get_should_send_persisted_report); void SetUpFeatureList(bool should_enable_remove_cookies); + // Returns a copy of the serialized persisted report that can be used to + // verify the data sent through URL loader. + std::string CallPersistThreatDetails(const std::string& url); - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; std::string key_param_; std::unique_ptr<MockWebUIDelegate> webui_delegate_ = std::make_unique<MockWebUIDelegate>(); base::FilePath persister_root_path_; + base::FilePath persister_dir_; FakeSafeBrowsingHatsDelegate* SetUpHatsDelegate(); private: @@ -100,7 +107,8 @@ "&key=%s", base::EscapeQueryParamValue(key, true).c_str()); } persister_root_path_ = base::CreateUniqueTempDirectoryScopedToTest(); - SetNewPingManager(std::nullopt, std::nullopt, std::nullopt); + persister_dir_ = persister_root_path_.AppendASCII("DownloadReports"); + SetNewPingManager(std::nullopt, std::nullopt, std::nullopt, std::nullopt); } void PingManagerTest::TearDown() { @@ -119,7 +127,9 @@ get_user_population_callback, std::optional< base::RepeatingCallback<ChromeUserPopulation::PageLoadToken(GURL)>> - get_page_load_token_callback) { + get_page_load_token_callback, + std::optional<base::RepeatingCallback<bool()>> + get_should_send_persisted_report) { ping_manager_.reset(new PingManager( safe_browsing::GetTestV4ProtocolConfig(), nullptr, nullptr, get_should_fetch_access_token.value_or( @@ -127,7 +137,9 @@ webui_delegate_.get(), base::SequencedTaskRunner::GetCurrentDefault(), get_user_population_callback.value_or(base::NullCallback()), get_page_load_token_callback.value_or(base::NullCallback()), nullptr, - persister_root_path_)); + persister_root_path_, + get_should_send_persisted_report.value_or( + base::BindRepeating([]() { return false; })))); } void PingManagerTest::SetUpFeatureList(bool should_enable_remove_cookies) { @@ -141,6 +153,24 @@ feature_list_.InitWithFeatures(enabled_features, disabled_features); } +std::string PingManagerTest::CallPersistThreatDetails(const std::string& url) { + std::unique_ptr<ClientSafeBrowsingReportRequest> report = + std::make_unique<ClientSafeBrowsingReportRequest>(); + report->set_type( + ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_PROFILE_CLOSED); + report->set_url(url); + std::string serialized_report; + EXPECT_TRUE(report->SerializeToString(&serialized_report)); + + PingManager::PersistThreatDetailsResult result = + ping_manager()->PersistThreatDetailsAndReportOnNextStartup( + std::move(report)); + EXPECT_EQ(result, + PingManager::PersistThreatDetailsResult::kPersistTaskPosted); + task_environment_.RunUntilIdle(); + return serialized_report; +} + TestSafeBrowsingTokenFetcher* PingManagerTest::SetUpTokenFetcher() { auto token_fetcher = std::make_unique<TestSafeBrowsingTokenFetcher>(); auto* raw_token_fetcher = token_fetcher.get(); @@ -519,7 +549,8 @@ /*get_should_fetch_access_token=*/base::BindRepeating( []() { return true; }), /*get_user_population_callback=*/std::nullopt, - /*get_page_load_token_callback=*/std::nullopt); + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/std::nullopt); SetUpFeatureList(/*should_enable_remove_cookies=*/true); RunReportThreatDetailsTest(/*expect_access_token=*/true, /*expected_user_population=*/std::nullopt, @@ -532,7 +563,8 @@ /*get_should_fetch_access_token=*/base::BindRepeating( []() { return true; }), /*get_user_population_callback=*/std::nullopt, - /*get_page_load_token_callback=*/std::nullopt); + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/std::nullopt); SetUpFeatureList(/*should_enable_remove_cookies=*/false); RunReportThreatDetailsTest(/*expect_access_token=*/true, /*expected_user_population=*/std::nullopt, @@ -547,7 +579,8 @@ population.set_user_population(ChromeUserPopulation::SAFE_BROWSING); return population; }), - /*get_page_load_token_callback=*/std::nullopt); + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/std::nullopt); auto population = ChromeUserPopulation(); population.set_user_population(ChromeUserPopulation::SAFE_BROWSING); RunReportThreatDetailsTest(/*expect_access_token=*/false, @@ -564,7 +597,8 @@ ChromeUserPopulation::PageLoadToken token; token.set_token_value("testing_page_load_token"); return token; - })); + }), + /*get_should_send_persisted_report=*/std::nullopt); RunReportThreatDetailsTest( /*expect_access_token=*/false, /*expected_user_population=*/std::nullopt, @@ -572,23 +606,12 @@ /*expect_cookies_removed=*/false); } -TEST_F(PingManagerTest, PersistThreatDetails) { - std::unique_ptr<ClientSafeBrowsingReportRequest> report = - std::make_unique<ClientSafeBrowsingReportRequest>(); - report->set_type( - ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_PROFILE_CLOSED); - report->set_url("https://some.url.com/"); - PingManager::PersistThreatDetailsResult result = - ping_manager()->PersistThreatDetailsAndReportOnNextStartup( - std::move(report)); - EXPECT_EQ(result, - PingManager::PersistThreatDetailsResult::kPersistTaskPosted); - task_environment_.RunUntilIdle(); +TEST_F(PingManagerTest, PersistThreatDetailsAtShutdown) { + base::HistogramTester histogram_tester; - base::FilePath persister_dir = - persister_root_path_.AppendASCII("DownloadReports"); - ASSERT_TRUE(base::PathExists(persister_dir)); - base::FileEnumerator directory_enumerator(persister_dir, + CallPersistThreatDetails("https://some.url.com/"); + ASSERT_TRUE(base::PathExists(persister_dir_)); + base::FileEnumerator directory_enumerator(persister_dir_, /*recursive=*/false, base::FileEnumerator::FILES); int number_of_files = 0; @@ -608,6 +631,10 @@ EXPECT_EQ(persisted_report->url(), "https://some.url.com/"); } EXPECT_EQ(number_of_files, 1); + histogram_tester.ExpectUniqueSample( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterWriteResult", + /*sample=*/PingManager::Persister::WriteResult::kSuccess, + /*expected_bucket_count=*/1); } TEST_F(PingManagerTest, PersistThreatDetailsAtShutdown_EmptyReport) { @@ -619,6 +646,135 @@ EXPECT_EQ(result, PingManager::PersistThreatDetailsResult::kEmptyReport); } +TEST_F(PingManagerTest, SendPersistedThreatDetailsOnStartup) { + base::HistogramTester histogram_tester; + std::string persisted_report1 = + CallPersistThreatDetails("https://some.url1.com/"); + std::string persisted_report2 = + CallPersistThreatDetails("https://some.url2.com/"); + EXPECT_TRUE(base::PathExists(persister_dir_)); + + // Create a new ping manager instance to simulate browser startup. + SetNewPingManager( + /*get_should_fetch_access_token=*/std::nullopt, + /*get_user_population_callback=*/std::nullopt, + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/base::BindRepeating([]() { + return true; + })); + + network::TestURLLoaderFactory test_url_loader_factory; + bool report1_sent = false, report2_sent = false; + test_url_loader_factory.SetInterceptor( + base::BindLambdaForTesting([&](const network::ResourceRequest& request) { + std::string upload_data = GetUploadData(request); + if (upload_data == persisted_report1) { + report1_sent = true; + } else if (upload_data == persisted_report2) { + report2_sent = true; + } + })); + ping_manager()->SetURLLoaderFactoryForTesting( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory)); + EXPECT_CALL(*webui_delegate_.get(), AddToCSBRRsSent(_)).Times(2); + + // Request is sent after delay. + task_environment_.FastForwardBy(base::Seconds(14)); + EXPECT_EQ(test_url_loader_factory.total_requests(), 0u); + task_environment_.FastForwardBy(base::Seconds(1)); + EXPECT_EQ(test_url_loader_factory.total_requests(), 2u); + EXPECT_TRUE(report1_sent); + EXPECT_TRUE(report2_sent); + // Directory is deleted. + EXPECT_FALSE(base::PathExists(persister_dir_)); + histogram_tester.ExpectUniqueSample( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterReadReportSuccessful", + /*sample=*/true, + /*expected_bucket_count=*/2); + histogram_tester.ExpectUniqueSample( + "SafeBrowsing.ClientSafeBrowsingReport.PersisterReportCountOnStartup", + /*sample=*/2, + /*expected_bucket_count=*/1); +} + +TEST_F(PingManagerTest, SendPersistedThreatDetailsOnStartup_MalformedReports) { + base::CreateDirectory(persister_dir_); + base::FilePath empty_file = persister_dir_.AppendASCII("empty"); + base::WriteFile(empty_file, ""); + base::FilePath malformed_file = persister_dir_.AppendASCII("malformed"); + base::WriteFile(malformed_file, "malformed_report"); + + // Create a new ping manager instance to simulate browser startup. + SetNewPingManager( + /*get_should_fetch_access_token=*/std::nullopt, + /*get_user_population_callback=*/std::nullopt, + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/base::BindRepeating([]() { + return true; + })); + + network::TestURLLoaderFactory test_url_loader_factory; + ping_manager()->SetURLLoaderFactoryForTesting( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory)); + + task_environment_.FastForwardBy(base::Seconds(15)); + // Persisted report should not be sent because their content is invalid. + EXPECT_EQ(test_url_loader_factory.total_requests(), 0u); + // Persisted report should still be deleted from disk. + EXPECT_FALSE(base::PathExists(persister_dir_)); +} + +TEST_F(PingManagerTest, SendPersistedThreatDetailsOnStartup_EmptyDirectory) { + base::CreateDirectory(persister_dir_); + + // Create a new ping manager instance to simulate browser startup. + SetNewPingManager( + /*get_should_fetch_access_token=*/std::nullopt, + /*get_user_population_callback=*/std::nullopt, + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/base::BindRepeating([]() { + return true; + })); + + network::TestURLLoaderFactory test_url_loader_factory; + ping_manager()->SetURLLoaderFactoryForTesting( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory)); + + task_environment_.FastForwardBy(base::Seconds(15)); + // The directory should be cleaned up. + EXPECT_FALSE(base::PathExists(persister_dir_)); +} + +TEST_F(PingManagerTest, + SendPersistedThreatDetailsOnStartup_ShouldNotSendReport) { + CallPersistThreatDetails("https://some.url1.com/"); + EXPECT_TRUE(base::PathExists(persister_dir_)); + + // Create a new ping manager instance to simulate browser startup. + SetNewPingManager( + /*get_should_fetch_access_token=*/std::nullopt, + /*get_user_population_callback=*/std::nullopt, + /*get_page_load_token_callback=*/std::nullopt, + /*get_should_send_persisted_report=*/base::BindRepeating([]() { + return false; + })); + + network::TestURLLoaderFactory test_url_loader_factory; + ping_manager()->SetURLLoaderFactoryForTesting( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory)); + + task_environment_.FastForwardBy(base::Seconds(15)); + // Persisted report should not be sent because + // get_should_send_persisted_report is false. + EXPECT_EQ(test_url_loader_factory.total_requests(), 0u); + // Persisted report should still be deleted from disk. + EXPECT_FALSE(base::PathExists(persister_dir_)); +} + TEST_F(PingManagerTest, ReportSafeBrowsingHit) { std::unique_ptr<HitReport> hit_report = std::make_unique<HitReport>(); std::string post_data = "testing_hit_report_post_data"; @@ -663,7 +819,8 @@ ChromeUserPopulation::PageLoadToken token; token.set_token_value("testing_page_load_token"); return token; - })); + }), + /*get_should_send_persisted_report=*/std::nullopt); FakeSafeBrowsingHatsDelegate* raw_fake_sb_hats_delegate = SetUpHatsDelegate(); ping_manager()->AttachThreatDetailsAndLaunchSurvey(std::move(report)); std::string deserialized_report_string;
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc index 5a80b17..6e6b32f4 100644 --- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc +++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -80,6 +80,31 @@ std::numeric_limits<int>::max(), std::numeric_limits<int>::max()); +// Note about RGBA/BGRA/ARGB pixel format names: +// In FrameSinkVideoCapturer, ARGB is a "format name", the frames it gives +// could be RGBA/BGRA depends on platform and the preference of the buffer +// format. When user wants ARGB result, it requests a CopyOutputRequest with +// ResultFormat::RGBA which gives RGBA/BGRA results depends on platform and +// where the result is stored (system memory or shared texture). +// In our case, when requesting a kPreferGpuMemoryBuffer, it will create a blit +// request, results in CopyOutputRequest uses whatever RGBA/BGRA pixel format +// the GMB is, which we created in advance. For now, it is determined by +// GetFramePoolPlatformPixelFormat. +// This is also documented in the mojom comments (https://crrev.com/c/5418235) +// about SetFormat, indicating the ARGB format may produce RGBA/BGRA frames +// depends on platform. + +media::VideoPixelFormat GetFramePoolPlatformPixelFormat( + media::VideoPixelFormat format, + mojom::BufferFormatPreference buffer_format_preference) { + if (format == media::PIXEL_FORMAT_ARGB && + buffer_format_preference == + mojom::BufferFormatPreference::kPreferGpuMemoryBuffer) { + return media::PIXEL_FORMAT_ABGR; + } + return format; +} + // Get the frame pool for the specific format. We need context_provider if the // format is NV12 or ARGB (when buffer_format_preference is kNativeTexture). // Thus, buffer_format_preference is also needed to tell which mode ARGB use. @@ -99,8 +124,9 @@ switch (buffer_format_preference) { case mojom::BufferFormatPreference::kPreferGpuMemoryBuffer: return std::make_unique<GpuMemoryBufferVideoFramePool>( - capacity, format, gfx::ColorSpace::CreateSRGB(), - context_provider); + capacity, + GetFramePoolPlatformPixelFormat(format, buffer_format_preference), + gfx::ColorSpace::CreateSRGB(), context_provider); case mojom::BufferFormatPreference::kDefault: return std::make_unique<SharedMemoryVideoFramePool>(capacity); default: @@ -837,7 +863,10 @@ region_properties->render_pass_subrect.ToString()); auto reserve_start_time = base::TimeTicks::Now(); - frame = frame_pool_->ReserveVideoFrame(pixel_format_, capture_size); + frame = frame_pool_->ReserveVideoFrame( + GetFramePoolPlatformPixelFormat(pixel_format_, + buffer_format_preference_), + capture_size); UMA_HISTOGRAM_CUSTOM_TIMES( "Viz.FrameSinkVideoCapturer.ReserveFrameDuration", @@ -1171,7 +1200,7 @@ // NV12 is currently supported only via GpuMemoryBuffers, everything else is // returned as a bitmap: const bool is_bitmap = - pixel_format_ != media::VideoPixelFormat::PIXEL_FORMAT_NV12; + buffer_format_preference_ == mojom::BufferFormatPreference::kDefault; consumer_->OnLog(base::StringPrintf( "FrameSinkVideoCapturerImpl: Sending CopyRequest: " "format=%s (%s) area:%s "
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc index 9ad60e5f..e99ac30 100644 --- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc +++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -33,6 +33,7 @@ #include "components/viz/test/test_context_provider.h" #include "gpu/command_buffer/client/client_shared_image.h" #include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "media/base/format_utils.h" #include "media/base/limits.h" #include "media/base/test_helpers.h" #include "media/base/video_util.h" @@ -89,6 +90,15 @@ gfx::PointF(*rso_x, *rso_y) == root_scroll_offset; } +// The following functions, CopyOutputRequestFormatToVideoPixelFormat and +// GetColorSpaceForPixelFormat only deal with pixel_format_ which is the user +// requested format, so we only have to care media::PIXEL_FORMAT_ARGB, and +// ResultFormat::RGBA. GetBufferFormatForVideoPixelFormat and +// GetBufferSizeInPixelsForVideoPixelFormat needs to deal with the GMB and frame +// result passed from the capture callback, it could be RGBA/BGRA depends on +// which platform we are, so we have to handle both media::PIXEL_FORMAT_ARGB +// and media::PIXEL_FORMAT_ABGR. + media::VideoPixelFormat CopyOutputRequestFormatToVideoPixelFormat( CopyOutputRequest::ResultFormat format) { switch (format) { @@ -116,23 +126,12 @@ } } -gfx::BufferFormat GetBufferFormatForVideoPixelFormat( - media::VideoPixelFormat format) { - switch (format) { - case media::PIXEL_FORMAT_ABGR: - return gfx::BufferFormat::RGBA_8888; - case media::PIXEL_FORMAT_NV12: - return gfx::BufferFormat::YUV_420_BIPLANAR; - default: - NOTREACHED_NORETURN(); - } -} - gfx::Size GetBufferSizeInPixelsForVideoPixelFormat( media::VideoPixelFormat format, const gfx::Size& coded_size) { switch (format) { case media::PIXEL_FORMAT_ABGR: + case media::PIXEL_FORMAT_ARGB: return coded_size; case media::PIXEL_FORMAT_NV12: return {cc::MathUtil::CheckedRoundUp(coded_size.width(), 2), @@ -282,7 +281,7 @@ auto gmb_dummy = std::make_unique<media::FakeGpuMemoryBuffer>( GetBufferSizeInPixelsForVideoPixelFormat(info->pixel_format, info->coded_size), - GetBufferFormatForVideoPixelFormat(info->pixel_format)); + VideoPixelFormatToGfxBufferFormat(info->pixel_format).value()); gpu::MailboxHolder mailbox_dummy[4]; // The frame is only gonna tell Letterbox to skip the test.
diff --git a/content/browser/bluetooth/bluetooth_metrics.h b/content/browser/bluetooth/bluetooth_metrics.h index 2d04af2..fa03609 100644 --- a/content/browser/bluetooth/bluetooth_metrics.h +++ b/content/browser/bluetooth/bluetooth_metrics.h
@@ -53,6 +53,7 @@ NOT_CONNECTED = 13, DOES_NOT_EXIST = 14, INVALID_ARGS = 15, + NON_AUTH_TIMEOUT = 16, // Note: Add new ConnectGATT outcomes immediately above this line. Make sure // to update the enum list in tools/metrics/histograms/histograms.xml // accordingly.
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc index 7ff0f0ed..46a3efc 100644 --- a/content/browser/bluetooth/web_bluetooth_service_impl.cc +++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -262,6 +262,9 @@ case BluetoothDevice::ERROR_INVALID_ARGS: RecordConnectGATTOutcome(UMAConnectGATTOutcome::INVALID_ARGS); return blink::mojom::WebBluetoothResult::CONNECT_INVALID_ARGS; + case BluetoothDevice::ERROR_NON_AUTH_TIMEOUT: + RecordConnectGATTOutcome(UMAConnectGATTOutcome::NON_AUTH_TIMEOUT); + return blink::mojom::WebBluetoothResult::CONNECT_NON_AUTH_TIMEOUT; case BluetoothDevice::NUM_CONNECT_ERROR_CODES: NOTREACHED(); return blink::mojom::WebBluetoothResult::CONNECT_UNKNOWN_FAILURE;
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc index 74f95ce..3703599 100644 --- a/content/browser/media/session/media_session_impl.cc +++ b/content/browser/media/session/media_session_impl.cc
@@ -636,6 +636,18 @@ for (auto& observer : observers_) observer->MediaSessionPositionChanged(position_); + + const bool is_considered_live = + position_.has_value() && position_->duration().is_max(); + if (is_considered_live == is_considered_live_) { + return; + } + + // The available actions can be different depending on whether we're + // considered live or not, so if that has changed we must re-notify for the + // new state. + is_considered_live_ = is_considered_live; + RebuildAndNotifyActionsChanged(); } void MediaSessionImpl::Resume(SuspendType suspend_type) { @@ -1750,10 +1762,14 @@ actions.insert(media_session::mojom::MediaSessionAction::kPlay); actions.insert(media_session::mojom::MediaSessionAction::kPause); actions.insert(media_session::mojom::MediaSessionAction::kStop); - actions.insert(media_session::mojom::MediaSessionAction::kSeekTo); - actions.insert(media_session::mojom::MediaSessionAction::kScrubTo); - actions.insert(media_session::mojom::MediaSessionAction::kSeekForward); - actions.insert(media_session::mojom::MediaSessionAction::kSeekBackward); + + // Support seeking as long as this isn't live media. + if (!is_considered_live_) { + actions.insert(media_session::mojom::MediaSessionAction::kSeekTo); + actions.insert(media_session::mojom::MediaSessionAction::kScrubTo); + actions.insert(media_session::mojom::MediaSessionAction::kSeekForward); + actions.insert(media_session::mojom::MediaSessionAction::kSeekBackward); + } } // If the website has specified an action handler for 'enterpictureinpicture',
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h index abb402e..c6e8baf2 100644 --- a/content/browser/media/session/media_session_impl.h +++ b/content/browser/media/session/media_session_impl.h
@@ -653,6 +653,11 @@ // active session. bool always_ignore_for_active_session_for_testing_ = false; + // True if the given media has infinite duration OR has a duration that + // changes often enough to be considered live. See + // `MaybeGuardDurationUpdate()` for details on duration changes. + bool is_considered_live_ = false; + WEB_CONTENTS_USER_DATA_KEY_DECL(); };
diff --git a/content/browser/media/session/media_session_impl_unittest.cc b/content/browser/media/session/media_session_impl_unittest.cc index b4cd784..6f8312fd 100644 --- a/content/browser/media/session/media_session_impl_unittest.cc +++ b/content/browser/media/session/media_session_impl_unittest.cc
@@ -35,6 +35,7 @@ using media_session::mojom::AudioFocusType; using media_session::mojom::MediaPlaybackState; +using media_session::mojom::MediaSessionAction; using media_session::mojom::MediaSessionInfo; using media_session::mojom::MediaSessionInfoPtr; using media_session::test::MockMediaSessionMojoObserver; @@ -98,15 +99,13 @@ MediaSessionImplTest() : RenderViewHostTestHarness( base::test::TaskEnvironment::TimeSource::MOCK_TIME) { - default_actions_.insert(media_session::mojom::MediaSessionAction::kPlay); - default_actions_.insert(media_session::mojom::MediaSessionAction::kPause); - default_actions_.insert(media_session::mojom::MediaSessionAction::kStop); - default_actions_.insert(media_session::mojom::MediaSessionAction::kSeekTo); - default_actions_.insert(media_session::mojom::MediaSessionAction::kScrubTo); - default_actions_.insert( - media_session::mojom::MediaSessionAction::kSeekForward); - default_actions_.insert( - media_session::mojom::MediaSessionAction::kSeekBackward); + default_actions_.insert(MediaSessionAction::kPlay); + default_actions_.insert(MediaSessionAction::kPause); + default_actions_.insert(MediaSessionAction::kStop); + default_actions_.insert(MediaSessionAction::kSeekTo); + default_actions_.insert(MediaSessionAction::kScrubTo); + default_actions_.insert(MediaSessionAction::kSeekForward); + default_actions_.insert(MediaSessionAction::kSeekBackward); } MediaSessionImplTest(const MediaSessionImplTest&) = delete; @@ -212,8 +211,7 @@ return player_observer_.get(); } - const std::set<media_session::mojom::MediaSessionAction>& default_actions() - const { + const std::set<MediaSessionAction>& default_actions() const { return default_actions_; } @@ -226,7 +224,7 @@ } private: - std::set<media_session::mojom::MediaSessionAction> default_actions_; + std::set<MediaSessionAction> default_actions_; base::test::ScopedFeatureList scoped_feature_list_; @@ -376,9 +374,8 @@ } TEST_F(MediaSessionImplTest, SuspendUI) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPause, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPause, _)) .Times(0); StartNewPlayer(); @@ -392,14 +389,12 @@ } TEST_F(MediaSessionImplTest, SuspendContent_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPause, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPause, _)) .Times(0); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPause); + mock_media_session_service().EnableAction(MediaSessionAction::kPause); GetMediaSession()->Suspend(MediaSession::SuspendType::kContent); mock_media_session_service().FlushForTesting(); @@ -410,14 +405,12 @@ } TEST_F(MediaSessionImplTest, SuspendSystem_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPause, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPause, _)) .Times(0); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPause); + mock_media_session_service().EnableAction(MediaSessionAction::kPause); GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem); mock_media_session_service().FlushForTesting(); @@ -428,13 +421,11 @@ } TEST_F(MediaSessionImplTest, SuspendUI_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPause, _)); + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPause, _)); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPause); + mock_media_session_service().EnableAction(MediaSessionAction::kPause); GetMediaSession()->Suspend(MediaSession::SuspendType::kUI); mock_media_session_service().FlushForTesting(); @@ -445,9 +436,8 @@ } TEST_F(MediaSessionImplTest, ResumeUI) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPlay, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPlay, _)) .Times(0); StartNewPlayer(); @@ -462,14 +452,12 @@ } TEST_F(MediaSessionImplTest, ResumeContent_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPlay, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPlay, _)) .Times(0); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPlay); + mock_media_session_service().EnableAction(MediaSessionAction::kPlay); GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem); GetMediaSession()->Resume(MediaSession::SuspendType::kContent); @@ -481,14 +469,12 @@ } TEST_F(MediaSessionImplTest, ResumeSystem_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPlay, _)) + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPlay, _)) .Times(0); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPlay); + mock_media_session_service().EnableAction(MediaSessionAction::kPlay); GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem); GetMediaSession()->Resume(MediaSession::SuspendType::kSystem); @@ -500,13 +486,11 @@ } TEST_F(MediaSessionImplTest, ResumeUI_WithAction) { - EXPECT_CALL( - mock_media_session_service().mock_client(), - DidReceiveAction(media_session::mojom::MediaSessionAction::kPlay, _)); + EXPECT_CALL(mock_media_session_service().mock_client(), + DidReceiveAction(MediaSessionAction::kPlay, _)); StartNewPlayer(); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPlay); + mock_media_session_service().EnableAction(MediaSessionAction::kPlay); GetMediaSession()->Suspend(MediaSession::SuspendType::kSystem); GetMediaSession()->Resume(MediaSession::SuspendType::kUI); @@ -806,18 +790,15 @@ media_session::test::MockMediaSessionMojoObserver observer( *GetMediaSession()); mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kEnterPictureInPicture); + MediaSessionAction::kEnterPictureInPicture); mock_media_session_service().FlushForTesting(); - EXPECT_TRUE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kEnterPictureInPicture)); - EXPECT_TRUE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kEnterAutoPictureInPicture)); - EXPECT_TRUE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kExitPictureInPicture)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kEnterPictureInPicture)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kEnterAutoPictureInPicture)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kExitPictureInPicture)); } TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureVideo) { @@ -828,16 +809,13 @@ StartNewPlayer(); media_session::test::MockMediaSessionMojoObserver observer( *GetMediaSession()); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPause); + mock_media_session_service().EnableAction(MediaSessionAction::kPause); mock_media_session_service().FlushForTesting(); - EXPECT_FALSE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kEnterPictureInPicture)); - EXPECT_TRUE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kExitPictureInPicture)); + EXPECT_FALSE(base::Contains(observer.actions(), + MediaSessionAction::kEnterPictureInPicture)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kExitPictureInPicture)); } TEST_F(MediaSessionImplTest, WebContentsHasPictureInPictureDocument) { @@ -848,16 +826,13 @@ StartNewPlayer(); media_session::test::MockMediaSessionMojoObserver observer( *GetMediaSession()); - mock_media_session_service().EnableAction( - media_session::mojom::MediaSessionAction::kPause); + mock_media_session_service().EnableAction(MediaSessionAction::kPause); mock_media_session_service().FlushForTesting(); - EXPECT_FALSE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kEnterPictureInPicture)); - EXPECT_TRUE(base::Contains( - observer.actions(), - media_session::mojom::MediaSessionAction::kExitPictureInPicture)); + EXPECT_FALSE(base::Contains(observer.actions(), + MediaSessionAction::kEnterPictureInPicture)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kExitPictureInPicture)); } TEST_F(MediaSessionImplTest, SufficientlyVisibleVideo_NoPlayer) { @@ -927,6 +902,45 @@ EXPECT_TRUE(GetMediaSession()->IsControllable()); } +TEST_F(MediaSessionImplTest, SeekingAndScrubbingNotAllowedWithMaxDuration) { + MockMediaSessionMojoObserver observer(*GetMediaSession()); + int player_id = player_observer_->StartNewPlayer(); + GetMediaSession()->AddPlayer(player_observer_.get(), player_id); + + media_session::MediaPosition pos; + pos = media_session::MediaPosition( + /*playback_rate=*/1.0, + /*duration=*/base::TimeDelta::Max(), + /*position=*/base::TimeDelta(), /*end_of_media=*/false); + + player_observer_->SetPosition(player_id, pos); + GetMediaSession()->RebuildAndNotifyMediaPositionChanged(); + FlushForTesting(GetMediaSession()); + + // With a max duration, we should be considered live media and should not + // allow seeking and scrubbing actions by default. + EXPECT_FALSE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kScrubTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekForward)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekBackward)); + + // However, if the website explicitly supports the action, then we will still + // route it. + mock_media_session_service().EnableAction(MediaSessionAction::kSeekTo); + FlushForTesting(GetMediaSession()); + + EXPECT_TRUE(base::Contains(observer.actions(), MediaSessionAction::kSeekTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kScrubTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekForward)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekBackward)); +} + class MediaSessionImplWithMediaSessionClientTest : public MediaSessionImplTest { protected: TestMediaSessionClient client_; @@ -1043,8 +1057,30 @@ /*playback_rate=*/0.0, /*duration=*/base::TimeDelta::Max(), /*position=*/base::TimeDelta(), /*end_of_media=*/false)); + + // Since we're now considered live, the seeking and scrubbing actions + // should no longer be available. + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kScrubTo)); + EXPECT_FALSE( + base::Contains(observer.actions(), MediaSessionAction::kSeekForward)); + EXPECT_FALSE(base::Contains(observer.actions(), + MediaSessionAction::kSeekBackward)); } else { EXPECT_EQ(**observer.session_position(), pos); + + // If we're not considered live, then the seeking and scrubbing actions + // should still be available. + EXPECT_TRUE( + base::Contains(observer.actions(), MediaSessionAction::kSeekTo)); + EXPECT_TRUE( + base::Contains(observer.actions(), MediaSessionAction::kScrubTo)); + EXPECT_TRUE( + base::Contains(observer.actions(), MediaSessionAction::kSeekForward)); + EXPECT_TRUE(base::Contains(observer.actions(), + MediaSessionAction::kSeekBackward)); } }
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc index 837b7fa2..af3374c6 100644 --- a/content/browser/shared_storage/shared_storage_browsertest.cc +++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -870,7 +870,7 @@ {"SharedStorageStalenessThreshold", TimeDeltaToString(base::Days(kStalenessThresholdDays))}, }}, - {blink::features::kSharedStorageAPIM124, {}}}, + {blink::features::kSharedStorageAPIM125, {}}}, /*disabled_features=*/{}); fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
diff --git a/content/browser/shared_storage/shared_storage_document_service_impl.cc b/content/browser/shared_storage/shared_storage_document_service_impl.cc index aee9aab..f9831e6 100644 --- a/content/browser/shared_storage/shared_storage_document_service_impl.cc +++ b/content/browser/shared_storage/shared_storage_document_service_impl.cc
@@ -115,9 +115,9 @@ mojo::PendingAssociatedReceiver<blink::mojom::SharedStorageWorkletHost> worklet_host, CreateWorkletCallback callback) { - // A document can only create multiple worklets with `kSharedStorageAPIM124` + // A document can only create multiple worklets with `kSharedStorageAPIM125` // enabled. - if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM124)) { + if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125)) { if (create_worklet_called_) { // This could indicate a compromised renderer, so let's terminate it. receiver_.ReportBadMessage("Attempted to create multiple worklets."); @@ -130,8 +130,8 @@ create_worklet_called_ = true; // A document can only create cross-origin worklets with - // `kSharedStorageAPIM124` enabled. - if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM124) && + // `kSharedStorageAPIM125` enabled. + if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125) && !render_frame_host().GetLastCommittedOrigin().IsSameOriginWith( script_source_url)) { // This could indicate a compromised renderer, so let's terminate it.
diff --git a/content/browser/shared_storage/shared_storage_worklet_host_manager.cc b/content/browser/shared_storage/shared_storage_worklet_host_manager.cc index 1e0d83e3..5de98fb 100644 --- a/content/browser/shared_storage/shared_storage_worklet_host_manager.cc +++ b/content/browser/shared_storage/shared_storage_worklet_host_manager.cc
@@ -73,9 +73,9 @@ auto worklet_hosts_it = attached_shared_storage_worklet_hosts_.find(document_service); - // A document can only create multiple worklets with `kSharedStorageAPIM124` + // A document can only create multiple worklets with `kSharedStorageAPIM125` // enabled. - if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM124)) { + if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125)) { CHECK(worklet_hosts_it == attached_shared_storage_worklet_hosts_.end()); }
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc index 671eec05..3161cd58b 100644 --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc
@@ -234,8 +234,8 @@ kSetOnlyIfOverridden}, {wf::EnableSharedStorageAPIM118, raw_ref(blink::features::kSharedStorageAPIM118), kDefault}, - {wf::EnableSharedStorageAPIM124, - raw_ref(blink::features::kSharedStorageAPIM124), kDefault}, + {wf::EnableSharedStorageAPIM125, + raw_ref(blink::features::kSharedStorageAPIM125), kDefault}, {wf::EnableFedCmMultipleIdentityProviders, raw_ref(features::kFedCmMultipleIdentityProviders), kDefault}, {wf::EnableFedCmSelectiveDisclosure, @@ -675,15 +675,15 @@ WebRuntimeFeatures::EnableSharedStorageAPIM118(false); } - if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM124) || + if (!base::FeatureList::IsEnabled(blink::features::kSharedStorageAPIM125) || !base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) { - LOG_IF(WARNING, WebRuntimeFeatures::IsSharedStorageAPIM124Enabled()) - << "SharedStorage for M124+ cannot be enabled in this " + LOG_IF(WARNING, WebRuntimeFeatures::IsSharedStorageAPIM125Enabled()) + << "SharedStorage for M125+ cannot be enabled in this " "configuration. Use --" << switches::kEnableFeatures << "=" << blink::features::kSharedStorageAPI.name << "," - << blink::features::kSharedStorageAPIM124.name << " in addition."; - WebRuntimeFeatures::EnableSharedStorageAPIM124(false); + << blink::features::kSharedStorageAPIM125.name << " in addition."; + WebRuntimeFeatures::EnableSharedStorageAPIM125(false); } if (!base::FeatureList::IsEnabled(
diff --git a/content/test/data/interest_group/bidding_argument_validator.js b/content/test/data/interest_group/bidding_argument_validator.js index 1bfcbd20..ed9c737 100644 --- a/content/test/data/interest_group/bidding_argument_validator.js +++ b/content/test/data/interest_group/bidding_argument_validator.js
@@ -214,7 +214,7 @@ throw 'Wrong topLevelSeller ' + browserSignals.topLevelSeller; if (isGenerateBid) { - if (Object.keys(browserSignals).length !== 10) { + if (Object.keys(browserSignals).length !== 9) { throw 'Wrong number of browser signals fields ' + JSON.stringify(browserSignals); } @@ -231,8 +231,6 @@ if (browserSignals.forDebuggingOnlyInCooldownOrLockout) throw 'Wrong forDebuggingOnlyInCooldownOrLockout ' + browserSignals.forDebuggingOnlyInCooldownOrLockout; - if (browserSignals.multiBidLimit !== 1) - throw 'Wrong multiBidLimit ' + browserSignals.multiBidLimit; } else { // FledgePassKAnonStatusToReportWin feature adds a new parameter // KAnonStatus to reportWin(), which is under a Finch trial for some enabled
diff --git a/content/test/data/interest_group/component_auction_bidding_argument_validator.js b/content/test/data/interest_group/component_auction_bidding_argument_validator.js index 04d3ad5..165b21d7 100644 --- a/content/test/data/interest_group/component_auction_bidding_argument_validator.js +++ b/content/test/data/interest_group/component_auction_bidding_argument_validator.js
@@ -194,7 +194,7 @@ throw 'Wrong topLevelSeller ' + browserSignals.topLevelSeller; if (isGenerateBid) { - if (Object.keys(browserSignals).length !== 11) { + if (Object.keys(browserSignals).length !== 10) { throw 'Wrong number of browser signals fields ' + JSON.stringify(browserSignals); } @@ -211,8 +211,6 @@ if (browserSignals.forDebuggingOnlyInCooldownOrLockout) throw 'Wrong forDebuggingOnlyInCooldownOrLockout ' + browserSignals.forDebuggingOnlyInCooldownOrLockout; - if (browserSignals.multiBidLimit !== 1) - throw 'Wrong multiBidLimit ' + browserSignals.multiBidLimit; } else { // FledgePassKAnonStatusToReportWin feature adds a new parameter // KAnonStatus to reportWin(), which is under a Finch trial for some enabled
diff --git a/device/bluetooth/bluetooth_device.h b/device/bluetooth/bluetooth_device.h index a5fe634..f63d5c2 100644 --- a/device/bluetooth/bluetooth_device.h +++ b/device/bluetooth/bluetooth_device.h
@@ -110,6 +110,7 @@ ERROR_DEVICE_UNCONNECTED = 11, ERROR_DOES_NOT_EXIST = 12, ERROR_INVALID_ARGS = 13, + ERROR_NON_AUTH_TIMEOUT = 14, NUM_CONNECT_ERROR_CODES, // Keep as last enum. };
diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/device/bluetooth/bluetooth_l2cap_channel_mac.mm index 9229019..5bcbde96 100644 --- a/device/bluetooth/bluetooth_l2cap_channel_mac.mm +++ b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
@@ -183,6 +183,7 @@ IOBluetoothL2CAPChannel* channel) { DCHECK_EQ(channel_, channel); channel_ = nil; + [delegate_ resetOwner]; delegate_ = nil; socket()->OnChannelClosed(); }
diff --git a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm index 85d092a..97d1a46e 100644 --- a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm +++ b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
@@ -184,6 +184,7 @@ IOBluetoothRFCOMMChannel* channel) { DCHECK_EQ(channel_, channel); channel_ = nil; + [delegate_ resetOwner]; delegate_ = nil; socket()->OnChannelClosed(); }
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc index 4ff25ba..783cde53 100644 --- a/device/bluetooth/chromeos/bluetooth_utils.cc +++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -181,6 +181,10 @@ case ConnectionFailureReason::kFailed: [[fallthrough]]; case ConnectionFailureReason::kInprogress: + [[fallthrough]]; + case ConnectionFailureReason::kNotFound: + [[fallthrough]]; + case ConnectionFailureReason::kBluetoothDisabled: const std::string result_histogram_name_prefix = "Bluetooth.ChromeOS.Pairing.Result"; base::UmaHistogramEnumeration(
diff --git a/device/bluetooth/chromeos/bluetooth_utils.h b/device/bluetooth/chromeos/bluetooth_utils.h index 7aacf74..1cec69a 100644 --- a/device/bluetooth/chromeos/bluetooth_utils.h +++ b/device/bluetooth/chromeos/bluetooth_utils.h
@@ -56,7 +56,9 @@ kAuthCanceled = 8, kAuthRejected = 9, kInprogress = 10, - kMaxValue = kInprogress + kNotFound = 11, + kBluetoothDisabled = 12, + kMaxValue = kBluetoothDisabled }; // This enum is tied directly to a UMA enum defined in
diff --git a/device/bluetooth/floss/bluetooth_device_floss.cc b/device/bluetooth/floss/bluetooth_device_floss.cc index 0de2a59b..226d358 100644 --- a/device/bluetooth/floss/bluetooth_device_floss.cc +++ b/device/bluetooth/floss/bluetooth_device_floss.cc
@@ -309,7 +309,7 @@ void BluetoothDeviceFloss::ConnectionIncomplete() { UpdateConnectingState( ConnectingState::kIdle, - BluetoothDevice::ConnectErrorCode::ERROR_DEVICE_NOT_READY); + BluetoothDevice::ConnectErrorCode::ERROR_NON_AUTH_TIMEOUT); } #if BUILDFLAG(IS_CHROMEOS)
diff --git a/device/bluetooth/public/mojom/adapter.mojom b/device/bluetooth/public/mojom/adapter.mojom index 906b8c83..9a8a44c 100644 --- a/device/bluetooth/public/mojom/adapter.mojom +++ b/device/bluetooth/public/mojom/adapter.mojom
@@ -29,6 +29,7 @@ NOT_CONNECTED, DOES_NOT_EXIST, INVALID_ARGS, + NON_AUTH_TIMEOUT, }; union LocalCharacteristicReadResult {
diff --git a/device/bluetooth/public/mojom/connect_result_type_converter.h b/device/bluetooth/public/mojom/connect_result_type_converter.h index ac3b79c..c8813ab 100644 --- a/device/bluetooth/public/mojom/connect_result_type_converter.h +++ b/device/bluetooth/public/mojom/connect_result_type_converter.h
@@ -50,6 +50,8 @@ return bluetooth::mojom::ConnectResult::DOES_NOT_EXIST; case device::BluetoothDevice::ConnectErrorCode::ERROR_INVALID_ARGS: return bluetooth::mojom::ConnectResult::INVALID_ARGS; + case device::BluetoothDevice::ConnectErrorCode::ERROR_NON_AUTH_TIMEOUT: + return bluetooth::mojom::ConnectResult::NON_AUTH_TIMEOUT; case device::BluetoothDevice::ConnectErrorCode::NUM_CONNECT_ERROR_CODES: NOTREACHED(); return bluetooth::mojom::ConnectResult::FAILED;
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn index ad7d05a..32f50f3 100644 --- a/device/fido/BUILD.gn +++ b/device/fido/BUILD.gn
@@ -163,6 +163,8 @@ "enclave/transact.h", "enclave/types.cc", "enclave/types.h", + "enclave/verify/claim.cc", + "enclave/verify/claim.h", "enclave/verify/verify.h", "fido_authenticator.cc", "fido_authenticator.h",
diff --git a/device/fido/enclave/verify/claim.cc b/device/fido/enclave/verify/claim.cc new file mode 100644 index 0000000..4bf28fe --- /dev/null +++ b/device/fido/enclave/verify/claim.cc
@@ -0,0 +1,21 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/fido/enclave/verify/claim.h" + +namespace device::enclave { + +Subject::Subject() = default; +Subject::Subject(std::string name, std::map<std::string, std::string> digest) + : name(std::move(name)), digest(std::move(digest)) {} +Subject::~Subject() = default; + +ClaimEvidence::ClaimEvidence() = default; +ClaimEvidence::ClaimEvidence(std::optional<std::string> role, + std::string uri, + std::vector<uint8_t> digest) + : role(std::move(role)), uri(std::move(uri)), digest(std::move(digest)) {} +ClaimEvidence::~ClaimEvidence() = default; + +} // namespace device::enclave
diff --git a/device/fido/enclave/verify/claim.h b/device/fido/enclave/verify/claim.h new file mode 100644 index 0000000..6a96af46 --- /dev/null +++ b/device/fido/enclave/verify/claim.h
@@ -0,0 +1,67 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_ENCLAVE_VERIFY_CLAIM_H_ +#define DEVICE_FIDO_ENCLAVE_VERIFY_CLAIM_H_ + +#include <map> +#include <optional> +#include <string> +#include <vector> + +#include "base/time/time.h" + +namespace device::enclave { + +struct Subject { + Subject(std::string name, std::map<std::string, std::string> digest); + Subject(); + ~Subject(); + + std::string name; + std::map<std::string, std::string> digest; +}; + +template <typename T> +struct Statement { + std::string type; + std::string predicate_type; + std::vector<Subject> subject; + T predicate; +}; + +struct ClaimEvidence { + ClaimEvidence(std::optional<std::string> role, + std::string uri, + std::vector<uint8_t> digest); + ClaimEvidence(); + ~ClaimEvidence(); + + std::optional<std::string> role; + std::string uri; + std::vector<uint8_t> digest; +}; + +struct ClaimValidity { + base::Time not_before; + base::Time not_after; +}; + +template <typename T> +struct ClaimPredicate { + std::string claim_type; + std::optional<T> claim_spec; + std::string usage; + base::Time issued_on; + std::optional<ClaimValidity> validity; + std::vector<ClaimEvidence> evidence; +}; + +struct Claimless {}; + +typedef Statement<ClaimPredicate<Claimless>> EndorsementStatement; + +} // namespace device::enclave + +#endif // DEVICE_FIDO_ENCLAVE_VERIFY_CLAIM_H_
diff --git a/docs/updater/functional_spec.md b/docs/updater/functional_spec.md index f9d4e8a..74fd731 100644 --- a/docs/updater/functional_spec.md +++ b/docs/updater/functional_spec.md
@@ -825,11 +825,13 @@ The updater also checks for policy updates when the `RunPeriodicTasks` RPC is invoked at periodic intervals. +The maximum size of the token is 4K (Windows only). + #### Windows The enrollment token is searched in the order: -* The `EnrollmentToken` REG_SZ value from +* The `EnrollmentToken` REG_BINARY value from `HKLM\Software\Policies\{COMPANY_SHORTNAME}\CloudManagement` -* The `CloudManagementEnrollmentToken` REG_SZ value from +* The `CloudManagementEnrollmentToken` REG_BINARY value from `HKLM\Software\Policies\{COMPANY_SHORTNAME}\{BROWSER_NAME}` The `EnrollmentMandatory` REG_DWORD value is also read from @@ -862,9 +864,9 @@ DM token is stored at: ##### Windows -- The `dmtoken` REG_SZ value at path: +- The `dmtoken` REG_BINARY value at path: `HKLM\Software\WOW6432Node\{COMPANY_SHORTNAME}\Enrollment\` -- The `dmtoken` REG_SZ value at path: +- The `dmtoken` REG_BINARY value at path: `HKLM64\Software\{COMPANY_SHORTNAME}\{BROWSER_NAME}\Enrollment\`. This is for backward compatibility.
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 676cfb7..aac6dfa 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn
@@ -1049,6 +1049,7 @@ "//chromeos:test_support", "//chromeos/ash/components/dbus:test_support", "//chromeos/ash/components/dbus/audio", + "//chromeos/ash/components/dbus/debug_daemon", "//chromeos/ash/components/dbus/media_analytics", "//chromeos/ash/components/dbus/media_analytics:media_perception_proto", "//chromeos/ash/components/dbus/shill",
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_api.cc b/extensions/browser/api/bluetooth/bluetooth_private_api.cc index 72d7269..15cc0dc 100644 --- a/extensions/browser/api/bluetooth/bluetooth_private_api.cc +++ b/extensions/browser/api/bluetooth/bluetooth_private_api.cc
@@ -129,6 +129,8 @@ return bt_private::ConnectResultType::kDoesNotExist; case device::BluetoothDevice::ERROR_INVALID_ARGS: return bt_private::ConnectResultType::kInvalidArgs; + case device::BluetoothDevice::ERROR_NON_AUTH_TIMEOUT: + return bt_private::ConnectResultType::kNonAuthTimeout; case device::BluetoothDevice::NUM_CONNECT_ERROR_CODES: NOTREACHED(); break;
diff --git a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc index 4a5fde3..d5907352 100644 --- a/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc +++ b/extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_event_router.cc
@@ -237,6 +237,8 @@ case BluetoothDevice::ERROR_INVALID_ARGS: return extensions::BluetoothLowEnergyEventRouter:: kStatusErrorInvalidArguments; + case BluetoothDevice::ERROR_NON_AUTH_TIMEOUT: + return extensions::BluetoothLowEnergyEventRouter::kStatusErrorTimeout; case BluetoothDevice::NUM_CONNECT_ERROR_CODES: NOTREACHED(); return extensions::BluetoothLowEnergyEventRouter::
diff --git a/extensions/browser/api/feedback_private/BUILD.gn b/extensions/browser/api/feedback_private/BUILD.gn index 5aa7f77..c8f1acd 100644 --- a/extensions/browser/api/feedback_private/BUILD.gn +++ b/extensions/browser/api/feedback_private/BUILD.gn
@@ -45,8 +45,12 @@ deps += [ "//ash/public/cpp", + "//chromeos/ash/components/cryptohome", + "//chromeos/ash/components/dbus/debug_daemon", "//chromeos/ash/services/assistant/public/cpp", "//chromeos/ash/services/assistant/public/mojom", + "//components/account_id", + "//components/user_manager", ] } }
diff --git a/extensions/browser/api/feedback_private/DEPS b/extensions/browser/api/feedback_private/DEPS index 89fb4ef9..370f538f 100644 --- a/extensions/browser/api/feedback_private/DEPS +++ b/extensions/browser/api/feedback_private/DEPS
@@ -1,4 +1,7 @@ include_rules = [ - "+components/feedback", "+ash/public/cpp", + "+components/account_id", + "+components/feedback", + "+components/user_manager", + "+third_party/cros_system_api" ]
diff --git a/extensions/browser/api/feedback_private/feedback_service.cc b/extensions/browser/api/feedback_private/feedback_service.cc index ab004d7..3c9b17a 100644 --- a/extensions/browser/api/feedback_private/feedback_service.cc +++ b/extensions/browser/api/feedback_private/feedback_service.cc
@@ -12,6 +12,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/functional/bind.h" +#include "base/functional/callback_forward.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram_functions.h" @@ -34,7 +35,11 @@ #if BUILDFLAG(IS_CHROMEOS_ASH) #include "ash/public/cpp/assistant/controller/assistant_controller.h" +#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h" #include "chromeos/ash/services/assistant/public/cpp/assistant_service.h" +#include "components/account_id/account_id.h" +#include "components/user_manager/user_manager.h" +#include "third_party/cros_system_api/dbus/debugd/dbus-constants.h" #endif // BUILDFLAG(IS_CHROMEOS_ASH) namespace extensions { @@ -54,15 +59,11 @@ FILE_PATH_LITERAL("bluetooth/log.bz2.old"); constexpr base::FilePath::CharType kBluetoothQualityReportFilePath[] = FILE_PATH_LITERAL("bluetooth/bluetooth_quality_report"); -constexpr base::FilePath::CharType kWifiDebugLogsFilePath[] = - FILE_PATH_LITERAL("wifi/iwlwifi_firmware_dumps.tar.zst"); constexpr char kBluetoothLogsAttachmentName[] = "bluetooth_logs.bz2"; constexpr char kBluetoothLogsAttachmentNameOld[] = "bluetooth_logs.old.bz2"; constexpr char kBluetoothQualityReportAttachmentName[] = "bluetooth_quality_report"; -constexpr char kWifiDebugLogsAttachmentName[] = - "iwlwifi_firmware_dumps.tar.zst"; constexpr char kLacrosHistogramsFilename[] = "lacros_histograms.zip"; @@ -80,23 +81,22 @@ } } -void LoadAttachmentsIfRequested( - scoped_refptr<feedback::FeedbackData> feedback_data, - const base::FilePath& root_path, - bool send_bluetooth_logs, - bool send_wifi_debug_logs) { - if (send_bluetooth_logs) { - AddAttachment(feedback_data, root_path, kBluetoothLogsFilePath, - kBluetoothLogsAttachmentName); - AddAttachment(feedback_data, root_path, kBluetoothLogsFilePathOld, - kBluetoothLogsAttachmentNameOld); - AddAttachment(feedback_data, root_path, kBluetoothQualityReportFilePath, - kBluetoothQualityReportAttachmentName); - } +void AttachBluetoothLogs(scoped_refptr<feedback::FeedbackData> feedback_data, + const base::FilePath& root_path) { + AddAttachment(feedback_data, root_path, kBluetoothLogsFilePath, + kBluetoothLogsAttachmentName); + AddAttachment(feedback_data, root_path, kBluetoothLogsFilePathOld, + kBluetoothLogsAttachmentNameOld); + AddAttachment(feedback_data, root_path, kBluetoothQualityReportFilePath, + kBluetoothQualityReportAttachmentName); +} - if (send_wifi_debug_logs) { - AddAttachment(feedback_data, root_path, kWifiDebugLogsFilePath, - kWifiDebugLogsAttachmentName); +// A new case must be added for every new log type. Otherwise the code should +// not compile. +std::string_view GetAttachmentName(debugd::FeedbackBinaryLogType log_type) { + switch (log_type) { + case debugd::WIFI_FIRMWARE_DUMP: + return "iwlwifi_firmware_dumps.tar.zst"; } } #endif @@ -290,20 +290,46 @@ std::move(compressed_histograms)); } - // If at least one attachment is requested, invoke LoadAttachmentsIfRequested - // to add all requested attachments in a separate thread to avoid blocking the - // UI thread. - if (params.send_bluetooth_logs || params.send_wifi_debug_logs) { + auto barrier_closure = + base::BarrierClosure((params.send_bluetooth_logs ? 1 : 0) + + (params.send_wifi_debug_logs ? 1 : 0), + base::BindOnce(&FeedbackService::OnAllLogsFetched, + this, params, feedback_data)); + // If bluetooth logs are requested, invoke AttachBluetoothLogs to add + // them in a separate thread to avoid blocking the UI thread. + if (params.send_bluetooth_logs) { base::ThreadPool::PostTaskAndReply( FROM_HERE, {base::MayBlock()}, - base::BindOnce(&LoadAttachmentsIfRequested, feedback_data, - log_file_root_, params.send_bluetooth_logs, - params.send_wifi_debug_logs), - base::BindOnce(&FeedbackService::OnAllLogsFetched, this, params, - feedback_data)); - } else { - OnAllLogsFetched(params, feedback_data); + base::BindOnce(&AttachBluetoothLogs, feedback_data, log_file_root_), + barrier_closure); } + + if (params.send_wifi_debug_logs) { + const user_manager::User* user = + user_manager::UserManager::Get()->GetActiveUser(); + const auto account_identifier = + cryptohome::CreateAccountIdentifierFromAccountId( + user ? user->GetAccountId() : EmptyAccountId()); + + binary_log_files_reader_.GetFeedbackBinaryLogs( + account_identifier, debugd::FeedbackBinaryLogType::WIFI_FIRMWARE_DUMP, + base::BindOnce(&FeedbackService::OnBinaryLogFilesFetched, this, params, + feedback_data, barrier_closure)); + } +} + +void FeedbackService::OnBinaryLogFilesFetched( + const FeedbackParams& params, + scoped_refptr<feedback::FeedbackData> feedback_data, + base::RepeatingClosure barrier_closure_callback, + feedback::BinaryLogFilesReader::BinaryLogsResponse binary_logs_response) { + if (binary_logs_response) { + for (auto& item : *binary_logs_response) { + feedback_data->AddFile(GetAttachmentName(item.first).data(), + std::move(item.second)); + } + } + std::move(barrier_closure_callback).Run(); } #endif // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/extensions/browser/api/feedback_private/feedback_service.h b/extensions/browser/api/feedback_private/feedback_service.h index ef8a43f..f9bbbfa40 100644 --- a/extensions/browser/api/feedback_private/feedback_service.h +++ b/extensions/browser/api/feedback_private/feedback_service.h
@@ -5,6 +5,8 @@ #ifndef EXTENSIONS_BROWSER_API_FEEDBACK_PRIVATE_FEEDBACK_SERVICE_H_ #define EXTENSIONS_BROWSER_API_FEEDBACK_PRIVATE_FEEDBACK_SERVICE_H_ +#include <memory> + #include "base/files/file_path.h" #include "base/functional/bind.h" #include "base/memory/raw_ptr.h" @@ -16,6 +18,10 @@ #include "components/feedback/system_logs/system_logs_fetcher.h" #include "extensions/browser/api/feedback_private/feedback_private_delegate.h" +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "chromeos/ash/components/dbus/debug_daemon/binary_log_files_reader.h" +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + namespace feedback { class FeedbackData; } // namespace feedback @@ -94,27 +100,37 @@ const FeedbackParams& params, scoped_refptr<feedback::FeedbackData> feedback_data, std::unique_ptr<system_logs::SystemLogsResponse> sys_info); + void OnAllLogsFetched(const FeedbackParams& params, + scoped_refptr<feedback::FeedbackData> feedback_data); + #if BUILDFLAG(IS_CHROMEOS_ASH) // Gets logs that aren't covered by FetchSystemInformation, but should be // included in the feedback report. These currently consist of the Intel Wi-Fi // debug logs (if they exist). void FetchExtraLogs(const FeedbackParams& params, scoped_refptr<feedback::FeedbackData> feedback_data); + // Receive binary log files fetched from debugd. + void OnBinaryLogFilesFetched( + const FeedbackParams& params, + scoped_refptr<feedback::FeedbackData> feedback_data, + base::RepeatingClosure barrier_closure_callback, + feedback::BinaryLogFilesReader::BinaryLogsResponse binary_logs); void OnExtraLogsFetched(const FeedbackParams& params, scoped_refptr<feedback::FeedbackData> feedback_data); void OnLacrosHistogramsFetched( const FeedbackParams& params, scoped_refptr<feedback::FeedbackData> feedback_data, const std::string& compressed_histograms); - // Root file path for log files. It can be overwritten for testing purpose. - base::FilePath log_file_root_{FILE_PATH_LITERAL("/var/log/")}; #endif // BUILDFLAG(IS_CHROMEOS_ASH) - void OnAllLogsFetched(const FeedbackParams& params, - scoped_refptr<feedback::FeedbackData> feedback_data); raw_ptr<content::BrowserContext, AcrossTasksDanglingUntriaged> browser_context_; raw_ptr<FeedbackPrivateDelegate, AcrossTasksDanglingUntriaged> delegate_; +#if BUILDFLAG(IS_CHROMEOS_ASH) + // Root file path for log files. It can be overwritten for testing purpose. + base::FilePath log_file_root_{FILE_PATH_LITERAL("/var/log/")}; + feedback::BinaryLogFilesReader binary_log_files_reader_; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) }; } // namespace extensions
diff --git a/extensions/browser/api/feedback_private/feedback_service_unittest.cc b/extensions/browser/api/feedback_private/feedback_service_unittest.cc index 648d6be..a905201 100644 --- a/extensions/browser/api/feedback_private/feedback_service_unittest.cc +++ b/extensions/browser/api/feedback_private/feedback_service_unittest.cc
@@ -25,6 +25,13 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h" +#include "components/user_manager/fake_user_manager.h" +#include "components/user_manager/scoped_user_manager.h" +#include "components/user_manager/user_manager.h" +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + namespace extensions { using feedback::FeedbackData; @@ -99,16 +106,26 @@ }; #if BUILDFLAG(IS_CHROMEOS_ASH) -bool AttachmentExists(const std::string& name, - const scoped_refptr<FeedbackData>& feedback_data) { +const FeedbackCommon::AttachedFile* FindAttachment( + std::string_view name, + const scoped_refptr<FeedbackData>& feedback_data) { size_t num_attachments = feedback_data->attachments(); for (size_t i = 0; i < num_attachments; i++) { const FeedbackCommon::AttachedFile* file = feedback_data->attachment(i); - if (!std::strcmp(name.c_str(), file->name.c_str())) { - return true; + if (file->name == name) { + return file; } } - return false; + return nullptr; +} + +void VerifyAttachment(std::string_view name, + std::string_view data, + const scoped_refptr<FeedbackData>& feedback_data) { + const auto* attachment = FindAttachment(name, feedback_data); + ASSERT_TRUE(attachment); + EXPECT_EQ(name, attachment->name); + EXPECT_EQ(data, attachment->data); } #endif // BUILDFLAG(IS_CHROMEOS_ASH) @@ -126,6 +143,11 @@ test_shared_loader_factory_); feedback_data_ = base::MakeRefCounted<FeedbackData>( mock_uploader_->AsWeakPtr(), nullptr); +#if BUILDFLAG(IS_CHROMEOS_ASH) + auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>(); + scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( + std::move(fake_user_manager)); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) } ~FeedbackServiceTest() override = default; @@ -185,18 +207,27 @@ EXPECT_CALL(*mock_delegate, FetchSystemInformation(_, _)).Times(1); EXPECT_CALL(*mock_delegate, FetchExtraLogs(_, _)).Times(1); + if (send_wifi_debug_logs) { + ash::DebugDaemonClient::InitializeFake(); + } auto feedback_service = base::MakeRefCounted<FeedbackService>( browser_context(), mock_delegate.get()); feedback_service->SetLogFilesRootPathForTesting(scoped_temp_dir_.GetPath()); RunUntilFeedbackIsSent(feedback_service, params, mock_callback.Get()); + if (ash::DebugDaemonClient::Get()) { + ash::DebugDaemonClient::Shutdown(); + } EXPECT_EQ(1u, feedback_data_->sys_info()->count(kFakeKey)); // Verify the attachment is added if and only if send_wifi_debug_logs is // true. - EXPECT_EQ( - send_wifi_debug_logs, - AttachmentExists("iwlwifi_firmware_dumps.tar.zst", feedback_data_)); + constexpr char kWifiDumpName[] = "iwlwifi_firmware_dumps.tar.zst"; + if (send_wifi_debug_logs) { + VerifyAttachment(kWifiDumpName, "TestData", feedback_data_); + } else { + EXPECT_FALSE(FindAttachment(kWifiDumpName, feedback_data_)); + } } #endif // BUILDFLAG(IS_CHROMEOS_ASH) @@ -209,6 +240,10 @@ task_environment()->RunUntilIdle(); } +#if BUILDFLAG(IS_CHROMEOS_ASH) + std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + base::ScopedTempDir scoped_temp_dir_; network::TestURLLoaderFactory test_url_loader_factory_; scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
diff --git a/extensions/common/api/bluetooth_private.idl b/extensions/common/api/bluetooth_private.idl index 59ab4d89..33084d1 100644 --- a/extensions/common/api/bluetooth_private.idl +++ b/extensions/common/api/bluetooth_private.idl
@@ -58,7 +58,8 @@ alreadyExists, notConnected, doesNotExist, - invalidArgs + invalidArgs, + nonAuthTimeout }; // Valid pairing responses.
diff --git a/extensions/common/mojom/api_permission_id.mojom b/extensions/common/mojom/api_permission_id.mojom index 5b72acba..a09ad78 100644 --- a/extensions/common/mojom/api_permission_id.mojom +++ b/extensions/common/mojom/api_permission_id.mojom
@@ -282,6 +282,7 @@ kEnterpriseKioskInput = 255, kOdfsConfigPrivate = 256, kChromeOSManagementAudio = 257, + kChromeOSDiagnosticsNetworkInfoForMlab = 258, // Add new entries at the end of the enum and be sure to update the // "ExtensionPermission3" enum in
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl index fcd166a..3a46b1e 100644 --- a/infra/config/generated/testing/variants.pyl +++ b/infra/config/generated/testing/variants.pyl
@@ -267,32 +267,32 @@ }, 'LACROS_VERSION_SKEW_CANARY': { 'identifier': 'Lacros version skew testing ash canary', - 'description': 'Run with ash-chrome version 125.0.6415.0', + 'description': 'Run with ash-chrome version 125.0.6416.0', 'args': [ - '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome', + '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome', ], 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip', - 'location': 'lacros_version_skew_tests_v125.0.6415.0', - 'revision': 'version:125.0.6415.0', + 'location': 'lacros_version_skew_tests_v125.0.6416.0', + 'revision': 'version:125.0.6416.0', }, ], }, }, 'LACROS_VERSION_SKEW_DEV': { 'identifier': 'Lacros version skew testing ash dev', - 'description': 'Run with ash-chrome version 125.0.6398.0', + 'description': 'Run with ash-chrome version 125.0.6411.0', 'args': [ - '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome', + '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome', ], 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip', - 'location': 'lacros_version_skew_tests_v125.0.6398.0', - 'revision': 'version:125.0.6398.0', + 'location': 'lacros_version_skew_tests_v125.0.6411.0', + 'revision': 'version:125.0.6411.0', }, ], },
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json index ca06615..25b411b 100644 --- a/infra/config/targets/lacros-version-skew-variants.json +++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,32 +1,32 @@ { "LACROS_VERSION_SKEW_CANARY": { "args": [ - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "identifier": "Lacros version skew testing ash canary", "swarming": { "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ] } }, "LACROS_VERSION_SKEW_DEV": { "args": [ - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "identifier": "Lacros version skew testing ash dev", "swarming": { "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ] }
diff --git a/internal b/internal index 3dff879..f50f32a 160000 --- a/internal +++ b/internal
@@ -1 +1 @@ -Subproject commit 3dff8792f0d51d1790b512505334cdc668e54606 +Subproject commit f50f32ae0208beb1302fec22582ab6c8d36ba5eb
diff --git a/ios/chrome/browser/ui/autofill/BUILD.gn b/ios/chrome/browser/ui/autofill/BUILD.gn index daa8be6..b16cb09a6 100644 --- a/ios/chrome/browser/ui/autofill/BUILD.gn +++ b/ios/chrome/browser/ui/autofill/BUILD.gn
@@ -197,6 +197,7 @@ "//build:branding_buildflags", "//components/autofill/core/common:features", "//components/autofill/ios/browser:autofill_test_bundle_data", + "//components/autofill/ios/common", "//components/strings", "//components/sync/base:features", "//ios/chrome/app/strings",
diff --git a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn index 06a379a1..d94947e5 100644 --- a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn +++ b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
@@ -41,6 +41,7 @@ "//ios/chrome/browser/shared/ui/table_view:table_view", "//ios/chrome/browser/shared/ui/table_view:utils", "//ios/chrome/browser/ui/autofill/cells:cells", + "//ios/chrome/common/ui/colors:colors", "//ui/base:base", ] frameworks = [ "UIKit.framework" ] @@ -178,3 +179,19 @@ sources = [ "card_unmask_authentication_selection_mutator_bridge_target.h" ] public_deps = [ "//ios/chrome/browser/autofill/model/authentication:card_unmask_challenge_option_ios" ] } + +source_set("eg2_tests") { + configs += [ "//build/config/ios:xctest_config" ] + testonly = true + sources = [ "card_unmask_authentication_egtest.mm" ] + deps = [ + "//components/autofill/core/browser:test_support", + "//components/strings:components_strings_grit", + "//ios/chrome/app/strings", + "//ios/chrome/browser/ui/autofill:eg_test_support+eg2", + "//ios/chrome/test/earl_grey:eg_test_support+eg2", + "//ios/testing/earl_grey:eg_test_support+eg2", + "//net:test_support", + ] + frameworks = [ "UIKit.framework" ] +}
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm new file mode 100644 index 0000000..092bb20 --- /dev/null +++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm
@@ -0,0 +1,275 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <UIKit/UIKit.h> +#import <XCTest/XCTest.h> + +#import "base/strings/sys_string_conversions.h" +#import "components/autofill/core/common/autofill_payments_features.h" +#import "components/strings/grit/components_strings.h" +#import "ios/chrome/browser/ui/autofill/autofill_app_interface.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ios/chrome/test/earl_grey/chrome_actions.h" +#import "ios/chrome/test/earl_grey/chrome_earl_grey.h" +#import "ios/chrome/test/earl_grey/chrome_matchers.h" +#import "ios/chrome/test/earl_grey/chrome_test_case.h" +#import "ios/testing/earl_grey/earl_grey_test.h" +#import "ios/testing/earl_grey/matchers.h" +#import "net/test/embedded_test_server/default_handlers.h" +#import "ui/base/l10n/l10n_util.h" +#import "ui/base/l10n/l10n_util_mac.h" + +namespace { + +// The test page url. +const char kCreditCardUrl[] = "/credit_card.html"; + +// A string on the credit_card.html page used to know when the page has loaded. +const char kAutofillTestString[] = "Autofill Test"; + +// The name of the card name form input. +const char kFormCardName[] = "CCName"; + +} // namespace + +// The url to intercept in order to inject card unmask responses. These tests +// do not make requests to the real server. +NSString* const kUnmaskCardRequestUrl = + @"https://payments.google.com/payments/apis-secure/creditcardservice/" + @"getrealpan?s7e_suffix=chromewallet"; + +// The fake response from the payment server when OTP, email and CVC card unmask +// options are available. +NSString* const kUnmaskCardResponseSuccessOtpAndEmailAndCvc = + @"{\"context_token\":\"__fake_context_token__\",\"idv_challenge_options\":[" + @"{\"sms_otp_challenge_option\":{\"challenge_id\":" + @"\"JGQ1YTkxM2ZjLWY4YTAtMTFlZS1hMmFhLWZmYjYwNWVjODcwMwo=\",\"masked_phone_" + @"number\":\"*******1234\",\"otp_length\":6}},{\"email_otp_challenge_" + @"option\":{\"challenge_id\":" + @"\"JDNhNTdlMzVhLWY4YTEtMTFlZS1hOTUwLWZiNzY3ZWM4ZWY3ZAo=\",\"masked_email_" + @"address\":\"a***b@gmail.com\",\"otp_length\":6}},{\"cvc_challenge_" + @"option\":{\"challenge_id\":\"hardcoded_3CSC_challenge_id\",\"cvc_" + @"length\":3,\"cvc_position\":\"CVC_POSITION_BACK\"}}]}"; + +// The masked phone number associated with the OTP card unmask option. +NSString* const kUnmaskOptionMaskedPhoneNumber = @"*******1234"; + +// The masked email associated with the email card unmask option. +NSString* const kUnmaskOptionMaskedEmailAddress = @"a***b@gmail.com"; + +// The length of the cvc challenge as shown in the challenge message. +NSString* const kUnmaskOptionCvcLength = @"3"; + +// Matcher for a navigation bar with the "Verification" title. +id<GREYMatcher> CardUnmaskPromptNavigationBarTitle() { + return chrome_test_util::NavigationBarTitleWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION); +} + +// Matcher for the text message challenge option label. +id<GREYMatcher> CardUnmaskTextMessageChallengeOptionLabel() { + return chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_AUTHENTICATION_MODE_GET_TEXT_MESSAGE); +} + +// Matcher for the CVC challenge option label. +id<GREYMatcher> CardUnmaskCvcChallengeOptionLabel() { + return chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_AUTHENTICATION_MODE_SECURITY_CODE); +} + +// Matcher for the "Send" button. +id<GREYMatcher> CardUnmaskAuthenticationSelectionSendButton() { + return grey_allOf( + chrome_test_util::ButtonWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND), + grey_not(grey_accessibilityTrait(UIAccessibilityTraitNotEnabled)), nil); +} + +// Matcher for the "Cancel" button. +id<GREYMatcher> CardUnmaskAuthenticationSelectionCancelButton() { + return grey_allOf( + chrome_test_util::CancelButton(), + grey_not(grey_accessibilityTrait(UIAccessibilityTraitNotEnabled)), nil); +} + +@interface CardUnmaskAuthenticationSelectionEgtest : ChromeTestCase +@end + +@implementation CardUnmaskAuthenticationSelectionEgtest { + NSString* _enrolledCardNameAndLastFour; +} + +#pragma mark - Setup + +- (AppLaunchConfiguration)appConfigurationForTestCase { + AppLaunchConfiguration config; + config.features_enabled.push_back( + autofill::features::kAutofillEnableVirtualCards); + return config; +} + +- (void)setUp { + [super setUp]; + [AutofillAppInterface setUpSaveCardInfobarEGTestHelper]; + _enrolledCardNameAndLastFour = + [AutofillAppInterface saveMaskedCreditCardEnrolledInVirtualCard]; + [self setUpServer]; + [self setUpTestPage]; +} + +- (void)setUpServer { + net::test_server::RegisterDefaultHandlers(self.testServer); + GREYAssertTrue(self.testServer->Start(), @"Failed to start test server."); +} + +- (void)setUpTestPage { + [ChromeEarlGrey loadURL:self.testServer->GetURL(kCreditCardUrl)]; + [ChromeEarlGrey waitForWebStateContainingText:kAutofillTestString]; + + [AutofillAppInterface considerCreditCardFormSecureForTesting]; +} + +- (void)tearDown { + [AutofillAppInterface clearAllServerDataForTesting]; + [AutofillAppInterface tearDownSaveCardInfobarEGTestHelper]; + [super tearDown]; +} + +- (void)showAuthenticationSelection { + // Tap on the card name field in the web content. + [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()] + performAction:chrome_test_util::TapWebElementWithId(kFormCardName)]; + + // Wait for the payments bottom sheet to appear. + id<GREYMatcher> paymentsBottomSheetVirtualCard = grey_accessibilityID( + [NSString stringWithFormat:@"%@ %@", _enrolledCardNameAndLastFour, + @"Virtual card"]); + [ChromeEarlGrey + waitForUIElementToAppearWithMatcher:paymentsBottomSheetVirtualCard]; + [[EarlGrey selectElementWithMatcher:paymentsBottomSheetVirtualCard] + performAction:grey_tap()]; + [[EarlGrey selectElementWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_IOS_PAYMENT_BOTTOM_SHEET_CONTINUE)] + performAction:grey_tap()]; + + // Wait for the progress dialog to appear. + [ChromeEarlGrey waitForUIElementToAppearWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_UNMASK_PROGRESS_DIALOG_TITLE)]; + + // Inject the card unmask response with card unmask options. + [AutofillAppInterface + setPaymentsResponse:kUnmaskCardResponseSuccessOtpAndEmailAndCvc + forRequest:kUnmaskCardRequestUrl + withErrorCode:net::HTTP_OK]; + + // Wait for the card unmask authentication selection to appear. + [ChromeEarlGrey + waitForUIElementToAppearWithMatcher:CardUnmaskPromptNavigationBarTitle()]; +} + +- (void)testCardUnmaskAuthenticationSelectionIsShownForVirtualCard { + [self showAuthenticationSelection]; + + // Verify that the card unmask prompt was shown. + [[EarlGrey + selectElementWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_AUTH_SELECTION_DIALOG_TITLE_MULTIPLE_OPTIONS)] + assertWithMatcher:grey_sufficientlyVisible()]; + [[EarlGrey + selectElementWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_ISSUER_CONFIRMATION_TEXT)] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Verify that the card unmask options are shown starting with text message + // (SMS OTP). + [[EarlGrey + selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()] + assertWithMatcher:grey_sufficientlyVisible()]; + [[EarlGrey selectElementWithMatcher:chrome_test_util:: + StaticTextWithAccessibilityLabel( + kUnmaskOptionMaskedPhoneNumber)] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Verify that the email OTP unmask option is shown. + [[EarlGrey selectElementWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabelId( + IDS_AUTOFILL_AUTHENTICATION_MODE_GET_EMAIL)] + assertWithMatcher:grey_sufficientlyVisible()]; + [[EarlGrey selectElementWithMatcher:chrome_test_util:: + StaticTextWithAccessibilityLabel( + kUnmaskOptionMaskedEmailAddress)] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Verify that the CVC unmask option is shown. + [[EarlGrey selectElementWithMatcher:CardUnmaskCvcChallengeOptionLabel()] + assertWithMatcher:grey_sufficientlyVisible()]; + [[EarlGrey + selectElementWithMatcher: + chrome_test_util::StaticTextWithAccessibilityLabel(l10n_util::GetNSStringF( + IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_CVC_CHALLENGE_INFO, + base::SysNSStringToUTF16(kUnmaskOptionCvcLength), + l10n_util::GetStringUTF16( + IDS_AUTOFILL_CARD_UNMASK_PROMPT_SECURITY_CODE_POSITION_BACK_OF_CARD)))] + assertWithMatcher:grey_sufficientlyVisible()]; +} + +- (void)testCardUnmaskAuthenticationSelectionCancel { + [self showAuthenticationSelection]; + + // Tap the cancel button. + [[EarlGrey + selectElementWithMatcher:CardUnmaskAuthenticationSelectionCancelButton()] + performAction:grey_tap()]; + + // Expect the card unmask authentication selection view to disappear. + [ChromeEarlGrey waitForUIElementToDisappearWithMatcher: + CardUnmaskPromptNavigationBarTitle()]; +} + +- (void)testCardUnmaskAuthenticationSelectionAcceptanceButtonLabel { + [self showAuthenticationSelection]; + + // Verify selecting text message sets the acceptance button label to "Send". + [[EarlGrey + selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()] + performAction:grey_tap()]; + [[EarlGrey + selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Verify selecting CVC sets the acceptance button label to "Continue". + [[EarlGrey selectElementWithMatcher:CardUnmaskCvcChallengeOptionLabel()] + performAction:grey_tap()]; + [[EarlGrey + selectElementWithMatcher: + grey_allOf( + chrome_test_util::ButtonWithAccessibilityLabelId( + IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_CONTINUE), + grey_not(grey_accessibilityTrait(UIAccessibilityTraitNotEnabled)), + nil)] assertWithMatcher:grey_sufficientlyVisible()]; +} + +- (void)testCardUnmaskAuthenticationSelectionShowsActivityIndicatorView { + [self showAuthenticationSelection]; + + // Select the text message otp challenge option. + [[EarlGrey + selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()] + performAction:grey_tap()]; + [[EarlGrey + selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()] + performAction:grey_tap()]; + + // Verify the activity indicator has been set. + [[EarlGrey + selectElementWithMatcher:grey_kindOfClassName(@"UIActivityIndicatorView")] + assertWithMatcher:grey_sufficientlyVisible()]; +} + +@end
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm index d808d6f..abe051c 100644 --- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm +++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
@@ -41,7 +41,15 @@ } CardUnmaskAuthenticationSelectionMediator:: - ~CardUnmaskAuthenticationSelectionMediator() = default; + ~CardUnmaskAuthenticationSelectionMediator() { + if (!was_dismissed_ && model_controller_) { + // Our coordinator is stopping (e.g. closing Chromium). We must call + // OnDialogClosed on the model_controller_ before this mediator is + // destroyed. + model_controller_->OnDialogClosed( + /*user_closed_dialog=*/true, /*server_success=*/false); + } +} // Implementation of CardUnmaskAuthenticationSelectionMutatorBridgeTarget // follows:
diff --git a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm index 0ab0abc..5498eb3 100644 --- a/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm +++ b/ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_view_controller.mm
@@ -8,16 +8,19 @@ #import "components/strings/grit/components_strings.h" #import "ios/chrome/browser/shared/ui/list_model/list_model.h" #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_edit_item.h" +#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.h" #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h" #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_content.h" #import "ios/chrome/browser/ui/autofill/authentication/otp_input_dialog_mutator.h" #import "ios/chrome/browser/ui/autofill/cells/card_unmask_header_item.h" +#import "ios/chrome/common/ui/colors/semantic_color_names.h" #import "ui/base/l10n/l10n_util.h" namespace { typedef NS_ENUM(NSInteger, SectionIdentifier) { SectionIdentifierContent = kSectionIdentifierEnumZero, + SectionIdentifierError, }; typedef NS_ENUM(NSInteger, ItemIdentifier) { @@ -37,6 +40,7 @@ UITableViewDiffableDataSource<NSNumber*, NSNumber*>* _dataSource; BOOL _contentSet; NSString* _inputValue; + NSString* _errorTitle; } - (instancetype)init { @@ -53,25 +57,48 @@ initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(didTapCancelButton)]; - self.navigationItem.rightBarButtonItem = - [[UIBarButtonItem alloc] initWithTitle:_content.confirmButtonLabel - style:UIBarButtonItemStyleDone - target:self - action:@selector(didTapConfirmButton)]; - // Enable the confirm button only after a valid OTP has been entered. - self.navigationItem.rightBarButtonItem.enabled = NO; - self.tableView.allowsSelection = NO; + self.navigationItem.rightBarButtonItem = [self createConfirmButton]; [self loadModel]; } #pragma mark - UITableViewDelegate +- (CGFloat)tableView:(UITableView*)tableView + heightForHeaderInSection:(NSInteger)section { + SectionIdentifier sectionIdentifier = static_cast<SectionIdentifier>( + [_dataSource sectionIdentifierForIndex:section].integerValue); + switch (sectionIdentifier) { + case SectionIdentifierContent: + return UITableViewAutomaticDimension; + case SectionIdentifierError: + return ChromeTableViewHeightForHeaderInSection(sectionIdentifier); + } +} + - (UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section { - CardUnmaskHeaderView* view = - DequeueTableViewHeaderFooter<CardUnmaskHeaderView>(self.tableView); - view.titleLabel.text = _content.windowTitle; - return view; + SectionIdentifier sectionIdentifier = static_cast<SectionIdentifier>( + [_dataSource sectionIdentifierForIndex:section].integerValue); + switch (sectionIdentifier) { + case SectionIdentifierContent: { + CardUnmaskHeaderView* view = + DequeueTableViewHeaderFooter<CardUnmaskHeaderView>(self.tableView); + view.titleLabel.text = _content.windowTitle; + return view; + } + case SectionIdentifierError: { + if (!_errorTitle) { + return nil; + } + TableViewTextHeaderFooterView* errorMessage = + DequeueTableViewHeaderFooter<TableViewTextHeaderFooterView>( + self.tableView); + [errorMessage setSubtitle:_errorTitle + withColor:[UIColor colorNamed:kRedColor]]; + [errorMessage setForceIndents:YES]; + return errorMessage; + } + } } #pragma mark - PaymentsSuggestionBottomSheetConsumer @@ -88,13 +115,20 @@ } - (void)showPendingState { - // TODO(crbug.com/303715678): Handle pending state (after the confirm button - // is clicked). + UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; + UIBarButtonItem* pendingButton = + [[UIBarButtonItem alloc] initWithCustomView:activityIndicator]; + self.navigationItem.rightBarButtonItem = pendingButton; + [activityIndicator startAnimating]; + [self.tableView setUserInteractionEnabled:NO]; } - (void)showInvalidState:(NSString*)invalidLabelText { - // TODO(crbug.com/303715678): Handle error state (after the confirm button is - // clicked and server returns a result). + self.navigationItem.rightBarButtonItem = [self createConfirmButton]; + _errorTitle = invalidLabelText; + [self.tableView setUserInteractionEnabled:YES]; + [self reloadModel]; } #pragma mark - Private @@ -104,6 +138,7 @@ CHECK(_contentSet); RegisterTableViewHeaderFooter<CardUnmaskHeaderView>(self.tableView); RegisterTableViewCell<TableViewTextEditCell>(self.tableView); + RegisterTableViewHeaderFooter<TableViewTextHeaderFooterView>(self.tableView); __weak __typeof(self) weakSelf = self; _dataSource = [[UITableViewDiffableDataSource alloc] initWithTableView:self.tableView @@ -121,7 +156,15 @@ [snapshot appendSectionsWithIdentifiers:@[ @(SectionIdentifierContent) ]]; [snapshot appendItemsWithIdentifiers:@[ @(ItemTypeTextField) ] intoSectionWithIdentifier:@(SectionIdentifierContent)]; - [_dataSource applySnapshot:snapshot animatingDifferences:NO]; + [snapshot appendSectionsWithIdentifiers:@[ @(SectionIdentifierError) ]]; + [_dataSource applySnapshot:snapshot animatingDifferences:YES]; +} + +- (void)reloadModel { + NSDiffableDataSourceSnapshot* snapshot = [_dataSource snapshot]; + [snapshot reconfigureItemsWithIdentifiers:@[ @(ItemTypeTextField) ]]; + [snapshot reloadSectionsWithIdentifiers:@[ @(SectionIdentifierError) ]]; + [_dataSource applySnapshot:snapshot animatingDifferences:YES]; } // Returns the appropriate cell for the table view. @@ -131,7 +174,14 @@ TableViewTextEditCell* cell = DequeueTableViewCell<TableViewTextEditCell>(self.tableView); [cell setIdentifyingIcon:nil]; - [cell setIcon:TableViewTextEditItemIconTypeEdit]; + if (_errorTitle) { + [cell setIcon:TableViewTextEditItemIconTypeError]; + cell.textField.text = _inputValue; + cell.textField.textColor = [UIColor colorNamed:kRedColor]; + } else { + [cell setIcon:TableViewTextEditItemIconTypeEdit]; + cell.textField.textColor = [UIColor colorNamed:kTextPrimaryColor]; + } cell.textField.placeholder = _content.textFieldPlaceholder; [cell.textField addTarget:self action:@selector(textFieldDidChange:) @@ -143,6 +193,12 @@ } - (void)textFieldDidChange:(UITextField*)textField { + // Reset error state. + if (_errorTitle) { + _errorTitle = nil; + [self reloadModel]; + } + _inputValue = textField.text; [self didChangeOtpInputText]; } @@ -163,4 +219,15 @@ [_mutator onOtpInputChanges:_inputValue]; } +- (UIBarButtonItem*)createConfirmButton { + UIBarButtonItem* confirmButton = + [[UIBarButtonItem alloc] initWithTitle:_content.confirmButtonLabel + style:UIBarButtonItemStyleDone + target:self + action:@selector(didTapConfirmButton)]; + // Enable the confirm button only after a valid OTP has been entered. + confirmButton.enabled = NO; + return confirmButton; +} + @end
diff --git a/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm b/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm index dcb7674..26e31c0 100644 --- a/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm +++ b/ios/chrome/browser/ui/autofill/save_card_infobar_egtest.mm
@@ -5,8 +5,10 @@ #import <memory> #import "base/test/ios/wait_util.h" +#import "base/time/time.h" #import "build/branding_buildflags.h" #import "components/autofill/core/common/autofill_features.h" +#import "components/autofill/ios/common/features.h" #import "components/strings/grit/components_strings.h" #import "ios/chrome/browser/metrics/model/metrics_app_interface.h" #import "ios/chrome/browser/ui/autofill/autofill_app_interface.h" @@ -104,12 +106,8 @@ // Some tests are not compatible with explicit save prompts for addresses. - (AppLaunchConfiguration)appConfigurationForTestCase { AppLaunchConfiguration config; - if ([self isRunningTest:@selector(testUserData_LocalSave_UserAccepts)] || - [self - isRunningTest:@selector(testOfferLocalSave_FullData_RequestFails)] || - [self isRunningTest:@selector(testUserData_LocalSave_UserDeclines)] || - [self isRunningTest:@selector - (testOfferLocalSave_FullData_PaymentsDeclines)]) { + if ([self isRunningTest:@selector(testStickySavePromptJourney)]) { + config.features_enabled.push_back(kAutofillStickyInfobarIos); } return config; } @@ -605,4 +603,81 @@ @"Save card infobar should not show."); } +// Tests the sticky credit card prompt journey where the prompt remains there +// when navigating without an explicit user gesture, and then the prompt is +// dismissed when navigating with a user gesture. Test with the credit card save +// prompt but the type of credit card prompt doesn't matter in this test case. +- (void)testStickySavePromptJourney { + const GURL testPageURL = + web::test::HttpServer::MakeUrl(kCreditCardUploadForm); + + [ChromeEarlGrey loadURL:testPageURL]; + + // Set up the Google Payments server response. + [AutofillAppInterface setPaymentsResponse:kResponseGetUploadDetailsFailure + forRequest:kURLGetUploadDetailsRequest + withErrorCode:net::HTTP_OK]; + + [AutofillAppInterface resetEventWaiterForEvents:@[ + @(CreditCardSaveManagerObserverEvent::kOnDecideToRequestUploadSaveCalled), + @(CreditCardSaveManagerObserverEvent:: + kOnReceivedGetUploadDetailsResponseCalled), + @(CreditCardSaveManagerObserverEvent::kOnOfferLocalSaveCalled) + ] + timeout:kWaitForDownloadTimeout]; + [self fillAndSubmitForm]; + GREYAssertTrue([AutofillAppInterface waitForEvents], + @"Event was not triggered"); + + // Wait until the save card infobar becomes visible. + GREYAssert( + [self waitForUIElementToAppearWithMatcher:LocalBannerLabelsMatcher()], + @"Save card infobar failed to show."); + + [AutofillAppInterface resetEventWaiterForEvents:@[ + @(CreditCardSaveManagerObserverEvent::kOnStrikeChangeCompleteCalled) + ] + timeout:kWaitForDownloadTimeout]; + + { + // Reloading page from script shouldn't dismiss the infobar. + NSString* script = @"location.reload();"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Assigning url from script to the page aka open an url shouldn't dismiss + // the infobar. + NSString* script = @"window.location.assign(window.location.href);"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Pushing new history entry without reloading content shouldn't dismiss the + // infobar. + NSString* script = @"history.pushState({}, '', 'destination2.html');"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Replacing history entry without reloading content shouldn't dismiss the + // infobar. + NSString* script = @"history.replaceState({}, '', 'destination3.html');"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + + // Wait some time for things to settle. + base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(200)); + + // Verify that the prompt is still there after the non-user initiated + // navigations. + [[EarlGrey selectElementWithMatcher:LocalBannerLabelsMatcher()] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Navigate with an emulated user gesture. + [ChromeEarlGrey loadURL:testPageURL]; + + // Wait until the save card infobar disappears. + GREYAssertTrue( + [self waitForUIElementToDisappearWithMatcher:LocalBannerLabelsMatcher()], + @"Save card infobar failed to disappear."); +} + @end
diff --git a/ios/chrome/browser/ui/autofill/save_profile_egtest.mm b/ios/chrome/browser/ui/autofill/save_profile_egtest.mm index a3f1594c..a963c2b 100644 --- a/ios/chrome/browser/ui/autofill/save_profile_egtest.mm +++ b/ios/chrome/browser/ui/autofill/save_profile_egtest.mm
@@ -7,7 +7,9 @@ #import "base/strings/sys_string_conversions.h" #import "base/strings/utf_string_conversions.h" #import "base/test/ios/wait_util.h" +#import "base/time/time.h" #import "components/autofill/core/common/autofill_features.h" +#import "components/autofill/ios/common/features.h" #import "components/strings/grit/components_strings.h" #import "ios/chrome/browser/signin/model/fake_system_identity.h" #import "ios/chrome/browser/ui/authentication/signin_earl_grey.h" @@ -114,14 +116,18 @@ config.features_disabled.push_back( autofill::features::test::kAutofillServerCommunication); + if ([self isRunningTest:@selector(testStickySavePromptJourney)]) { + config.features_enabled.push_back(kAutofillStickyInfobarIos); + } + return config; } #pragma mark - Test helper methods // Fills the president profile in the form by clicking on the button, submits -// the form and accepts the save address banner. -- (void)fillPresidentProfileAndShowSaveModal { +// the form to the save address profile infobar. +- (void)fillPresidentProfileAndShowSaveInfobar { GREYAssertTrue(self.testServer->Start(), @"Server did not start."); [ChromeEarlGrey loadURL:self.testServer->GetURL(kProfileForm)]; @@ -132,6 +138,10 @@ [ChromeEarlGrey tapWebStateElementWithID:@"fill_profile_president"]; [ChromeEarlGrey tapWebStateElementWithID:@"submit_profile"]; [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:YES]; +} + +- (void)fillPresidentProfileAndShowSaveModal { + [self fillPresidentProfileAndShowSaveInfobar]; // Accept the banner. [[EarlGrey selectElementWithMatcher:BannerButtonMatcher()] @@ -357,4 +367,50 @@ [SigninEarlGrey signOut]; } +// Tests the sticky address prompt journey where the prompt remains there when +// navigating without an explicit user gesture, and then the prompt is dismissed +// when navigating with a user gesture. Test with the address save prompt but +// the type of address prompt doesn't matter in this test case. +- (void)testStickySavePromptJourney { + [self fillPresidentProfileAndShowSaveInfobar]; + + { + // Reloading page from script shouldn't dismiss the infobar. + NSString* script = @"location.reload();"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Assigning url from script to the page aka open an url shouldn't dismiss + // the infobar. + NSString* script = @"window.location.assign(window.location.href);"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Pushing new history entry without reloading content shouldn't dismiss the + // infobar. + NSString* script = @"history.pushState({}, '', 'destination2.html');"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + { + // Replacing history entry without reloading content shouldn't dismiss the + // infobar. + NSString* script = @"history.replaceState({}, '', 'destination3.html');"; + [ChromeEarlGrey evaluateJavaScriptForSideEffect:script]; + } + + // Wait some time for things to settle. + base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(200)); + + // Verify that the prompt is still there after the non-user initiated + // navigations. + [[EarlGrey selectElementWithMatcher:grey_accessibilityID( + kInfobarBannerViewIdentifier)] + assertWithMatcher:grey_sufficientlyVisible()]; + + // Navigate with an emulated user gesture and verify that dismisses the + // prompt. + [ChromeEarlGrey loadURL:self.testServer->GetURL(kProfileForm)]; + [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:NO]; +} + @end
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn index 62b5a65..feee13e5 100644 --- a/ios/chrome/test/earl_grey2/BUILD.gn +++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -186,6 +186,7 @@ "//ios/chrome/browser/plus_addresses/ui:eg2_tests", "//ios/chrome/browser/qr_scanner/ui_bundled:eg2_tests", "//ios/chrome/browser/settings/model/sync/utils:eg2_tests", + "//ios/chrome/browser/ui/autofill/authentication:eg2_tests", "//ios/chrome/browser/ui/autofill/bottom_sheet:eg2_tests", "//ios/chrome/browser/ui/autofill/form_input_accessory:eg2_tests", "//ios/chrome/browser/ui/bring_android_tabs:eg2_tests",
diff --git a/ios_internal b/ios_internal index b52f647..a5aea78 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit b52f647176b2a69101f532235ff01e26a03708c2 +Subproject commit a5aea7848cb458595e3fc715475154bc54ca53cd
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn index 5c85e84..bd8c193 100644 --- a/ipc/BUILD.gn +++ b/ipc/BUILD.gn
@@ -185,6 +185,12 @@ ] } +mojom("test_mojom") { + testonly = true + sources = [ "ipc_channel_mojo_unittest.test-mojom" ] + public_deps = [ "//mojo/public/mojom/base" ] +} + mojom_component("mojom") { output_prefix = "ipc_mojom" macro_prefix = "IPC_MOJOM" @@ -307,6 +313,7 @@ ":protobuf_support", ":run_all_unittests", ":test_interfaces", + ":test_mojom", ":test_proto", ":test_support", "//base",
diff --git a/ipc/ipc_channel_mojo_unittest.cc b/ipc/ipc_channel_mojo_unittest.cc index 893ebeb..2ecb28f5 100644 --- a/ipc/ipc_channel_mojo_unittest.cc +++ b/ipc/ipc_channel_mojo_unittest.cc
@@ -34,12 +34,14 @@ #include "base/synchronization/waitable_event.h" #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "base/test/test_io_thread.h" #include "base/test/test_shared_memory_util.h" #include "base/test/test_timeouts.h" #include "base/threading/thread.h" #include "build/build_config.h" +#include "ipc/ipc_channel_mojo_unittest.test-mojom.h" #include "ipc/ipc_message.h" #include "ipc/ipc_message_utils.h" #include "ipc/ipc_mojo_handle_attachment.h" @@ -53,6 +55,7 @@ #include "ipc/urgent_message_observer.h" #include "mojo/public/cpp/bindings/associated_receiver.h" #include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/features.h" #include "mojo/public/cpp/bindings/lib/validation_errors.h" #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "mojo/public/cpp/bindings/self_owned_associated_receiver.h" @@ -66,6 +69,7 @@ #include "ipc/ipc_platform_file_attachment_posix.h" #endif +namespace ipc_channel_mojo_unittest { namespace { void SendString(IPC::Sender* sender, const std::string& str) { @@ -1384,6 +1388,75 @@ DestroyProxy(); } +class ListenerWithClumsyBinder : public IPC::Listener { + public: + ListenerWithClumsyBinder() = default; + ~ListenerWithClumsyBinder() override = default; + + void RunUntilClientQuit() { run_loop_.Run(); } + + private: + // IPC::Listener: + bool OnMessageReceived(const IPC::Message& message) override { + run_loop_.Quit(); + return true; + } + + void OnAssociatedInterfaceRequest( + const std::string& interface_name, + mojo::ScopedInterfaceEndpointHandle handle) override { + // Ignore and drop the endpoint so it's closed. + } + + base::RunLoop run_loop_; +}; + +TEST_F(IPCChannelProxyMojoTest, DropAssociatedReceiverWithSyncCallInFlight) { + // Regression test for https://crbug.com/331636067. Verifies that endpoint + // lifetime is properly managed when associated endpoints are serialized into + // a message that gets dropped before transmission. + + Init("SyncCallToDroppedReceiver"); + ListenerWithClumsyBinder listener; + CreateProxy(&listener); + RunProxy(); + listener.RunUntilClientQuit(); + EXPECT_TRUE(WaitForClientShutdown()); + DestroyProxy(); +} + +DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE( + SyncCallToDroppedReceiver, + ChannelProxyClient) { + // Force-enable the fix, since ipc_tests doesn't initialize FeatureList. + const base::test::ScopedFeatureList kFeatures( + mojo::features::kMojoFixAssociatedHandleLeak); + + DummyListener listener; + CreateProxy(&listener); + RunProxy(); + + mojo::AssociatedRemote<mojom::Binder> binder; + proxy()->GetRemoteAssociatedInterface( + binder.BindNewEndpointAndPassReceiver()); + + // Wait for disconnection to be observed. This way we know any subsequent + // outgoing messages on `binder` will not be sent. + base::RunLoop loop; + binder.set_disconnect_handler(loop.QuitClosure()); + loop.Run(); + + // Send another endpoint over. This receiver will be dropped, and the remote + // should be properly notified of peer closure to terminate the sync call. The + // call should return false (because no reply), but shouldn't hang. + mojo::AssociatedRemote<mojom::Binder> another_binder; + binder->Bind(another_binder.BindNewEndpointAndPassReceiver()); + EXPECT_FALSE(another_binder->Ping()); + + SendString(proxy(), "ok bye"); + DestroyProxy(); +} + // TODO(https://crbug.com/1500560): Disabled for flaky behavior of forced // process termination. Will be re-enabled with a fix. TEST_F(IPCChannelProxyMojoTest, DISABLED_SyncAssociatedInterfacePipeError) { @@ -2061,3 +2134,4 @@ } } // namespace +} // namespace ipc_channel_mojo_unittest
diff --git a/ipc/ipc_channel_mojo_unittest.test-mojom b/ipc/ipc_channel_mojo_unittest.test-mojom new file mode 100644 index 0000000..7df7018d --- /dev/null +++ b/ipc/ipc_channel_mojo_unittest.test-mojom
@@ -0,0 +1,12 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module ipc_channel_mojo_unittest.mojom; + +import "mojo/public/mojom/base/generic_pending_associated_receiver.mojom"; + +interface Binder { + Bind(mojo_base.mojom.GenericPendingAssociatedReceiver receiver); + [Sync] Ping() => (); +};
diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc index 951c629..56c023e 100644 --- a/ipc/ipc_mojo_bootstrap.cc +++ b/ipc/ipc_mojo_bootstrap.cc
@@ -41,6 +41,7 @@ #include "mojo/public/cpp/bindings/associated_group.h" #include "mojo/public/cpp/bindings/associated_group_controller.h" #include "mojo/public/cpp/bindings/connector.h" +#include "mojo/public/cpp/bindings/features.h" #include "mojo/public/cpp/bindings/interface_endpoint_client.h" #include "mojo/public/cpp/bindings/interface_endpoint_controller.h" #include "mojo/public/cpp/bindings/interface_id.h" @@ -407,6 +408,22 @@ control_message_proxy_.NotifyPeerEndpointClosed(id, reason); } + void NotifyLocalEndpointOfPeerClosure(mojo::InterfaceId id) override { + if (!base::FeatureList::IsEnabled( + mojo::features::kMojoFixAssociatedHandleLeak)) { + return; + } + + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&ChannelAssociatedGroupController:: + NotifyLocalEndpointOfPeerClosure, + base::WrapRefCounted(this), id)); + return; + } + OnPeerAssociatedEndpointClosed(id, std::nullopt); + } + mojo::InterfaceEndpointController* AttachEndpointClient( const mojo::ScopedInterfaceEndpointHandle& handle, mojo::InterfaceEndpointClient* client,
diff --git a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc index 245ea07c..a7ac0f10 100644 --- a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc +++ b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
@@ -17,12 +17,14 @@ #include "base/task/sequenced_task_runner.h" #include "build/build_config.h" #include "cc/base/math_util.h" +#include "components/viz/common/resources/shared_image_format_utils.h" #include "gpu/GLES2/gl2extchromium.h" #include "gpu/command_buffer/client/client_shared_image.h" #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" #include "gpu/command_buffer/client/shared_image_interface.h" #include "gpu/command_buffer/common/gpu_memory_buffer_support.h" #include "gpu/command_buffer/common/shared_image_usage.h" +#include "media/base/format_utils.h" #include "media/base/media_switches.h" #include "media/base/video_frame.h" @@ -157,8 +159,9 @@ format_(format), coded_size_(coded_size), color_space_(color_space) { - // Currently only support ARGB and NV12. - CHECK(format == PIXEL_FORMAT_ARGB || format == PIXEL_FORMAT_NV12); + // Currently only support ARGB, ABGR and NV12. + CHECK(format == PIXEL_FORMAT_ARGB || format == PIXEL_FORMAT_ABGR || + format == PIXEL_FORMAT_NV12); } FrameResources::~FrameResources() { @@ -172,22 +175,12 @@ } } -gfx::BufferFormat GetBufferFormatForVideoPixelFormat(VideoPixelFormat format) { - switch (format) { - case PIXEL_FORMAT_ARGB: - return gfx::BufferFormat::RGBA_8888; - case PIXEL_FORMAT_NV12: - return gfx::BufferFormat::YUV_420_BIPLANAR; - default: - NOTREACHED_NORETURN(); - } -} - gfx::Size GetBufferSizeInPixelsForVideoPixelFormat( VideoPixelFormat format, const gfx::Size& coded_size) { switch (format) { case PIXEL_FORMAT_ARGB: + case PIXEL_FORMAT_ABGR: return coded_size; case PIXEL_FORMAT_NV12: // Align number of rows to 2, because it's required by YUV_420_BIPLANAR @@ -214,7 +207,7 @@ ; const gfx::BufferFormat buffer_format = - GetBufferFormatForVideoPixelFormat(format_); + VideoPixelFormatToGfxBufferFormat(format_).value(); const gfx::Size buffer_size_in_pixels = GetBufferSizeInPixelsForVideoPixelFormat(format_, coded_size_); @@ -288,11 +281,14 @@ } return true; } + case PIXEL_FORMAT_ABGR: case PIXEL_FORMAT_ARGB: { + const viz::SharedImageFormat image_format = + viz::GetSinglePlaneSharedImageFormat(buffer_format); shared_images_[0] = context->CreateSharedImage( - gpu_memory_buffer_.get(), viz::SinglePlaneFormat::kRGBA_8888, - color_space_, kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, - kSharedImageUsage, mailbox_holders_[0].sync_token); + gpu_memory_buffer_.get(), image_format, color_space_, + kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, kSharedImageUsage, + mailbox_holders_[0].sync_token); if (shared_images_[0]) { mailbox_holders_[0].mailbox = shared_images_[0]->mailbox(); mailbox_holders_[0].texture_target =
diff --git a/media/video/renderable_gpu_memory_buffer_video_frame_pool_unittest.cc b/media/video/renderable_gpu_memory_buffer_video_frame_pool_unittest.cc index d45f6fed..5c15e0ff 100644 --- a/media/video/renderable_gpu_memory_buffer_video_frame_pool_unittest.cc +++ b/media/video/renderable_gpu_memory_buffer_video_frame_pool_unittest.cc
@@ -15,6 +15,7 @@ #include "components/viz/test/test_context_provider.h" #include "gpu/command_buffer/client/client_shared_image.h" #include "gpu/config/gpu_finch_features.h" +#include "media/base/format_utils.h" #include "media/base/media_switches.h" #include "media/base/video_frame.h" #include "media/video/fake_gpu_memory_buffer.h" @@ -32,24 +33,13 @@ case media::PIXEL_FORMAT_NV12: return gfx::ColorSpace::CreateREC709(); case media::PIXEL_FORMAT_ARGB: + case media::PIXEL_FORMAT_ABGR: return gfx::ColorSpace::CreateSRGB(); default: NOTREACHED_NORETURN(); } } -gfx::BufferFormat GetBufferFormatForVideoPixelFormat( - media::VideoPixelFormat format) { - switch (format) { - case media::PIXEL_FORMAT_ARGB: - return gfx::BufferFormat::RGBA_8888; - case media::PIXEL_FORMAT_NV12: - return gfx::BufferFormat::YUV_420_BIPLANAR; - default: - NOTREACHED_NORETURN(); - } -} - class FakeContext : public RenderableGpuMemoryBufferVideoFramePool::Context { public: FakeContext() @@ -159,6 +149,12 @@ } case PIXEL_FORMAT_ARGB: { EXPECT_CALL(*context, + DoCreateSharedImage(viz::SinglePlaneFormat::kBGRA_8888, _, + _, _, _, _, _)); + break; + } + case PIXEL_FORMAT_ABGR: { + EXPECT_CALL(*context, DoCreateSharedImage(viz::SinglePlaneFormat::kRGBA_8888, _, _, _, _, _, _)); break; @@ -174,6 +170,7 @@ case PIXEL_FORMAT_NV12: { return nv12_multi_plane_ ? 1 : 2; } + case PIXEL_FORMAT_ABGR: case PIXEL_FORMAT_ARGB: { return 1; } @@ -192,7 +189,8 @@ TEST_P(RenderableGpuMemoryBufferVideoFramePoolTest, SimpleLifetimes) { base::test::SingleThreadTaskEnvironment task_environment; const gfx::Size size0(128, 256); - const gfx::BufferFormat format = GetBufferFormatForVideoPixelFormat(format_); + const gfx::BufferFormat format = + VideoPixelFormatToGfxBufferFormat(format_).value(); const gfx::ColorSpace color_space0 = GetColorSpaceForPixelFormat(format_); base::WeakPtr<FakeContext> context; @@ -252,7 +250,8 @@ TEST_P(RenderableGpuMemoryBufferVideoFramePoolTest, FrameFreedAfterPool) { base::test::SingleThreadTaskEnvironment task_environment; const gfx::Size size0(128, 256); - const gfx::BufferFormat format = GetBufferFormatForVideoPixelFormat(format_); + const gfx::BufferFormat format = + VideoPixelFormatToGfxBufferFormat(format_).value(); const gfx::ColorSpace color_space0 = GetColorSpaceForPixelFormat(format_); base::WeakPtr<FakeContext> context; @@ -317,7 +316,8 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; const gfx::Size size0(128, 256); - const gfx::BufferFormat format = GetBufferFormatForVideoPixelFormat(format_); + const gfx::BufferFormat format = + VideoPixelFormatToGfxBufferFormat(format_).value(); const gfx::ColorSpace color_space0 = GetColorSpaceForPixelFormat(format_); // Create a pool and several frames on the main thread. @@ -384,7 +384,8 @@ TEST_P(RenderableGpuMemoryBufferVideoFramePoolTest, RespectSizeAndColorSpace) { base::test::SingleThreadTaskEnvironment task_environment; - const gfx::BufferFormat format = GetBufferFormatForVideoPixelFormat(format_); + const gfx::BufferFormat format = + VideoPixelFormatToGfxBufferFormat(format_).value(); const gfx::Size size0(128, 256); const gfx::ColorSpace color_space0 = GetColorSpaceForPixelFormat(format_); const gfx::Size size1(256, 256); @@ -469,7 +470,8 @@ testing::Bool(), #endif testing::Values(media::VideoPixelFormat::PIXEL_FORMAT_NV12, - media::VideoPixelFormat::PIXEL_FORMAT_ARGB))); + media::VideoPixelFormat::PIXEL_FORMAT_ARGB, + media::VideoPixelFormat::PIXEL_FORMAT_ABGR))); } // namespace
diff --git a/mojo/public/cpp/bindings/associated_group_controller.h b/mojo/public/cpp/bindings/associated_group_controller.h index f2b65497..7603ea3 100644 --- a/mojo/public/cpp/bindings/associated_group_controller.h +++ b/mojo/public/cpp/bindings/associated_group_controller.h
@@ -49,6 +49,13 @@ InterfaceId id, const std::optional<DisconnectReason>& reason) = 0; + // Notifies the controller that the peer of interface `id` has been closed. + // Normally this notification comes from a remote client on the underlying + // pipe, but in some cases the remote client may never have been made aware of + // the new associated interface and will not be able to send such a + // notification. + virtual void NotifyLocalEndpointOfPeerClosure(InterfaceId id) = 0; + // Attaches a client to the specified endpoint to send and receive messages. // The returned object is still owned by the controller. It must only be used // on the same sequence as this call, and only before the client is detached
diff --git a/mojo/public/cpp/bindings/features.cc b/mojo/public/cpp/bindings/features.cc index 7f2950b..ec6ff28b 100644 --- a/mojo/public/cpp/bindings/features.cc +++ b/mojo/public/cpp/bindings/features.cc
@@ -41,5 +41,12 @@ #endif ); +// Enables a bugfix for https://crbug.com/331636067. This is a very old bug, and +// this flag will be used to understand the stability and performance impact of +// the fix, if any. +BASE_FEATURE(kMojoFixAssociatedHandleLeak, + "MojoFixAssociatedHandleLeak", + base::FEATURE_DISABLED_BY_DEFAULT); + } // namespace features } // namespace mojo
diff --git a/mojo/public/cpp/bindings/features.h b/mojo/public/cpp/bindings/features.h index 7ffc8bfc..c402f74 100644 --- a/mojo/public/cpp/bindings/features.h +++ b/mojo/public/cpp/bindings/features.h
@@ -19,6 +19,9 @@ COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) BASE_DECLARE_FEATURE(kMojoPredictiveAllocation); +COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) +BASE_DECLARE_FEATURE(kMojoFixAssociatedHandleLeak); + } // namespace features } // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc index 8a676d0..a32052f7 100644 --- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc +++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -598,8 +598,10 @@ // to work properly. message->SerializeHandles(handle_.group_controller()); - if (encountered_error_) + if (encountered_error_) { + message->NotifyPeerClosureForSerializedHandles(handle_.group_controller()); return false; + } InitControllerIfNecessary(); @@ -609,8 +611,10 @@ #endif message->set_heap_profiler_tag(interface_name_); - if (!controller_->SendMessage(message)) + if (!controller_->SendMessage(message)) { + message->NotifyPeerClosureForSerializedHandles(handle_.group_controller()); return false; + } if (!is_control_message && idle_handler_) ++num_unacked_messages_; @@ -630,8 +634,10 @@ // Please see comments in Accept(). message->SerializeHandles(handle_.group_controller()); - if (encountered_error_) + if (encountered_error_) { + message->NotifyPeerClosureForSerializedHandles(handle_.group_controller()); return false; + } InitControllerIfNecessary(); @@ -653,8 +659,10 @@ const bool exclusive_wait = message->has_flag(Message::kFlagNoInterrupt) || !SyncCallRestrictions::AreSyncCallInterruptsEnabled(); - if (!controller_->SendMessage(message)) + if (!controller_->SendMessage(message)) { + message->NotifyPeerClosureForSerializedHandles(handle_.group_controller()); return false; + } if (!is_control_message && idle_handler_) ++num_unacked_messages_;
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc index d78b74f..88587b54 100644 --- a/mojo/public/cpp/bindings/lib/message.cc +++ b/mojo/public/cpp/bindings/lib/message.cc
@@ -588,6 +588,19 @@ return result; } +void Message::NotifyPeerClosureForSerializedHandles( + AssociatedGroupController* group_controller) { + const uint32_t num_ids = payload_num_interface_ids(); + if (num_ids == 0) { + return; + } + + const uint32_t* ids = header_v2()->payload_interface_ids.Get()->storage(); + for (uint32_t i = 0; i < num_ids; ++i) { + group_controller->NotifyLocalEndpointOfPeerClosure(ids[i]); + } +} + void Message::SerializeIfNecessary() { MojoResult rv = MojoSerializeMessage(handle_->value(), nullptr); if (rv == MOJO_RESULT_FAILED_PRECONDITION)
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc index 90913f4..5bf35af9 100644 --- a/mojo/public/cpp/bindings/lib/multiplex_router.cc +++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -10,6 +10,7 @@ #include "base/containers/contains.h" #include "base/containers/flat_set.h" +#include "base/feature_list.h" #include "base/functional/bind.h" #include "base/location.h" #include "base/memory/ptr_util.h" @@ -19,6 +20,7 @@ #include "base/synchronization/waitable_event.h" #include "base/task/sequenced_task_runner.h" #include "base/types/pass_key.h" +#include "mojo/public/cpp/bindings/features.h" #include "mojo/public/cpp/bindings/interface_endpoint_client.h" #include "mojo/public/cpp/bindings/interface_endpoint_controller.h" #include "mojo/public/cpp/bindings/lib/may_auto_lock.h" @@ -512,6 +514,24 @@ ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); } +void MultiplexRouter::NotifyLocalEndpointOfPeerClosure(InterfaceId id) { + if (!base::FeatureList::IsEnabled(features::kMojoFixAssociatedHandleLeak)) { + return; + } + + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&MultiplexRouter::NotifyLocalEndpointOfPeerClosure, + base::WrapRefCounted(this), id)); + return; + } + OnPeerAssociatedEndpointClosed(id, std::nullopt); + + MayAutoLock locker(&lock_); + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); +} + InterfaceEndpointController* MultiplexRouter::AttachEndpointClient( const ScopedInterfaceEndpointHandle& handle, InterfaceEndpointClient* client,
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h index 2744818..bfe7c22 100644 --- a/mojo/public/cpp/bindings/lib/multiplex_router.h +++ b/mojo/public/cpp/bindings/lib/multiplex_router.h
@@ -132,6 +132,7 @@ void CloseEndpointHandle( InterfaceId id, const std::optional<DisconnectReason>& reason) override; + void NotifyLocalEndpointOfPeerClosure(InterfaceId id) override; InterfaceEndpointController* AttachEndpointClient( const ScopedInterfaceEndpointHandle& handle, InterfaceEndpointClient* endpoint_client,
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h index 0faff211b..c365d08 100644 --- a/mojo/public/cpp/bindings/message.h +++ b/mojo/public/cpp/bindings/message.h
@@ -257,6 +257,14 @@ bool DeserializeAssociatedEndpointHandles( AssociatedGroupController* group_controller); + // If this message contains serialized associated interface endponits but is + // going to be destroyed without being sent across a pipe, this notifies any + // relevant local peer endpoints about peer closure. Must be called on any + // unsent Message that is going to be destroyed after calling + // SerializeHandles(). + void NotifyPeerClosureForSerializedHandles( + AssociatedGroupController* group_controller); + // If this Message has an unserialized message context attached, force it to // be serialized immediately. Otherwise this does nothing. void SerializeIfNecessary();
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn index cfb6c98..763053a 100644 --- a/mojo/public/cpp/bindings/tests/BUILD.gn +++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -169,6 +169,7 @@ mojom("test_mojom") { testonly = true sources = [ + "associated_interface_unittest.test-mojom", "binder_map_unittest.test-mojom", "connection_group_unittest.test-mojom", "default_construct_unittest.test-mojom",
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc index 4e57b94..1abbc81 100644 --- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc +++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -21,16 +21,19 @@ #include "base/task/single_thread_task_runner.h" #include "base/task/thread_pool.h" #include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" #include "mojo/public/cpp/bindings/associated_receiver.h" #include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/features.h" #include "mojo/public/cpp/bindings/lib/multiplex_router.h" #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "mojo/public/cpp/bindings/pending_associated_remote.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/shared_associated_remote.h" +#include "mojo/public/cpp/bindings/tests/associated_interface_unittest.test-mojom.h" #include "mojo/public/cpp/bindings/unique_associated_receiver_set.h" #include "mojo/public/cpp/system/functions.h" #include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" @@ -39,6 +42,7 @@ namespace mojo { namespace test { +namespace associated_interface_unittest { namespace { using mojo::internal::MultiplexRouter; @@ -1170,6 +1174,54 @@ } } +class ClumsyBinderImpl : public mojom::ClumsyBinder { + public: + explicit ClumsyBinderImpl(PendingReceiver<mojom::ClumsyBinder> receiver) + : receiver_(this, std::move(receiver)) {} + ~ClumsyBinderImpl() override = default; + + // mojom::ClumsyBinder: + void DropAssociatedBinder( + PendingAssociatedReceiver<mojom::AssociatedBinder> receiver) override { + // Nothing to do but drop the receiver so it's closed. + } + + private: + Receiver<mojom::ClumsyBinder> receiver_; +}; + +TEST_F(AssociatedInterfaceTest, CloseSerializedAssociatedEndpoints) { + // Regression test for https://crbug.com/331636067. Verifies that endpoint + // lifetime is properly managed when associated endpoints are serialized into + // a message that gets dropped before transmission. + + // Force-enable the feature since this test requires it to pass. + base::test::ScopedFeatureList kFeatures{ + features::kMojoFixAssociatedHandleLeak}; + + Remote<mojom::ClumsyBinder> binder; + ClumsyBinderImpl binder_impl(binder.BindNewPipeAndPassReceiver()); + + AssociatedRemote<mojom::AssociatedBinder> associated_binder; + binder->DropAssociatedBinder( + associated_binder.BindNewEndpointAndPassReceiver()); + + // Wait for disconnection to be observed. This way we know any subsequent + // outgoing messages on `associated_binder` will not be sent. + base::RunLoop loop1; + associated_binder.set_disconnect_handler(loop1.QuitClosure()); + loop1.Run(); + + // Send another endpoint over. This receiver will be dropped, and the remote + // should be properly notified of peer closure to terminate this loop. + base::RunLoop loop2; + AssociatedRemote<mojom::AssociatedBinder> another_binder; + associated_binder->Bind(another_binder.BindNewEndpointAndPassReceiver()); + another_binder.set_disconnect_handler(loop2.QuitClosure()); + loop2.Run(); +} + } // namespace +} // namespace associated_interface_unittest } // namespace test } // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.test-mojom b/mojo/public/cpp/bindings/tests/associated_interface_unittest.test-mojom new file mode 100644 index 0000000..f609c718e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.test-mojom
@@ -0,0 +1,15 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test.associated_interface_unittest.mojom; + +import "mojo/public/mojom/base/generic_pending_associated_receiver.mojom"; + +interface ClumsyBinder { + DropAssociatedBinder(pending_associated_receiver<AssociatedBinder> receiver); +}; + +interface AssociatedBinder { + Bind(mojo_base.mojom.GenericPendingAssociatedReceiver receiver); +};
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json index 2198fb5a..7f73b47 100644 --- a/testing/buildbot/chromium.chromiumos.json +++ b/testing/buildbot/chromium.chromiumos.json
@@ -5484,9 +5484,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -5496,8 +5496,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -5514,9 +5514,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -5526,8 +5526,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -5640,9 +5640,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -5652,8 +5652,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -5670,9 +5670,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -5682,8 +5682,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json index 1adb55e7..2c87605 100644 --- a/testing/buildbot/chromium.coverage.json +++ b/testing/buildbot/chromium.coverage.json
@@ -19715,9 +19715,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -19727,8 +19727,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -19744,9 +19744,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -19756,8 +19756,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -19865,9 +19865,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -19877,8 +19877,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -19894,9 +19894,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -19906,8 +19906,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json index 747c8ec..b910a0a 100644 --- a/testing/buildbot/chromium.fyi.json +++ b/testing/buildbot/chromium.fyi.json
@@ -41738,9 +41738,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -41749,8 +41749,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -41767,9 +41767,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -41778,8 +41778,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -41888,9 +41888,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -41899,8 +41899,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -41917,9 +41917,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -41928,8 +41928,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -43237,9 +43237,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -43249,8 +43249,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -43267,9 +43267,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -43279,8 +43279,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -43393,9 +43393,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -43405,8 +43405,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -43423,9 +43423,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -43435,8 +43435,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -44718,9 +44718,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -44729,8 +44729,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -44747,9 +44747,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -44758,8 +44758,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -44868,9 +44868,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -44879,8 +44879,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -44897,9 +44897,9 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome" + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" }, @@ -44908,8 +44908,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json index 2b35774..7951973 100644 --- a/testing/buildbot/chromium.memory.json +++ b/testing/buildbot/chromium.memory.json
@@ -15765,12 +15765,12 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome", + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome", "--test-launcher-print-test-stdio=always", "--combine-ash-logs-on-bots", "--asan-symbolize-output" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -15780,8 +15780,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -15798,12 +15798,12 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome", + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome", "--test-launcher-print-test-stdio=always", "--combine-ash-logs-on-bots", "--asan-symbolize-output" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -15813,8 +15813,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": { @@ -15941,12 +15941,12 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome", + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome", "--test-launcher-print-test-stdio=always", "--combine-ash-logs-on-bots", "--asan-symbolize-output" ], - "description": "Run with ash-chrome version 125.0.6415.0", + "description": "Run with ash-chrome version 125.0.6416.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -15956,8 +15956,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6415.0", - "revision": "version:125.0.6415.0" + "location": "lacros_version_skew_tests_v125.0.6416.0", + "revision": "version:125.0.6416.0" } ], "dimensions": { @@ -15974,12 +15974,12 @@ { "args": [ "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter", - "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome", + "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome", "--test-launcher-print-test-stdio=always", "--combine-ash-logs-on-bots", "--asan-symbolize-output" ], - "description": "Run with ash-chrome version 125.0.6398.0", + "description": "Run with ash-chrome version 125.0.6411.0", "isolate_profile_data": true, "merge": { "script": "//testing/merge_scripts/standard_gtest_merge.py" @@ -15989,8 +15989,8 @@ "cipd_packages": [ { "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip", - "location": "lacros_version_skew_tests_v125.0.6398.0", - "revision": "version:125.0.6398.0" + "location": "lacros_version_skew_tests_v125.0.6411.0", + "revision": "version:125.0.6411.0" } ], "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl index fcd166a..3a46b1e 100644 --- a/testing/buildbot/variants.pyl +++ b/testing/buildbot/variants.pyl
@@ -267,32 +267,32 @@ }, 'LACROS_VERSION_SKEW_CANARY': { 'identifier': 'Lacros version skew testing ash canary', - 'description': 'Run with ash-chrome version 125.0.6415.0', + 'description': 'Run with ash-chrome version 125.0.6416.0', 'args': [ - '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6415.0/test_ash_chrome', + '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6416.0/test_ash_chrome', ], 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip', - 'location': 'lacros_version_skew_tests_v125.0.6415.0', - 'revision': 'version:125.0.6415.0', + 'location': 'lacros_version_skew_tests_v125.0.6416.0', + 'revision': 'version:125.0.6416.0', }, ], }, }, 'LACROS_VERSION_SKEW_DEV': { 'identifier': 'Lacros version skew testing ash dev', - 'description': 'Run with ash-chrome version 125.0.6398.0', + 'description': 'Run with ash-chrome version 125.0.6411.0', 'args': [ - '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6398.0/test_ash_chrome', + '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6411.0/test_ash_chrome', ], 'swarming': { 'cipd_packages': [ { 'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip', - 'location': 'lacros_version_skew_tests_v125.0.6398.0', - 'revision': 'version:125.0.6398.0', + 'location': 'lacros_version_skew_tests_v125.0.6411.0', + 'revision': 'version:125.0.6411.0', }, ], },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index a8507bc..2446c0b 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -11775,6 +11775,26 @@ ] } ], + "MojoFixAssociatedHandleLeak": [ + { + "platforms": [ + "android", + "chromeos", + "chromeos_lacros", + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "MojoFixAssociatedHandleLeak" + ] + } + ] + } + ], "MojoInlineMessagePayloads": [ { "platforms": [ @@ -11891,6 +11911,22 @@ ] } ], + "MultiCalendarSupport": [ + { + "platforms": [ + "chromeos", + "chromeos_lacros" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "MultiCalendarSupport" + ] + } + ] + } + ], "MutationEvents": [ { "platforms": [ @@ -15433,26 +15469,6 @@ ] } ], - "ProtectedAudiencesMultiBid": [ - { - "platforms": [ - "android", - "chromeos", - "chromeos_lacros", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "FledgeMultiBid" - ] - } - ] - } - ], "ProtectedAudiencesReportingTimeout": [ { "platforms": [
diff --git a/third_party/angle b/third_party/angle index 67fc293a..e229afa 160000 --- a/third_party/angle +++ b/third_party/angle
@@ -1 +1 @@ -Subproject commit 67fc293ab0b33e82cc151286f22d55a0781e9e86 +Subproject commit e229afada1d2af012b7ec55395d10f53f1ebfe60
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index 697c95bc..505438c 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc
@@ -2172,9 +2172,9 @@ "SharedStorageAPIM118", base::FEATURE_ENABLED_BY_DEFAULT); -BASE_FEATURE(kSharedStorageAPIM124, - "SharedStorageAPIM124", - base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kSharedStorageAPIM125, + "SharedStorageAPIM125", + base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kSharedStorageAPIEnableWALForDatabase, "SharedStorageAPIEnableWALForDatabase",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h index bf9780c6..6d17853 100644 --- a/third_party/blink/public/common/features.h +++ b/third_party/blink/public/common/features.h
@@ -1420,10 +1420,10 @@ // shipped. BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kSharedStorageAPIM118); -// Additional Shared Storage API features shipped in M124. +// Additional Shared Storage API features shipped in M125. // TODO(crbug.com/1218540): Merge this flag with `kSharedStorageAPI` once // shipped. -BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kSharedStorageAPIM124); +BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kSharedStorageAPIM125); // Enables WAL (write-ahead-logging) mode for the Shared Storage API SQLite // database backend.
diff --git a/third_party/blink/public/common/page/v8_compile_hints_histograms.h b/third_party/blink/public/common/page/v8_compile_hints_histograms.h index ab5d797..d59b4b43 100644 --- a/third_party/blink/public/common/page/v8_compile_hints_histograms.h +++ b/third_party/blink/public/common/page/v8_compile_hints_histograms.h
@@ -13,9 +13,6 @@ inline constexpr const char* kLocalCompileHintsGeneratedHistogram = "WebCore.Scripts.V8LocalCompileHintsGenerated"; -inline constexpr const char* kLocalCompileHintsObsoletedByCodeCacheHistogram = - "WebCore.Scripts.V8LocalCompileHintsObsoletedByCodeCache"; - // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class Status {
diff --git a/third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom b/third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom index 7c0c288..618b270 100644 --- a/third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom +++ b/third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom
@@ -72,6 +72,7 @@ CONNECT_ALREADY_CONNECTED, CONNECT_ALREADY_EXISTS, CONNECT_NOT_CONNECTED, + CONNECT_NON_AUTH_TIMEOUT, // NotFoundError: NO_BLUETOOTH_ADAPTER, CHOSEN_DEVICE_VANISHED,
diff --git a/third_party/blink/renderer/bindings/core/v8/script_streamer.cc b/third_party/blink/renderer/bindings/core/v8/script_streamer.cc index 00a980a..f70de92 100644 --- a/third_party/blink/renderer/bindings/core/v8/script_streamer.cc +++ b/third_party/blink/renderer/bindings/core/v8/script_streamer.cc
@@ -639,8 +639,6 @@ } } - V8CodeCache::RecordCacheGetStatistics(script_resource_->CacheHandler()); - // Here we can't call Check on the cache handler because it requires the // script source, which would require having already loaded the script. It is // OK at this point to disable streaming even though we might end up rejecting @@ -1120,8 +1118,6 @@ scoped_refptr<CachedMetadata> metadata = big_buffer ? CachedMetadata::CreateFromSerializedData(*big_buffer) : nullptr; - - V8CodeCache::RecordCacheGetStatistics(metadata.get(), encoding); std::unique_ptr<v8_compile_hints::CompileHintsForStreaming> result = std::move(builder).Build( (metadata && V8CodeCache::HasHotCompileHints(*metadata, encoding)) @@ -1350,8 +1346,6 @@ } if (HasCodeCache(cached_metadata, encoding_.GetName())) { SuppressStreaming(NotStreamingReason::kHasCodeCacheBackground); - V8CodeCache::RecordCacheGetStatistics( - V8CodeCache::GetMetadataType::kCodeCache); return false; } compile_hints_ = BuildCompileHintsForStreaming(
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc index 07f06a8d..5a8f747 100644 --- a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc +++ b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.cc
@@ -84,76 +84,6 @@ kFull = 1, }; -V8CodeCache::GetMetadataType ReadGetMetadataType( - const CachedMetadataHandler* cache_handler) { - // Check the metadata types in the same preference order they're checked in - // the code: code cache, local compile hints, timestamp. That way we get the - // right sample in case several metadata types are set. - uint32_t code_cache_tag = V8CodeCache::TagForCodeCache(cache_handler); - if (cache_handler - ->GetCachedMetadata(code_cache_tag, - CachedMetadataHandler::kAllowUnchecked) - .get()) { - return V8CodeCache::GetMetadataType::kCodeCache; - } - scoped_refptr<CachedMetadata> cached_metadata = - cache_handler->GetCachedMetadata( - V8CodeCache::TagForCompileHints(cache_handler), - CachedMetadataHandler::kAllowUnchecked); - if (cached_metadata) { - return TimestampIsRecent(cached_metadata.get()) - ? V8CodeCache::GetMetadataType:: - kLocalCompileHintsWithHotTimestamp - : V8CodeCache::GetMetadataType:: - kLocalCompileHintsWithColdTimestamp; - } - cached_metadata = cache_handler->GetCachedMetadata( - V8CodeCache::TagForTimeStamp(cache_handler), - CachedMetadataHandler::kAllowUnchecked); - if (cached_metadata) { - return TimestampIsRecent(cached_metadata.get()) - ? V8CodeCache::GetMetadataType::kHotTimestamp - : V8CodeCache::GetMetadataType::kColdTimestamp; - } - return V8CodeCache::GetMetadataType::kNone; -} - -V8CodeCache::GetMetadataType ReadGetMetadataType( - const CachedMetadata* cached_metadata, - const String& encoding) { - if (!cached_metadata) { - return V8CodeCache::GetMetadataType::kNone; - } - - // Check the metadata types in the same preference order they're checked in - // the code: code cache, local compile hints, timestamp. That way we get the - // right sample in case several metadata types are set. - if (cached_metadata->DataTypeID() == CacheTag(kCacheTagCode, encoding)) { - return V8CodeCache::GetMetadataType::kCodeCache; - } - - if (cached_metadata->DataTypeID() == - CacheTag(kCacheTagCompileHints, encoding)) { - return TimestampIsRecent(cached_metadata) - ? V8CodeCache::GetMetadataType:: - kLocalCompileHintsWithHotTimestamp - : V8CodeCache::GetMetadataType:: - kLocalCompileHintsWithColdTimestamp; - } - - if (cached_metadata->DataTypeID() == CacheTag(kCacheTagTimeStamp, encoding)) { - return TimestampIsRecent(cached_metadata) - ? V8CodeCache::GetMetadataType::kHotTimestamp - : V8CodeCache::GetMetadataType::kColdTimestamp; - } - return V8CodeCache::GetMetadataType::kNone; -} - -constexpr const char* kCacheGetHistogram = - "WebCore.Scripts.V8CodeCacheMetadata.Get"; -constexpr const char* kCacheSetHistogram = - "WebCore.Scripts.V8CodeCacheMetadata.Set"; - } // namespace // Check previously stored timestamp (either from the code cache or compile @@ -342,10 +272,6 @@ no_cache_reason); } - // By recording statistics at this point we exclude scripts for which we're - // not going to generate metadata. - RecordCacheGetStatistics(cache_handler); - if (HasCodeCache(cache_handler) && no_code_cache_compile_options != v8::ScriptCompiler::kProduceCompileHints) { @@ -462,8 +388,6 @@ std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data( v8::ScriptCompiler::CreateCodeCache(unbound_script)); if (cached_data) { - V8CodeCache::RecordCacheSetStatistics( - V8CodeCache::SetMetadataType::kCodeCache); const uint8_t* data = cached_data->data; int length = cached_data->length; cache_handler->ClearCachedMetadata( @@ -534,7 +458,6 @@ // Store a timestamp to the cache as hint. void V8CodeCache::SetCacheTimeStamp(CodeCacheHost* code_cache_host, CachedMetadataHandler* cache_handler) { - RecordCacheSetStatistics(V8CodeCache::SetMetadataType::kTimestamp); uint64_t now_ms = GetTimestamp(); cache_handler->ClearCachedMetadata(code_cache_host, CachedMetadataHandler::kClearLocally); @@ -621,27 +544,4 @@ return cached_metadata; } -void V8CodeCache::RecordCacheGetStatistics( - const CachedMetadataHandler* cache_handler) { - base::UmaHistogramEnumeration(kCacheGetHistogram, - ReadGetMetadataType(cache_handler)); -} - -void V8CodeCache::RecordCacheGetStatistics( - const CachedMetadata* cached_metadata, - const String& encoding) { - base::UmaHistogramEnumeration(kCacheGetHistogram, - ReadGetMetadataType(cached_metadata, encoding)); -} - -void V8CodeCache::RecordCacheGetStatistics( - V8CodeCache::GetMetadataType metadata_type) { - base::UmaHistogramEnumeration(kCacheGetHistogram, metadata_type); -} - -void V8CodeCache::RecordCacheSetStatistics( - V8CodeCache::SetMetadataType metadata_type) { - base::UmaHistogramEnumeration(kCacheSetHistogram, metadata_type); -} - } // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.h b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.h index 2edb39d..6c0f4e72 100644 --- a/third_party/blink/renderer/bindings/core/v8/v8_code_cache.h +++ b/third_party/blink/renderer/bindings/core/v8/v8_code_cache.h
@@ -127,36 +127,6 @@ const KURL& source_url, const WTF::TextEncoding&, OpaqueMode); - - // These values are persisted to logs. Entries should not be renumbered and - // numeric values should never be reused. - enum class GetMetadataType { - kNone = 0, - kHotTimestamp = 1, - kColdTimestamp = 2, - kLocalCompileHintsWithHotTimestamp = 3, - kLocalCompileHintsWithColdTimestamp = 4, - kCodeCache = 5, - kMaxValue = kCodeCache - }; - - // These values are persisted to logs. Entries should not be renumbered and - // numeric values should never be reused. - enum class SetMetadataType { - kTimestamp = 0, - kLocalCompileHintsAtFMP = 1, - kLocalCompileHintsAtInteractive = 2, - kCodeCache = 3, - kMaxValue = kCodeCache - }; - - static void RecordCacheGetStatistics( - const CachedMetadataHandler* cache_handler); - static void RecordCacheGetStatistics(const CachedMetadata* cached_metadata, - const String& encoding); - static void RecordCacheGetStatistics(GetMetadataType metadata_type); - - static void RecordCacheSetStatistics(SetMetadataType metadata_type); }; } // namespace blink
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_local_compile_hints_producer.cc b/third_party/blink/renderer/bindings/core/v8/v8_local_compile_hints_producer.cc index d892e91..f08cff4 100644 --- a/third_party/blink/renderer/bindings/core/v8/v8_local_compile_hints_producer.cc +++ b/third_party/blink/renderer/bindings/core/v8/v8_local_compile_hints_producer.cc
@@ -62,23 +62,6 @@ continue; } - if (V8CodeCache::HasCodeCache(cache_handler, - CachedMetadataHandler::kAllowUnchecked)) { - // We're trying to set compile hints even though the code cache exists - // already. This can happen if the user navigated around on the website - // and the script became so hot that a code cache was created. - base::UmaHistogramBoolean(kLocalCompileHintsObsoletedByCodeCacheHistogram, - true); - return; - } - base::UmaHistogramBoolean(kLocalCompileHintsObsoletedByCodeCacheHistogram, - false); - - V8CodeCache::RecordCacheSetStatistics( - final_data - ? V8CodeCache::SetMetadataType::kLocalCompileHintsAtInteractive - : V8CodeCache::SetMetadataType::kLocalCompileHintsAtFMP); - uint64_t timestamp = V8CodeCache::GetTimestamp(); std::unique_ptr<v8::ScriptCompiler::CachedData> data( CreateCompileHintsCachedDataForScript(compile_hints, timestamp));
diff --git a/third_party/blink/renderer/modules/bluetooth/bluetooth_error.cc b/third_party/blink/renderer/modules/bluetooth/bluetooth_error.cc index cedf850..159ceb36 100644 --- a/third_party/blink/renderer/modules/bluetooth/bluetooth_error.cc +++ b/third_party/blink/renderer/modules/bluetooth/bluetooth_error.cc
@@ -118,6 +118,8 @@ "Connection Error: Already exists."); MAP_ERROR(CONNECT_NOT_CONNECTED, DOMExceptionCode::kInvalidStateError, "Connection Error: Not connected."); + MAP_ERROR(CONNECT_NON_AUTH_TIMEOUT, DOMExceptionCode::kInvalidStateError, + "Connection Error: Non-authentication timeout."); // NetworkErrors: MAP_ERROR(CONNECT_ALREADY_IN_PROGRESS, DOMExceptionCode::kNetworkError,
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage.idl b/third_party/blink/renderer/modules/shared_storage/shared_storage.idl index 9065a623..01af65bd 100644 --- a/third_party/blink/renderer/modules/shared_storage/shared_storage.idl +++ b/third_party/blink/renderer/modules/shared_storage/shared_storage.idl
@@ -72,7 +72,7 @@ ] Promise<any> run(DOMString name, optional SharedStorageRunOperationMethodOptions options); [ - RuntimeEnabled=SharedStorageAPIM124, + RuntimeEnabled=SharedStorageAPIM125, CallWith=ScriptState, RaisesException, MeasureAs=SharedStorageAPI_CreateWorklet_Method
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet.idl b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet.idl index b222dfd..9cafcfb 100644 --- a/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet.idl +++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_worklet.idl
@@ -15,7 +15,7 @@ ] Promise<undefined> addModule(USVString moduleURL, optional WorkletOptions options = {}); [ - RuntimeEnabled=SharedStorageAPIM124, + RuntimeEnabled=SharedStorageAPIM125, Exposed=Window, CallWith=ScriptState, RaisesException, @@ -25,7 +25,7 @@ optional SharedStorageRunOperationMethodOptions options); [ - RuntimeEnabled=SharedStorageAPIM124, + RuntimeEnabled=SharedStorageAPIM125, Exposed=Window, CallWith=ScriptState, RaisesException,
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index c543b07..f5fa6f9 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3523,7 +3523,7 @@ public: true, }, { - name: "SharedStorageAPIM124", + name: "SharedStorageAPIM125", base_feature: "none", public: true, },
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index 32222d5..8383368 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1861,7 +1861,7 @@ "external/wpt/shared-storage", "http/tests/inspector-protocol/shared-storage" ], - "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM124,SharedStorageAPIEnableWALForDatabase,FencedFramesEnforceFocus", + "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,PrivacySandboxAdsAPIsOverride,FencedFramesAPIChanges,FencedFramesDefaultMode,SharedStorageAPIM118,SharedStorageAPIM125,SharedStorageAPIEnableWALForDatabase,FencedFramesEnforceFocus", "--disable-threaded-compositing", "--disable-threaded-animation"], "expires": "Jul 31, 2024" }, @@ -1874,7 +1874,7 @@ "exclusive_tests": [ "external/wpt/shared-storage-selecturl-limit/" ], - "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,FencedFramesAPIChanges,FencedFramesDefaultMode,FencedFramesEnforceFocus,PrivacySandboxAdsAPIsOverride,SharedStorageSelectURLLimit:SharedStorageSelectURLBitBudgetPerPageLoad/9,SharedStorageAPIM118,SharedStorageAPIM124,SharedStorageAPIEnableWALForDatabase", + "args": ["--enable-features=SharedStorageAPI,FencedFrames:implementation_type/mparch,FencedFramesAPIChanges,FencedFramesDefaultMode,FencedFramesEnforceFocus,PrivacySandboxAdsAPIsOverride,SharedStorageSelectURLLimit:SharedStorageSelectURLBitBudgetPerPageLoad/9,SharedStorageAPIM118,SharedStorageAPIM125,SharedStorageAPIEnableWALForDatabase", "--disable-threaded-compositing", "--disable-threaded-animation"], "expires": "Jul 31, 2024" },
diff --git a/third_party/blink/web_tests/external/wpt/compute-pressure/observe_return_type.https.any.js b/third_party/blink/web_tests/external/wpt/compute-pressure/observe_return_type.https.any.js new file mode 100644 index 0000000..b24878ab --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/compute-pressure/observe_return_type.https.any.js
@@ -0,0 +1,18 @@ +// META: script=/resources/test-only-api.js +// META: script=resources/pressure-helpers.js +// META: global=window,dedicatedworker,sharedworker + +'use strict'; + +// Regression test for https://issues.chromium.org/issues/333957909 +// Make sure that observe() always returns a Promise. +pressure_test(async (t, mockPressureService) => { + const observer = new PressureObserver(() => {}); + t.add_cleanup(() => observer.disconnect()); + + for (let i = 0; i < 2; i++) { + const promise = observer.observe('cpu'); + assert_class_string(promise, 'Promise'); + await promise; + } +}, 'PressureObserver.observe() is idempotent');
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index 97da054..fe8b439 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -9472,6 +9472,7 @@ method append method clear method constructor + method createWorklet method delete method get method run @@ -9481,6 +9482,8 @@ attribute @@toStringTag method addModule method constructor + method run + method selectURL interface SharedWorker : EventTarget attribute @@toStringTag getter onerror
diff --git a/third_party/chromium-variations b/third_party/chromium-variations index 9bbacc0..5951e69 160000 --- a/third_party/chromium-variations +++ b/third_party/chromium-variations
@@ -1 +1 @@ -Subproject commit 9bbacc037e69792d5f5c70a389ce780e35b71f29 +Subproject commit 5951e69a2333e36fbd7912bd160f4686347e6c5f
diff --git a/third_party/closure_compiler/externs/bluetooth_private.js b/third_party/closure_compiler/externs/bluetooth_private.js index 46801ebd..b54db7d 100644 --- a/third_party/closure_compiler/externs/bluetooth_private.js +++ b/third_party/closure_compiler/externs/bluetooth_private.js
@@ -54,6 +54,7 @@ NOT_CONNECTED: 'notConnected', DOES_NOT_EXIST: 'doesNotExist', INVALID_ARGS: 'invalidArgs', + NON_AUTH_TIMEOUT: 'nonAuthTimeout', }; /**
diff --git a/third_party/dawn b/third_party/dawn index 5864d1b..37f756f 160000 --- a/third_party/dawn +++ b/third_party/dawn
@@ -1 +1 @@ -Subproject commit 5864d1bef534d0e54f64d489c865db7f76deb2fe +Subproject commit 37f756f60fb233f9fa1d622c3fe277816baab4cc
diff --git a/third_party/depot_tools b/third_party/depot_tools index 8f6d774..609288a 160000 --- a/third_party/depot_tools +++ b/third_party/depot_tools
@@ -1 +1 @@ -Subproject commit 8f6d774a8d8ddcd5a4dc6e8aac06a35576c2b113 +Subproject commit 609288a46b37758cbbfd82aefc01359631aec81f
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal index 5674bb7..1c8f358 160000 --- a/third_party/devtools-frontend-internal +++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@ -Subproject commit 5674bb7b0c13888a0222377953c082b1abf53fbd +Subproject commit 1c8f3583f1e10d91097074d41aba0b8f78837ae1
diff --git a/third_party/googletest/src b/third_party/googletest/src index b1a777f..5197b1a 160000 --- a/third_party/googletest/src +++ b/third_party/googletest/src
@@ -1 +1 @@ -Subproject commit b1a777f31913f8a047f43b2a5f823e736e7f5082 +Subproject commit 5197b1a8e6a1ef9f214f4aa537b0be17cbf91946
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn index 2b160ac7..f1173706 100644 --- a/third_party/lit/v3_0/BUILD.gn +++ b/third_party/lit/v3_0/BUILD.gn
@@ -23,6 +23,7 @@ "//chrome/test/data/webui/cr_elements:build_ts", "//chrome/test/data/webui/extensions:build_ts", "//chrome/test/data/webui/history:build_ts", + "//chrome/test/data/webui/welcome:build_ts", "//ui/webui/resources/cr_components/customize_color_scheme_mode:build_ts", "//ui/webui/resources/cr_components/help_bubble:build_ts", "//ui/webui/resources/cr_components/history_clusters:build_ts",
diff --git a/third_party/pdfium b/third_party/pdfium index 2c66e07..fde20e1 160000 --- a/third_party/pdfium +++ b/third_party/pdfium
@@ -1 +1 @@ -Subproject commit 2c66e07e9c3b52feee720c03aa11984895f09803 +Subproject commit fde20e170bebdde902d38dd577a0543e11b6d4d4
diff --git a/third_party/skia b/third_party/skia index b40fdf1..293de35 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit b40fdf12342f60a32930e1cc5758380d0c786756 +Subproject commit 293de35a9d1e046bf919f32ec6eb693f82ee4df9
diff --git a/third_party/webrtc b/third_party/webrtc index 83a1c92..f2cdbc9 160000 --- a/third_party/webrtc +++ b/third_party/webrtc
@@ -1 +1 @@ -Subproject commit 83a1c92bb409cb16dfa2cb60f7f3064db388aca6 +Subproject commit f2cdbc9b0717e67821826f7ff93ce7fe0e9485c3
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index a32b88c..5fbaa7f 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -12256,6 +12256,36 @@ <int value="8" label="Overflow"/> </enum> +<enum name="GameControlsButtonOptionsMenuFunction"> + <int value="0" label="Select single button type"/> + <int value="1" label="Select joystick type"/> + <int value="2" label="Edit label is focused"/> + <int value="3" label="Key assigned successfully"/> + <int value="4" label="Press done button"/> + <int value="5" label="Press delete button"/> +</enum> + +<enum name="GameControlsEditDeleteMenuFunction"> + <int value="0" label="Press edit button"/> + <int value="1" label="Press delete button"/> +</enum> + +<enum name="GameControlsEditingListFunction"> + <int value="0" label="Add"/> + <int value="1" label="Done"/> + <int value="2" label="Hover list item"/> + <int value="3" label="Press list item"/> + <int value="4" label="Edit label is focused"/> + <int value="5" label="Key assigned successfully"/> +</enum> + +<enum name="GameControlsMappingSource"> + <int value="0" label="Empty"/> + <int value="1" label="Default mapping, including position change"/> + <int value="2" label="User added mapping only"/> + <int value="3" label="Default and user-added mapping"/> +</enum> + <enum name="GameDashboardFunction"> <int value="0" label="Feedback"/> <int value="1" label="Help"/> @@ -19753,6 +19783,7 @@ <int value="-614223913" label="ClickToCallContextMenuForSelectedText:enabled"/> <int value="-613596048" label="new-canvas-2d-api"/> + <int value="-613595118" label="StarterPackIPH:enabled"/> <int value="-613072171" label="ElementCapture:enabled"/> <int value="-612860466" label="Projector:disabled"/> <int value="-612633819" label="NotificationScrollBar:disabled"/> @@ -23379,6 +23410,7 @@ <int value="1009976778" label="SidePanel:disabled"/> <int value="1010832751" label="VideoToolboxAv1Decoding:disabled"/> <int value="1011491959" label="PageInfoCookiesSubpage:enabled"/> + <int value="1012449492" label="StarterPackIPH:disabled"/> <int value="1012643576" label="CommerceDeveloper:enabled"/> <int value="1012916096" label="AutocorrectToggle:enabled"/> <int value="1012942422" label="HorizontalTabSwitcherAndroid:disabled"/>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml index ed327b7b..7faf9f7 100644 --- a/tools/metrics/histograms/metadata/arc/histograms.xml +++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1135,6 +1135,59 @@ </token> </histogram> +<histogram name="Arc.GameControls.ButtonOptionsMenuFunctionTriggered" + enum="GameControlsButtonOptionsMenuFunction" expires_after="2025-04-07"> + <owner>cuicuiruan@google.com</owner> + <owner>pjlee@google.com</owner> + <owner>arc-gaming@google.com</owner> + <summary> + Records how often a specific function within the game controls button + options menu is triggered. + </summary> +</histogram> + +<histogram name="Arc.GameControls.EditDeleteMenuFuctionTriggered" + enum="GameControlsEditDeleteMenuFunction" expires_after="2025-04-07"> + <owner>cuicuiruan@google.com</owner> + <owner>pjlee@google.com</owner> + <owner>arc-gaming@google.com</owner> + <summary> + Records how often a specific function within the game controls edit/delete + menu is triggered. + </summary> +</histogram> + +<histogram name="Arc.GameControls.EditingListFunctionTriggered" + enum="GameControlsEditingListFunction" expires_after="2025-04-07"> + <owner>cuicuiruan@google.com</owner> + <owner>pjlee@google.com</owner> + <owner>arc-gaming@google.com</owner> + <summary> + Records how often a specific function within the game controls editing list + is triggered. + </summary> +</histogram> + +<histogram + name="Arc.GameControls.{FeatureOrHint}ToggleWithMappingSource.{OnOrOff}" + enum="GameControlsMappingSource" expires_after="2025-04-07"> + <owner>cuicuiruan@google.com</owner> + <owner>pjlee@google.com</owner> + <owner>arc-gaming@google.com</owner> + <summary> + Records the game controls feature or hint is toggled on or off with current + mapping source. + </summary> + <token key="FeatureOrHint"> + <variant name="Feature"/> + <variant name="Hint"/> + </token> + <token key="OnOrOff"> + <variant name="Off"/> + <variant name="On"/> + </token> +</histogram> + <histogram name="Arc.GhostWindowViewType" units="ArcGhostWindowViewType" expires_after="2024-09-01"> <owner>sstan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml index 1b179d4..0964237 100644 --- a/tools/metrics/histograms/metadata/ash/enums.xml +++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -1110,6 +1110,7 @@ <int value="20" label="Stop screen recording keyboard shortcut"/> <int value="21" label="Game dashboard stop recording button"/> <int value="22" label="Game toolbar stop recording button"/> + <int value="23" label="Game dashboard stop recording for tablet mode"/> </enum> <enum name="EolIncentiveButtonType"> @@ -1281,6 +1282,12 @@ <int value="6" label="IME tray"/> </enum> +<enum name="MahiQuestionSource"> + <int value="0" label="menu view"/> + <int value="1" label="panel view"/> + <int value="2" label="retry button"/> +</enum> + <enum name="MantaStatusCode"> <int value="0" label="kOk"/> <int value="1" label="kGenericError"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml index 8e365a27e..b5f208b 100644 --- a/tools/metrics/histograms/metadata/ash/histograms.xml +++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -4833,6 +4833,36 @@ </summary> </histogram> +<histogram name="Ash.Mahi.QuestionAnswer.LoadingTime" units="ms" + expires_after="2025-04-10"> + <owner>leandre@chromium.org</owner> + <owner>cros-status-area-eng@google.com</owner> + <summary> + Record the time it takes to answer the question inside the Mahi panel. + Emitted when an answer text is fully loaded and shows up. + </summary> +</histogram> + +<histogram name="Ash.Mahi.QuestionSource" enum="MahiQuestionSource" + expires_after="2025-02-01"> + <owner>andrewxu@chromium.org</owner> + <owner>cros-status-area-eng@google.com</owner> + <summary> + Record the sources of questions sent to the Mahi backend. Emitted when a + question is posted. + </summary> +</histogram> + +<histogram name="Ash.Mahi.Summary.LoadingTime" units="ms" + expires_after="2025-04-10"> + <owner>leandre@chromium.org</owner> + <owner>cros-status-area-eng@google.com</owner> + <summary> + Record the time it takes to load the summary inside the Mahi panel. Emitted + when summary text is fully loaded and shows up. + </summary> +</histogram> + <histogram name="Ash.Mahi.UserJourneyTime" units="ms" expires_after="2025-04-10"> <owner>leandre@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/bluetooth/enums.xml b/tools/metrics/histograms/metadata/bluetooth/enums.xml index a0d1a93..794c2ad 100644 --- a/tools/metrics/histograms/metadata/bluetooth/enums.xml +++ b/tools/metrics/histograms/metadata/bluetooth/enums.xml
@@ -60,6 +60,8 @@ <int value="8" label="Authentication canceled"/> <int value="9" label="Authentication rejected"/> <int value="10" label="Connection in progress"/> + <int value="11" label="Device not found"/> + <int value="12" label="Bluetooth disabled"/> </enum> <enum name="BluetoothConnectToServiceError"> @@ -84,6 +86,7 @@ <int value="11" label="Not Connected"/> <int value="12" label="Does Not Exist"/> <int value="13" label="Invalid Arguments"/> + <int value="14" label="Non-authorization Timeout"/> </enum> <enum name="BluetoothDeviceConnectToServiceFailureReason"> @@ -1397,6 +1400,7 @@ <int value="13" label="Not Connected"/> <int value="14" label="Does Not Exist"/> <int value="15" label="Invalid Arguments"/> + <int value="16" label="Non-auth Timeout"/> </enum> <enum name="WebBluetoothGATTOperationOutcome">
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml index 8757140..acfba71 100644 --- a/tools/metrics/histograms/metadata/extensions/enums.xml +++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -3109,6 +3109,7 @@ <int value="255" label="kEnterpriseKioskInput"/> <int value="256" label="kOdfsConfigPrivate"/> <int value="257" label="kChromeOSManagementAudio"/> + <int value="258" label="kChromeOSDiagnosticsNetworkInfoForMlab"/> </enum> <enum name="ExtensionPolicyReinstallReason">
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml index fcbf9e8..8272bce 100644 --- a/tools/metrics/histograms/metadata/others/histograms.xml +++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -12066,7 +12066,7 @@ </histogram> <histogram name="WebUITabStrip.CloseAction" enum="WebUITabStripCloseActions" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>collinbaker@chromium.org</owner> <owner>tluk@chromium.org</owner> <summary> @@ -12077,7 +12077,7 @@ </histogram> <histogram name="WebUITabStrip.CloseTabAction" - enum="WebUITabStripCloseTabActions" expires_after="2024-09-15"> + enum="WebUITabStripCloseTabActions" expires_after="2024-10-02"> <owner>johntlee@chromium.org</owner> <owner>dpapad@chromium.org</owner> <summary> @@ -12088,7 +12088,7 @@ </histogram> <histogram name="WebUITabStrip.LoadCompletedTime" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>yuhengh@chromium.org</owner> <owner>tluk@chromium.org</owner> <owner>romanarora@chromium.org</owner> @@ -12102,7 +12102,7 @@ </histogram> <histogram name="WebUITabStrip.LoadDocumentTime" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>yuhengh@chromium.org</owner> <owner>tluk@chromium.org</owner> <owner>romanarora@chromium.org</owner> @@ -12116,7 +12116,7 @@ </histogram> <histogram name="WebUITabStrip.OpenAction" enum="WebUITabStripOpenActions" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>collinbaker@chromium.org</owner> <owner>tluk@chromium.org</owner> <summary> @@ -12127,7 +12127,7 @@ </histogram> <histogram name="WebUITabStrip.OpenDuration" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>collinbaker@chromium.org</owner> <owner>tluk@chromium.org</owner> <summary> @@ -12138,7 +12138,7 @@ </histogram> <histogram name="WebUITabStrip.TabActivation" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>robliao@chromium.org</owner> <owner>johntlee@chromium.org</owner> <summary> @@ -12148,7 +12148,7 @@ </histogram> <histogram name="WebUITabStrip.TabCreation" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>robliao@chromium.org</owner> <owner>johntlee@chromium.org</owner> <summary> @@ -12157,7 +12157,7 @@ </histogram> <histogram name="WebUITabStrip.TabDataReceived" units="ms" - expires_after="2024-05-15"> + expires_after="2024-10-02"> <owner>robliao@chromium.org</owner> <owner>johntlee@chromium.org</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/enums.xml b/tools/metrics/histograms/metadata/safe_browsing/enums.xml index f3ca53ce..d4ede5d 100644 --- a/tools/metrics/histograms/metadata/safe_browsing/enums.xml +++ b/tools/metrics/histograms/metadata/safe_browsing/enums.xml
@@ -148,6 +148,18 @@ <int value="1" label="Unavailable"/> </enum> +<enum name="ClientReportPersistDownloadReportResult"> + <int value="0" label="kPersistTaskPosted"/> + <int value="1" label="kSerializationError"/> + <int value="2" label="kEmptyReport"/> +</enum> + +<enum name="ClientReportPersisterWriteResult"> + <int value="0" label="kSuccess"/> + <int value="1" label="kFailedCreateDirectory"/> + <int value="2" label="kFailedWriteFile"/> +</enum> + <enum name="ClientSafeBrowsingReportType"> <int value="0" label="Unknown"/> <int value="1" label="URL phishing"/> @@ -170,6 +182,8 @@ <int value="22" label="Phishy site interactions"/> <int value="23" label="Safe Browsing warning shown to user"/> <int value="24" label="Abusive notification permission accepted"/> + <int value="25" label="dangerous download auto deleted"/> + <int value="26" label="dangerous download canceled on profile closure"/> </enum> <enum name="SafeBrowsingAllowlistAsyncMatch">
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml index fb58a63..b48f96f 100644 --- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml +++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -516,6 +516,46 @@ </token> </histogram> +<histogram + name="SafeBrowsing.ClientSafeBrowsingReport.PersistDownloadReportResult" + enum="ClientReportPersistDownloadReportResult" expires_after="2024-10-12"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-counter-abuse-alerts@google.com</owner> + <summary> + Records the result of the call to persisting a download report on disk. + Logged each time a download report needs to be persisted at shutdown. + </summary> +</histogram> + +<histogram + name="SafeBrowsing.ClientSafeBrowsingReport.PersisterReadReportSuccessful" + enum="BooleanSuccess" expires_after="2024-10-12"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-counter-abuse-alerts@google.com</owner> + <summary> + Records whether reading a persisted report is successful. Logged each time a + persisted report is found on startup. + </summary> +</histogram> + +<histogram + name="SafeBrowsing.ClientSafeBrowsingReport.PersisterReportCountOnStartup" + units="count" expires_after="2024-10-12"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-counter-abuse-alerts@google.com</owner> + <summary>Record the number of persisted report on startup.</summary> +</histogram> + +<histogram name="SafeBrowsing.ClientSafeBrowsingReport.PersisterWriteResult" + enum="ClientReportPersisterWriteResult" expires_after="2024-10-12"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-counter-abuse-alerts@google.com</owner> + <summary> + Records the result of persisting a report on disk. Logged each time the + persister write a report on disk. + </summary> +</histogram> + <histogram name="SafeBrowsing.ClientSafeBrowsingReport.ReportType" enum="ClientSafeBrowsingReportType" expires_after="2024-09-29"> <owner>xinghuilu@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/web_core/enums.xml b/tools/metrics/histograms/metadata/web_core/enums.xml index b014a1f5..cfa8f7d2 100644 --- a/tools/metrics/histograms/metadata/web_core/enums.xml +++ b/tools/metrics/histograms/metadata/web_core/enums.xml
@@ -400,22 +400,6 @@ <int value="1" label="Has client"/> </enum> -<enum name="V8CodeCacheGetMetadataType"> - <int value="0" label="None"/> - <int value="1" label="Hot timestamp"/> - <int value="2" label="Cold timestamp"/> - <int value="3" label="Local compile hints with hot timestamp"/> - <int value="4" label="Local compile hints with cold timestamp"/> - <int value="5" label="Code cache"/> -</enum> - -<enum name="V8CodeCacheSetMetadataType"> - <int value="0" label="Timestamp"/> - <int value="1" label="Local compile hints at FMP"/> - <int value="2" label="Local compile hints at interactive"/> - <int value="3" label="Code cache"/> -</enum> - <enum name="V8CompileHintsModelQuality"> <int value="0" label="No model"/> <int value="1" label="Bad model"/>
diff --git a/tools/metrics/histograms/metadata/web_core/histograms.xml b/tools/metrics/histograms/metadata/web_core/histograms.xml index 5866c68..262d303 100644 --- a/tools/metrics/histograms/metadata/web_core/histograms.xml +++ b/tools/metrics/histograms/metadata/web_core/histograms.xml
@@ -793,27 +793,6 @@ <summary>Whether a parsing blocking script was streamed or not.</summary> </histogram> -<histogram name="WebCore.Scripts.V8CodeCacheMetadata.Get" - enum="V8CodeCacheGetMetadataType" expires_after="2024-09-15"> - <owner>marja@chromium.org</owner> - <owner>v8-runtime@google.com</owner> - <summary> - What type of cache metadata we retrieved for a script. Recorded when we are - about to compile a script. - </summary> -</histogram> - -<histogram name="WebCore.Scripts.V8CodeCacheMetadata.Set" - enum="V8CodeCacheSetMetadataType" expires_after="2024-09-15"> - <owner>marja@chromium.org</owner> - <owner>v8-runtime@google.com</owner> - <summary> - What type of cache metadata we set for a script. If we set multiple metadata - types for the same script, multiple samples are recorded. Recorded when we - set the metadata. - </summary> -</histogram> - <histogram name="WebCore.Scripts.V8CompileHintsStatus" enum="V8CompileHintsStatus" expires_after="2024-09-15"> <owner>marja@chromium.org</owner> @@ -847,16 +826,6 @@ </summary> </histogram> -<histogram name="WebCore.Scripts.V8LocalCompileHintsObsoletedByCodeCache" - enum="BooleanYesNo" expires_after="2024-09-15"> - <owner>marja@chromium.org</owner> - <owner>v8-runtime@google.com</owner> - <summary> - Whether there already was a code cache when we tried to set local compile - hints. Recorded when we set compile hints. - </summary> -</histogram> - </histograms> </histogram-configuration>
diff --git a/tools/perf/contrib/shared_storage/shared_storage.py b/tools/perf/contrib/shared_storage/shared_storage.py index 4058660..954c2f1 100644 --- a/tools/perf/contrib/shared_storage/shared_storage.py +++ b/tools/perf/contrib/shared_storage/shared_storage.py
@@ -17,7 +17,7 @@ # Features to enable via command line. _ENABLED_FEATURES = [ 'SharedStorageAPI:ExposeDebugMessageForSettingsStatus/true', - 'SharedStorageAPIM118', 'SharedStorageAPIM124', + 'SharedStorageAPIM118', 'SharedStorageAPIM125', 'SharedStorageAPIEnableWALForDatabase', 'FencedFrames:implementation_type/mparch', 'FencedFramesDefaultMode', 'PrivacySandboxAdsAPIsOverride', 'DefaultAllowPrivacySandboxAttestations'