diff --git a/.geminiignore b/.geminiignore index e511f3b6..85d98655 100644 --- a/.geminiignore +++ b/.geminiignore
@@ -1,2 +1,3 @@ +remoting/GEMINI.md v8/GEMINI.md .jj/ \ No newline at end of file
diff --git a/.github/prompts/create_copilot_instructions.prompt.md b/.github/prompts/create_copilot_instructions.prompt.md index fb4f5b2..5ca12bc41 100644 --- a/.github/prompts/create_copilot_instructions.prompt.md +++ b/.github/prompts/create_copilot_instructions.prompt.md
@@ -60,7 +60,7 @@ 3. User personalization **Do not** include filepath syntax in the output, such as: -`// filepath: ...\.github\instructions\haystack.instructions.md` +`// filepath: ...\.github\instructions\chromium.instructions.md` ### Default Chromium or Embedder Instructions The default instructions should be a copy of one of the following files at the top
diff --git a/DEPS b/DEPS index 89876525..ce73b7c 100644 --- a/DEPS +++ b/DEPS
@@ -267,7 +267,7 @@ # pathname relative to build/config/siso/backend_config, or absolute path. 'reapi_backend_config_path': Str(''), # siso CIPD package version. - 'siso_version': 'git_revision:7495bf92ea44f7eed89273b5aea7bd72b702ed1c', + 'siso_version': 'git_revision:a4a219a47e8a8d1a4a2fe306ffa70d65ab4b23b8', # reclient options. # download reclient binaries, required for 'use_reclient` gn arg. @@ -312,11 +312,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '23eba721990ca397ed94756cc4d3a902319722ee', + 'skia_revision': 'a94df1cdabb03d3ead2486413bc2999b500aa4f8', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '07ff726835cdda38049d1af45c0d69135e5d0503', + 'v8_revision': '7317cc77afe339d0618369e2c299a3ada5e6c23f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -332,7 +332,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. - 'boringssl_revision': '5774eca6004ed7c7467bd644c057797ca96b65f2', + 'boringssl_revision': '745d3ebf4064a4ea7c0a115ecbba5affa2609bcd', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. @@ -424,7 +424,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': 'ad8d51fbdd599d8dd0e2997f88900ac89f404513', + 'dawn_revision': '33a083f7a9e71cfe7e8a9b05c94fda00e5715900', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -1198,7 +1198,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm', - 'version': 'xk-C1pnkiPjborb84hke8cTBEeFv_zeiT1RwsjVhXdwC', + 'version': 'OWRlRScxqbG6Kcb0ET76yI2-GoOYu9L8bCSeRp27d9UC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1209,7 +1209,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm64', - 'version': 'maKZX_p_ObOQba0vYPY7KENQO_a0cZAUHYqv0zRZRPEC', + 'version': 'ELNtV_LtndadsmFAD-npIHWAyqcVMzc-80ye-Lbk1nkC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1220,7 +1220,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm', - 'version': 'ZNTUWix3Pudc5ht-bGrnyfqo4PXkauVJB_lsQ5yF7e0C', + 'version': 'hS7CN9CzU4QuREIJImx4vWf8Dmdipb5h0lFaNqpsrfAC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1231,7 +1231,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm64', - 'version': 'oPNNedE-sMK1FZb_8ErzdEKL4lRr581LjMOraHC7mo4C', + 'version': 'fjpgH_OWhC3j_G93SjDrh4EGpOO282HdFXUypIk3NZkC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1403,7 +1403,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_linux64', - 'version': 'version:2@1588009', + 'version': 'version:2@1589002', }, ], }, @@ -1414,7 +1414,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_mac_amd64', - 'version': 'version:2@1588060', + 'version': 'version:2@1589006', }, ], }, @@ -1425,7 +1425,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_mac_arm64', - 'version': 'version:2@1588035', + 'version': 'version:2@1589009', }, ], }, @@ -1436,7 +1436,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_win_arm64', - 'version': 'version:2@1588071', + 'version': 'version:2@1589044', }, ], }, @@ -1447,7 +1447,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_win_x86', - 'version': 'version:2@1588065', + 'version': 'version:2@1589019', }, ], }, @@ -1458,7 +1458,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chromium_win_x86_64', - 'version': 'version:2@1588001', + 'version': 'version:2@1589020', }, ], }, @@ -1536,7 +1536,7 @@ 'packages': [ { 'package': 'chromium/third_party/enterprise_companion/chromium_linux64', - 'version': 'LyIuod3OsO3SF1Kf2dE8dHgUx5kH7e1TWa-Sp02lHUEC', + 'version': '0hCOtVQniotwV79YN18roZPD5MQrhSjdhUhIEYOR7b8C', }, ], }, @@ -1547,7 +1547,7 @@ 'packages': [ { 'package': 'chromium/third_party/enterprise_companion/chromium_mac_amd64', - 'version': 'IqxhzppHkcnVGao57feNGy7xPUv8tpcolv61v8Ff7B0C', + 'version': 'H5fzeM0A0fz6CuwZ4lSrkcLuIY4uYdFkXsX1WoPe7uoC', }, ], }, @@ -1558,7 +1558,7 @@ 'packages': [ { 'package': 'chromium/third_party/enterprise_companion/chromium_mac_arm64', - 'version': 'BRIzYu_kp6yiThC2-sx-eegGyKalTzAiXZEyht6UrtkC', + 'version': 'dXnyVHYBBgysK_ZzWlUiKQbwE34-AqIsCmraUD3K7NMC', }, ], }, @@ -1569,7 +1569,7 @@ 'packages': [ { 'package': 'chromium/third_party/enterprise_companion/chromium_win_x86', - 'version': 'nAxF0buIxg6dvoAb4FRRD3Hz588GhvDkIsCa7MseCOQC', + 'version': 'LOVACndsGHfQJMPOl4jU2Ixu_ITzgyLIt2aVLPpwU3oC', }, ], }, @@ -1580,7 +1580,7 @@ 'packages': [ { 'package': 'chromium/third_party/enterprise_companion/chromium_win_x86_64', - 'version': 'EG4h6sl62yIy6Nk1OMdT3443JyUASeeLXbFnm5xzhIsC', + 'version': 'YiwFYvBHVBrZqsE6jdbYK9uQYRmTBEJmE1j2YgY6VjQC', }, ], }, @@ -1616,7 +1616,7 @@ 'packages': [ { 'package': 'chromium/chrome/test/data/variations/cipd', - 'version': 'Wk4PA-MOnXl8nE71ABamzeKcrkPzleHvhvOEAMh01NUC', + 'version': 'hQGLFGrk0uhS2ROVi31zF5LeBBy1QKqkNau_JYx_uucC', }, ], 'condition': 'non_git_source', @@ -1728,7 +1728,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'Y9ecsp20FCn4bBgTNBLDqMM8XRz45d17SEsZ6tqD8XsC', + 'version': '4fkujxcHswgZM_Abba388nGkoYd7fgfTF_0jCmWzodMC', }, ], 'condition': 'checkout_android and non_git_source', @@ -2076,7 +2076,7 @@ Var('chromium_git') + '/external/github.com/jk-jeon/dragonbox.git' + '@' + 'beeeef91cf6fef89a4d4ba5e95d47ca64ccb3a44', 'src/third_party/eigen3/src': - Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + 'dd826edb42520814dab9239ec81f32ab91a4afe4', + Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + 'eea4d31f58572591bdaa3b654227e48efb495b96', 'src/third_party/emoji-metadata/src': { 'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '045f146fca682a836e01cd265171312bfb300e06', @@ -2958,15 +2958,15 @@ 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@534fffbfc1cd503ab60af3fc2c5e657b26004420', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@b4ca123cdf076f8798b3ca0b78bfb5106ba4d410', 'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@c0d54e2b6b3f1e56e657c8e388b2310791624303', 'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3', 'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@f88a2d766840fc825af1fc065977953ba1fa4a91', - 'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@46b1e005696db761fd19067cda0d6d93e4d0b63c', + 'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@d83d9363b0b4466b78331f73ab64550416389d95', 'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@ad9ce1235e88dc09287e19171dfac384db8ec32c', 'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@e0e501b0ba42df7b3af023470ad068c48a3ac4de', 'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@7f423e2b242c154e6ace85c804c65462a7d41870', - 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@738ec97a3f659dd6469bff3c4078ef981b0a343f', + 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@6878b60ffdabe0ce79f02163c4430c27d21d1e41', 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@d1fa926f76c6f32bd333159b8ff2fc135dd7a851', 'src/third_party/vulkan_memory_allocator': @@ -3721,7 +3721,7 @@ 'src/components/optimization_guide/internal': { 'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' + - 'db2468268385dbd81bb71e8accf26a76f0e93b81', + '0dda382cde62bc88d92bf698a1e7d4dee0ede5c8', 'condition': 'checkout_src_internal', }, @@ -3793,7 +3793,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - '41d2c40a3c313310de29d05825893caea562da5d', + '9dd3170ffb37ac42975a3e61ab8dc264b944ff63', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/WATCHLISTS b/WATCHLISTS index 6bb464cc..df16ee3 100644 --- a/WATCHLISTS +++ b/WATCHLISTS
@@ -5,7 +5,7 @@ # Watchlist Rules # Refer: https://chromium.googlesource.com/chromium/src/+/main/docs/infra/watchlists.md -# IMPORTANT: The regular expression filepath is tested against each path using +# Important: The regular expression filepath is tested against each path using # re.search, so it is not usually necessary to add .* at the end of the pattern. { @@ -678,6 +678,16 @@ 'filepath': 'third_party/blink/web_tests/external/' \ '|third_party/blink/tools/blinkpy/w3c/' }, + 'blink_webmcp': { + 'filepath': 'third_party/blink/public/mojom/content_extraction/script_tools.mojom' \ + '|third_party/blink/public/web/.*script_tool' \ + '|third_party/blink/renderer/core/events/web_mcp' \ + '|third_party/blink/renderer/core/html/forms/.*_mcp_' \ + '|third_party/blink/renderer/core/script_tool' \ + '|third_party/blink/web_tests/fast/webmcp' \ + '|third_party/blink/web_tests/wpt_internal/script_tools' \ + '|third_party/blink/web_tests/wpt_internal/webmcp', + }, 'blink_webp': { 'filepath': 'third_party/blink/renderer/platform/image-decoders/webp' \ '|third_party/blink/renderer/platform/image-encoders/skia/webp' @@ -2491,6 +2501,12 @@ '|content/browser/webid'\ '|chrome/browser/.*webid', }, + 'webmcp': { + 'filepath': 'chrome/browser/actor/tools/.*script_tool' \ + '|chrome/browser/glic/host/.*script_tool' \ + '|chrome/renderer/actor/.*script_tool' \ + '|chrome/test/data/actor/.*script_tool', + }, 'webnn': { 'filepath': 'third_party/blink/renderer/modules/ml/webnn/'\ '|services/webnn/', @@ -2815,6 +2831,8 @@ 'blink_viewport_interaction': ['kenneth.christiansen@gmail.com', 'bokan@chromium.org'], 'blink_w3ctests': ['blink-reviews-w3ctests@chromium.org'], + 'blink_webmcp': ['khushalsagar@chromium.org', + 'mfoltz+watch-webmcp@chromium.org'], 'blink_webp': ['jzern@chromium.org', 'urvang@chromium.org', 'mbarowsky+watch-blink-webp@chromium.org'], @@ -3467,6 +3485,7 @@ 'webid': ['yigu+watch@chromium.org', 'cbiesinger@chromium.org', 'npm+watch@chromium.org'], + 'webmcp': ['mfoltz+watch-webmcp@chromium.org'], 'webnn': ['qjw@chromium.org', 'ningxin.hu@intel.com'], 'webotp': ['yigu+watch@chromium.org'], 'webrtc_browser_tests': ['phoglund+watch@chromium.org'],
diff --git a/android_webview/common/BUILD.gn b/android_webview/common/BUILD.gn index 0e610ae..366b04b 100644 --- a/android_webview/common/BUILD.gn +++ b/android_webview/common/BUILD.gn
@@ -61,8 +61,9 @@ "//ui/base", "//ui/gfx/geometry", "//ui/gfx/ipc/geometry", - "//url", ] + + public_deps = [ "//url" ] } mojom("mojom") {
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index 8aa9785..93ad7b9 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -1463,15 +1463,13 @@ private static class WindowAndroidWrapper { private final WindowAndroid mWindowAndroid; private final CleanupReference mCleanupReference; - private final WeakReference<Context> mContextRef; // This ref-counts is used only to destroy WindowAndroid eagerly // when AwContents is destroyed. The CleanupReference is still used // if a Wrapper is created without any AwContents. private int mRefFromAwContentsDestroyRunnable; - public WindowAndroidWrapper(Context context, WindowAndroid windowAndroid) { - mContextRef = new WeakReference<>(context); + public WindowAndroidWrapper(WindowAndroid windowAndroid) { mWindowAndroid = windowAndroid; mCleanupReference = new CleanupReference(this, (e) -> windowAndroid.destroy()); } @@ -1493,7 +1491,7 @@ private void maybeCleanupEarly() { if (mRefFromAwContentsDestroyRunnable != 0) return; - Context context = mContextRef.get(); + Context context = mWindowAndroid.getContext().get(); if (context != null && sContextWindowMap.get(context) != this) return; mCleanupReference.cleanupNow(); @@ -1517,18 +1515,18 @@ try (DualTraceEvent e2 = DualTraceEvent.scoped("AwContents.createActivityWindow")) { final boolean listenToActivityState = false; activityWindow = - ActivityWindowAndroid.create( - activity, + new ActivityWindowAndroid( + context, listenToActivityState, IntentRequestTracker.createFromActivity(activity), /* insetObserver= */ null, /* trackOcclusion= */ false); } - wrapper = new WindowAndroidWrapper(context, activityWindow); + wrapper = new WindowAndroidWrapper(activityWindow); } else { wrapper = new WindowAndroidWrapper( - context, new WindowAndroid(context, /* trackOcclusion= */ false)); + new WindowAndroid(context, /* trackOcclusion= */ false)); } sContextWindowMap.put(context, wrapper); }
diff --git a/base/system/sys_info_unittest.cc b/base/system/sys_info_unittest.cc index c239502..b453357 100644 --- a/base/system/sys_info_unittest.cc +++ b/base/system/sys_info_unittest.cc
@@ -504,10 +504,10 @@ size_t expected_count = SysInfo::NumberOfEfficientProcessors() == 0 ? frequencies.size() : SysInfo::NumberOfEfficientProcessors(); - EXPECT_EQ(std::count_if(frequencies.begin(), frequencies.end(), - [min_frequency](uint64_t freq) { - return freq == min_frequency; - }), + EXPECT_EQ(std::ranges::count_if(frequencies, + [min_frequency](uint64_t freq) { + return freq == min_frequency; + }), expected_count); }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridView.java index 3baeb19f..babb95a 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridView.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridView.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.tasks.tab_management; +import static org.chromium.build.NullUtil.assumeNonNull; import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.BASE_ANIMATION_DURATION_MS; import android.animation.Animator; @@ -17,6 +18,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; @@ -77,6 +79,8 @@ private @Nullable QuickDeleteAnimationGradientDrawable mQuickDeleteAnimationDrawable; private ImageView mActionButton; private @Nullable ColorStateList mActionButtonTint; + private boolean mActorUiVisible; + private boolean mIsAttachedToWindow; public TabGridView(Context context, AttributeSet attrs) { super(context, attrs); @@ -334,6 +338,55 @@ applyActionButtonTint(); } + private @Nullable View getActorUi(boolean inflateIfMissing) { + View actorContainer = fastFindViewById(R.id.actor_ui_container); + + if (actorContainer == null && inflateIfMissing) { + LayoutInflater.from(getContext()).inflate(R.layout.actor_gts_tab_indicator, this, true); + + actorContainer = fastFindViewById(R.id.actor_ui_container); + + assumeNonNull(actorContainer) + .setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + bringChildToFront(actorContainer); + } + return actorContainer; + } + + /** + * Sets the visibility of the actor-specific tab UI elements. Injects the view programmatically + * if it doesn't exist yet. + * + * @param visible Whether the actor active UI should be shown. + */ + public void setActorActiveUiVisible(boolean visible) { + mActorUiVisible = visible; + if (!mIsAttachedToWindow) return; + + View actorContainer = getActorUi(visible); + if (actorContainer == null) return; + + if (visible) { + actorContainer.setVisibility(View.VISIBLE); + } else { + actorContainer.setVisibility(View.GONE); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mIsAttachedToWindow = true; + setActorActiveUiVisible(mActorUiVisible); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mIsAttachedToWindow = false; + } + // SelectableItemViewBase implementation. @Override protected void handleNonSelectionClick() {}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java index 92f8e79..1484355 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -30,6 +30,8 @@ import org.chromium.base.ResettersForTesting; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.UiTabState; +import org.chromium.chrome.browser.actor.ui.TabIndicatorStatus; import org.chromium.chrome.browser.tab.MediaState; import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData.PriceDrop; @@ -245,6 +247,13 @@ } else if (TabProperties.TAB_CONTEXT_CLICK_LISTENER == propertyKey) { setNullableContextClickListener( model.get(TabProperties.TAB_CONTEXT_CLICK_LISTENER), view, model); + } else if (TabProperties.ACTOR_UI_STATE == propertyKey) { + UiTabState state = model.get(TabProperties.ACTOR_UI_STATE); + boolean shouldBeVisible = + state != null + && (state.tabIndicator == TabIndicatorStatus.DYNAMIC + || state.tabIndicator == TabIndicatorStatus.STATIC); + ((TabGridView) view).setActorActiveUiVisible(shouldBeVisible); } }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java index 63878fa2..99addab8 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -58,6 +58,9 @@ import org.chromium.build.annotations.Initializer; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.UiTabState; +import org.chromium.chrome.browser.actor.ui.TabIndicatorStatus; import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingTabManager; @@ -372,6 +375,29 @@ private int mCurrentSpanCount; private @Nullable OnLongPressTabItemEventListener mOnLongPressTabItemEventListener; + private final ActorUiTabController.Observer mActorObserver = + new ActorUiTabController.Observer() { + @Override + public void onUiTabStateChanged(UiTabState state) { + int tabId = state.tabId; + Tab tab = getCurrentTabModelChecked().getTabById(tabId); + if (tab == null) return; + + PropertyModel model = mModelList.getModelFromTabId(tabId); + List<Tab> groupTabs = getRelatedTabsForId(tabId); + if (model == null && mActionsOnAllRelatedTabs) { + for (Tab sibling : groupTabs) { + model = mModelList.getModelFromTabId(sibling.getId()); + if (model != null) break; + } + } + if (model != null) { + model.set( + TabProperties.ACTOR_UI_STATE, getAggregatedActorUiState(groupTabs)); + } + } + }; + private final TabActionListener mTabSelectedListener = new TabActionListener() { @Override @@ -914,7 +940,7 @@ return; } - movedTab.addObserver(mTabObserver); + addObserversForTab(movedTab); onTabAdded(movedTab, /* onlyShowRelatedTabs= */ true); if (mTabGridDialogHandler != null) { mTabGridDialogHandler.updateDialogContent( @@ -1114,7 +1140,7 @@ public void tabClosureUndone(Tab tab) { assert mShowingTabs; - tab.addObserver(mTabObserver); + addObserversForTab(tab); onTabAdded(tab, !mActionsOnAllRelatedTabs); if (sTabClosedFromMap.containsKey(tab.getId())) { @@ -1170,7 +1196,7 @@ return; } - tab.addObserver(mTabObserver); + addObserversForTab(tab); // Check if we need to delay tab addition to model. boolean delayAdd = @@ -1217,7 +1243,7 @@ private void onTabClose(Tab tab) { assert mShowingTabs; - tab.removeObserver(mTabObserver); + removeObserversForTab(tab); // If the tab closed was part of a tab group and the closure was triggered // from the tab switcher, update the group to reflect the closure instead of @@ -1250,7 +1276,7 @@ public void tabRemoved(Tab tab) { assert mShowingTabs; - tab.removeObserver(mTabObserver); + removeObserversForTab(tab); int index = mModelList.indexFromTabId(tab.getId()); if (index == TabModel.INVALID_TAB_INDEX) return; @@ -2020,6 +2046,23 @@ } } + private @Nullable UiTabState getAggregatedActorUiState(List<Tab> tabs) { + for (Tab tab : tabs) { + if (tab == null || !tab.isInitialized()) continue; + + ActorUiTabController controller = ActorUiTabController.from(tab); + if (controller != null) { + UiTabState state = controller.getUiTabState(); + // If at least one tab in the group is being acted on, the indicator will be shown + // on the tab group. + if (state != null && state.tabIndicator != TabIndicatorStatus.NONE) { + return state; + } + } + } + return null; + } + private void unbindTabActionStateProperties(PropertyModel model) { model.set(TabProperties.IS_SELECTED, false); for (WritableObjectPropertyKey propertyKey : TabProperties.TAB_ACTION_STATE_OBJECT_KEYS) { @@ -2191,7 +2234,12 @@ private void addTabInfoToModel(Tab tab, int index, boolean isSelected) { assert index != TabModel.INVALID_TAB_INDEX; boolean isInTabGroup = isTabInTabGroup(tab); - + UiTabState initialState = null; + if (mActionsOnAllRelatedTabs && isInTabGroup) { + initialState = getAggregatedActorUiState(getRelatedTabsForId(tab.getId())); + } else { + initialState = getAggregatedActorUiState(List.of(tab)); + } PropertyModel tabInfo = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID) .with(TabProperties.TAB_ACTION_STATE, mTabActionState) @@ -2217,6 +2265,7 @@ .with(TabProperties.USE_SHRINK_CLOSE_ANIMATION, false) .with(TabProperties.MEDIA_INDICATOR, getTabGridMediaIndicator(tab)) .with(TabProperties.IS_PINNED, tab.getIsPinned()) + .with(TabProperties.ACTOR_UI_STATE, initialState) .build(); if (!mActionsOnAllRelatedTabs || isInTabGroup) { tabInfo.set( @@ -2832,16 +2881,30 @@ // for the oldFilter which can result in invalid updates. } + private void addObserversForTab(Tab tab) { + tab.addObserver(mTabObserver); + + ActorUiTabController controller = ActorUiTabController.from(tab); + if (controller != null) controller.addObserver(mActorObserver); + } + + private void removeObserversForTab(Tab tab) { + tab.removeObserver(mTabObserver); + + ActorUiTabController controller = ActorUiTabController.from(tab); + if (controller != null) controller.removeObserver(mActorObserver); + } + private void addObservers(TabGroupModelFilter filter, List<Tab> tabs) { if (mActionsOnAllRelatedTabs) { for (Tab rootTab : tabs) { for (Tab tab : filter.getRelatedTabList(rootTab.getId())) { - tab.addObserver(mTabObserver); + addObserversForTab(tab); } } } else { for (Tab tab : tabs) { - tab.addObserver(mTabObserver); + addObserversForTab(tab); } } @@ -2857,7 +2920,7 @@ // If no observer was added this will no-op. Previously this was only done in // destroy(), but that left observers behind on the inactive model. for (Tab tab : tabModel) { - tab.removeObserver(mTabObserver); + removeObserversForTab(tab); } filter.removeObserver(mTabModelObserver); filter.removeTabGroupObserver(mTabGroupObserver);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java index 74bbc4d..3d3f457 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java
@@ -14,6 +14,7 @@ import androidx.annotation.IntDef; import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.UiTabState; import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider; import org.chromium.chrome.browser.tasks.tab_management.TabListMediator.ShoppingPersistedTabDataFetcher; import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate; @@ -206,6 +207,10 @@ /** The {@link org.chromium.chrome.browser.tab.TabImpl.MediaState} indicator of the tab. */ public static final WritableIntPropertyKey MEDIA_INDICATOR = new WritableIntPropertyKey(); + /** The {@link ActorUiTabController.UiTabState} indicator of the tab. */ + public static final WritableObjectPropertyKey<UiTabState> ACTOR_UI_STATE = + new WritableObjectPropertyKey<>(); + private static final PropertyKey[] COMMON_KEYS_TAB_AND_GROUP_GRID = new PropertyKey[] { IS_INCOGNITO, @@ -233,6 +238,7 @@ TAB_GROUP_CARD_COLOR, VISIBILITY, USE_SHRINK_CLOSE_ANIMATION, + ACTOR_UI_STATE }; // TAB_ACTION_STATE must always be the first property as keys are iterated in order. TAB_ID must
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java index 127d19e..3bcadbd 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
@@ -36,6 +36,7 @@ import org.chromium.base.supplier.SettableNonNullObservableSupplier; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.Features.EnableFeatures; import org.chromium.chrome.browser.back_press.BackPressManager; import org.chromium.chrome.browser.bookmarks.BookmarkModel; import org.chromium.chrome.browser.bookmarks.TabBookmarker; @@ -44,6 +45,7 @@ import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingTabManager; import org.chromium.chrome.browser.feature_engagement.TrackerFactory; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.hub.PaneManager; import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager; import org.chromium.chrome.browser.layouts.LayoutStateProvider; @@ -85,6 +87,7 @@ import java.util.function.Supplier; /** Unit tests for {@link TabSwitcherPaneCoordinatorFactory}. */ +@EnableFeatures(ChromeFeatureList.GLIC) @RunWith(BaseRobolectricTestRunner.class) public class TabSwitcherPaneCoordinatorFactoryUnitTest { private static final int TAB1_ID = 456;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java index e7e79f4d..a4f9de6 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
@@ -117,6 +117,7 @@ * nothing will crash since the bulk of the behaviors from the coordinator are either unit tested by * classes hosted insider the coordinator or have to be verified in an integration test. */ +@EnableFeatures(ChromeFeatureList.GLIC) @RunWith(BaseRobolectricTestRunner.class) public class TabSwitcherPaneCoordinatorUnitTest {
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java index ca1f9c7..31f348b3 100644 --- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java +++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherLayoutPTTest.java
@@ -120,7 +120,7 @@ @Rule public ChromeRenderTestRule mRenderTestRule = ChromeRenderTestRule.Builder.withPublicCorpus() - .setRevision(13) // GTS update + .setRevision(14) // Setup list .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_HUB) .build();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java index 8f9c07ad..ec2f1b6 100644 --- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java +++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -107,6 +108,11 @@ import org.chromium.base.test.util.Features.DisableFeatures; import org.chromium.base.test.util.Features.EnableFeatures; import org.chromium.build.BuildConfig; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.ActorOverlayState; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.HandoffButtonState; +import org.chromium.chrome.browser.actor.ui.ActorUiTabController.UiTabState; +import org.chromium.chrome.browser.actor.ui.TabIndicatorStatus; import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingTabManager; @@ -227,6 +233,7 @@ @DisableFeatures({ ChromeFeatureList.DATA_SHARING, ChromeFeatureList.DATA_SHARING_JOIN_ONLY, + ChromeFeatureList.GLIC }) public class TabListMediatorUnitTest { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.LENIENT); @@ -367,6 +374,9 @@ @Mock DataSharingService mDataSharingService; @Mock CollaborationService mCollaborationService; @Mock ServiceStatus mServiceStatus; + @Mock ActorUiTabController mActorUiTabController; + @Mock ActorOverlayState mActorOverlayState; + @Mock HandoffButtonState mHandoffButtonState; @Mock UndoBarExplicitTrigger mUndoBarExplicitTrigger; @Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor; @@ -550,6 +560,19 @@ .setTabGroupTitle(any(), anyString()); } + private void setUpActorState(Tab tab, @TabIndicatorStatus int status) { + UiTabState state = + new UiTabState( + tab.getId(), + mActorOverlayState, + mHandoffButtonState, + status, + tab.isIncognito()); + + when(mActorUiTabController.getUiTabState()).thenReturn(state); + tab.getUserDataHost().setUserData(ActorUiTabController.class, mActorUiTabController); + } + @Test public void initializesWithCurrentTabs() { initAndAssertAllProperties(); @@ -5606,6 +5629,110 @@ assertNull(mModelList.get(0).model.get(TabProperties.TAB_CONTEXT_CLICK_LISTENER)); } + @EnableFeatures(ChromeFeatureList.GLIC) + @Test + public void testActorUiState_InitialSet() { + setUpActorState(mTab1, TabIndicatorStatus.DYNAMIC); + + mMediator.resetWithListOfTabs(List.of(mTab1), null, false); + + PropertyModel model = mModelList.get(0).model; + UiTabState state = model.get(TabProperties.ACTOR_UI_STATE); + assertNotNull(state); + assertEquals(TabIndicatorStatus.DYNAMIC, state.tabIndicator); + } + + @EnableFeatures(ChromeFeatureList.GLIC) + @Test + public void testActorUiState_ObserverUpdatesModel() { + setUpActorState(mTab1, TabIndicatorStatus.NONE); + mMediator.resetWithListOfTabs(List.of(mTab1), null, false); + + PropertyModel model = mModelList.get(0).model; + + ArgumentCaptor<ActorUiTabController.Observer> observerCaptor = + ArgumentCaptor.forClass(ActorUiTabController.Observer.class); + verify(mActorUiTabController).addObserver(observerCaptor.capture()); + + setUpActorState(mTab1, TabIndicatorStatus.DYNAMIC); + UiTabState newState = + new UiTabState(TAB1_ID, null, null, TabIndicatorStatus.DYNAMIC, false); + observerCaptor.getValue().onUiTabStateChanged(newState); + assertEquals( + TabIndicatorStatus.DYNAMIC, model.get(TabProperties.ACTOR_UI_STATE).tabIndicator); + } + + @EnableFeatures(ChromeFeatureList.GLIC) + @Test + public void testActorUiState_ObserverRemovedOnReset() { + setUpActorState(mTab1, TabIndicatorStatus.NONE); + mMediator.resetWithListOfTabs(List.of(mTab1), null, false); + + verify(mActorUiTabController, atLeastOnce()).addObserver(any()); + + doReturn(mTabModel).when(mTabGroupModelFilter).getTabModel(); + when(mTabModel.iterator()).thenAnswer(inv -> List.of(mTab1).iterator()); + + mMediator.resetWithListOfTabs(null, null, false); + verify(mActorUiTabController, atLeastOnce()).removeObserver(any()); + } + + @EnableFeatures(ChromeFeatureList.GLIC) + @Test + public void testActorUiState_NewTabAdded() { + mMediator.resetWithListOfTabs(List.of(mTab1), null, false); + + Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL); + setUpActorState(newTab, TabIndicatorStatus.STATIC); + + doReturn(3).when(mTabGroupModelFilter).getIndividualTabAndGroupCount(); + doReturn(newTab).when(mTabGroupModelFilter).getRepresentativeTabAt(2); + doReturn(Arrays.asList(newTab)).when(mTabGroupModelFilter).getRelatedTabList(TAB3_ID); + + mTabModelObserverCaptor + .getValue() + .didAddTab( + newTab, + TabLaunchType.FROM_CHROME_UI, + TabCreationState.LIVE_IN_FOREGROUND, + false); + + int index = mModelList.indexFromTabId(TAB3_ID); + assertNotEquals(TabModel.INVALID_TAB_INDEX, index); + + PropertyModel newModel = mModelList.get(index).model; + assertEquals( + TabIndicatorStatus.STATIC, newModel.get(TabProperties.ACTOR_UI_STATE).tabIndicator); + + verify(mActorUiTabController).addObserver(any()); + } + + @EnableFeatures(ChromeFeatureList.GLIC) + @Test + public void testActorUiState_ObserverUpdatesToNone() { + setUpActorState(mTab1, TabIndicatorStatus.DYNAMIC); + mMediator.resetWithListOfTabs(List.of(mTab1), null, false); + PropertyModel model = mModelList.get(0).model; + + assertNotNull(model.get(TabProperties.ACTOR_UI_STATE)); + assertEquals( + TabIndicatorStatus.DYNAMIC, model.get(TabProperties.ACTOR_UI_STATE).tabIndicator); + ArgumentCaptor<ActorUiTabController.Observer> observerCaptor = + ArgumentCaptor.forClass(ActorUiTabController.Observer.class); + verify(mActorUiTabController).addObserver(observerCaptor.capture()); + + setUpActorState(mTab1, TabIndicatorStatus.NONE); + UiTabState finishedState = + new UiTabState( + TAB1_ID, + mActorOverlayState, + mHandoffButtonState, + TabIndicatorStatus.NONE, + false); + observerCaptor.getValue().onUiTabStateChanged(finishedState); + assertNull(model.get(TabProperties.ACTOR_UI_STATE)); + } + private void setUpTabGroupCardDescriptionString() { doAnswer( invocation -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java index f881e0e..7f0fefb 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java
@@ -18,7 +18,6 @@ import org.chromium.ui.base.IntentRequestTracker; import org.chromium.ui.insets.InsetObserver; import org.chromium.ui.modaldialog.ModalDialogManager; -import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import java.lang.ref.WeakReference; import java.util.function.Supplier; @@ -63,7 +62,6 @@ super( activity, /* listenToActivityState= */ true, - new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), sKeyboardVisibilityDelegateFactory.create( new WeakReference<>(activity), manualFillingComponentSupplier), /* activityTopResumedSupported= */ true,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java index 1e214ff..a512e90 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java
@@ -81,7 +81,7 @@ /* context= */ this, /* componentName= */ parentComponent); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java index 7dd0ee1..ff45bc4 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
@@ -111,9 +111,9 @@ IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, - /* listenToActivityState= */ false, + false, intentRequestTracker, getInsetObserver(), /* trackOcclusion= */ true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java index fa330e4..45a6503b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -81,6 +81,9 @@ import org.chromium.chrome.browser.theme.ThemeColorProvider; import org.chromium.chrome.browser.theme.TopUiThemeColorProvider; import org.chromium.chrome.browser.toolbar.ControlContainer; +import org.chromium.chrome.browser.ui.side_ui.SideUiCoordinator.SideUiSpecs; +import org.chromium.chrome.browser.ui.side_ui.SideUiObserver; +import org.chromium.chrome.browser.ui.side_ui.SideUiStateProvider; import org.chromium.components.browser_ui.widget.TouchEventObserver; import org.chromium.components.browser_ui.widget.TouchEventProvider; import org.chromium.components.content_capture.OnscreenContentProvider; @@ -124,6 +127,7 @@ BrowserControlsStateProvider.Observer, AccessibilityUtil.Observer, TabObscuringHandler.Observer, + SideUiObserver, ViewGroup.OnHierarchyChangeListener { private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500; private static final long BACKGROUND_REMOVAL_TIMEOUT_MS = 2500; @@ -178,6 +182,7 @@ private TabModelSelector mTabModelSelector; private @Nullable BrowserControlsManager mBrowserControlsManager; + private @Nullable SideUiStateProvider mSideUiStateProvider; @VisibleForTesting @Nullable View mAccessibilityView; private @Nullable CompositorAccessibilityProvider mNodeProvider; @@ -1002,6 +1007,15 @@ int width = viewportSize.x; int height = viewportSize.y; + // The view size takes into account side-anchored UI whose width should be subtracted from + // the view if they are visible, therefore shrinking the Blink-side view size. + int horizontalViewportInsets = 0; + if (ChromeFeatureList.sEnableAndroidSidePanel.isEnabled() && mSideUiStateProvider != null) { + SideUiSpecs sideUiSpecs = mSideUiStateProvider.getCurrentSideUiSpecs(); + horizontalViewportInsets = + sideUiSpecs.mStartContainerWidth + sideUiSpecs.mEndContainerWidth; + } + // The view size takes into account of the browser controls whose height should be // subtracted from the view if they are visible, therefore shrink Blink-side view size. // TODO(crbug.com/40767446): Centralize the logic for calculating bottom insets by @@ -1022,10 +1036,10 @@ ? mApplicationBottomInsetSupplier.getInsets().webContentsHeightInset : 0; - int viewportInsets = controlsInsets + keyboardInset; + int verticalViewportInsets = controlsInsets + keyboardInset; if (isAttachedToWindow(view)) { - webContents.setSize(width, height - viewportInsets); + webContents.setSize(width - horizontalViewportInsets, height - verticalViewportInsets); // Dispatch the geometrychange JavaScript event to the page. // TODO(bokan): This doesn't belong in updateWebContentsSize. Ideally the content/ layer @@ -1045,7 +1059,9 @@ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - webContents.setSize(view.getWidth(), view.getHeight() - viewportInsets); + webContents.setSize( + view.getWidth() - horizontalViewportInsets, + view.getHeight() - verticalViewportInsets); requestRender(); } } @@ -1230,6 +1246,19 @@ } } + @Override + public void onSideUiSpecsChanged(SideUiSpecs sideUiSpecs) { + // Rather than using the specs provided here, instead pull directly from + // mSideUiStateProvider. This is done, since we need the offset every time we update the + // WebContents size and we want to avoid caching the specs here. + updateWebContentsSize(getCurrentTab()); + // TODO(crbug.com/483748424): Update #getWindowViewport and #getVisibleViewport through + // #onViewportChanged as well. This change is not trivial, since other items, such as + // the tab strip, infer their bounds from the viewport. For SidePanel, however, we only + // want to resize the WebContents, and not the tab strip. As such, we need to decouple + // the viewport bounds from these items. + } + // View.OnHierarchyChangeListener implementation @Override @@ -1432,6 +1461,18 @@ onViewportChanged(); } + /** + * Sets the {@link SideUiStateProvider}. Will only be called if the related feature flag is + * enabled. + * + * @param sideUiStateProvider The {@link SideUiStateProvider}. + */ + public void setSideUiStateProvider(SideUiStateProvider sideUiStateProvider) { + mSideUiStateProvider = sideUiStateProvider; + mSideUiStateProvider.addObserver(this); + updateWebContentsSize(getCurrentTab()); + } + public int getTopControlsHeightPixels() { return mBrowserControlsManager != null ? mBrowserControlsManager.getTopControlsHeight() : 0; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java index 1783f50..f1fa842 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -998,20 +998,17 @@ mWidth - mRightPadding - (isMsbVisible - ? getModelSelectorButtonWidthWithEndPadding() + ? (getModelSelectorButtonWidthWithEndPadding() + mGlicButton.getWidth() - + GLIC_MSB_BUTTON_PADDING_DP + + GLIC_MSB_BUTTON_PADDING_DP) : getGlicButtonWidthWithEndPadding())); } else { mGlicButton.setDrawX( mLeftPadding + (isMsbVisible - ? getModelSelectorButtonWidthWithEndPadding() - - assumeNonNull(mModelSelectorButton).getWidth() - - GLIC_MSB_BUTTON_PADDING_DP - : 0) - + getGlicButtonWidthWithEndPadding() - - mGlicButton.getWidth()); + ? (getModelSelectorButtonWidthWithEndPadding() + + GLIC_MSB_BUTTON_PADDING_DP) + : mStripEndPadding)); } } @@ -1174,10 +1171,7 @@ mModelSelectorButton.setDrawX( mWidth - mRightPadding - getModelSelectorButtonWidthWithEndPadding()); } else { - mModelSelectorButton.setDrawX( - mLeftPadding - + getModelSelectorButtonWidthWithEndPadding() - - mModelSelectorButton.getWidth()); + mModelSelectorButton.setDrawX(mLeftPadding + mStripEndPadding); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java index 7066565..0aba555 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java
@@ -69,7 +69,7 @@ mFrameLayout = new FrameLayout(this); setContentView(mFrameLayout); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java index 960da39..57d5a0d 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java
@@ -881,7 +881,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java index 7800112..3a2af17 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java
@@ -74,7 +74,7 @@ view, getEdgeToEdgeSupplier()); } mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java index fe66b37e..fd18dc6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java
@@ -358,7 +358,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerTabObserver.java index 0608a29c..794a522 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerTabObserver.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerTabObserver.java
@@ -9,6 +9,7 @@ import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabObserver; +import org.chromium.chrome.browser.ui.native_page.NativePage; import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.WebContents; @@ -102,7 +103,7 @@ private boolean isTabPickable(Tab tab) { // We do not support capture of native pages. - if (tab.isNativePage()) return false; + if (NativePage.isChromePageUrl(tab.getUrl(), tab.isIncognito())) return false; // Filter out all tabs that are not this tab for capture this tab. if (mParams.captureThisTab && tab.getWebContents() != mParams.webContents) return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java index 749a3c8..7f0f6ad 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
@@ -841,7 +841,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java index 894cbee..e9781f8 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -89,7 +89,6 @@ import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.edge_to_edge.EdgeToEdgeSystemBarColorHelper; import org.chromium.ui.modaldialog.ModalDialogManager; -import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import org.chromium.url.GURL; import java.lang.annotation.Retention; @@ -275,7 +274,6 @@ return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, - new ActivityAndroidPermissionDelegate(new WeakReference(this)), new ActivityKeyboardVisibilityDelegate(new WeakReference(this)), /* activityTopResumedSupported= */ false, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java index 8dfa566..2771938b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
@@ -361,7 +361,7 @@ mIntentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroidSupplier.set( - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, /* listenToActivityState= */ true, mIntentRequestTracker,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java index a34448e..48769afa 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java
@@ -203,7 +203,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java index 063dd59..f641d5b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -627,7 +627,7 @@ compositorViewHolderSupplier, modalDialogManagerSupplier, () -> mLayoutManager.getStripLayoutHelperManager(), // Gets current SLHM - getTabObscuringHandler(), + mTabObscuringHandlerSupplier.get(), () -> mToolbarManager // Gets current value of mToolbarManager ); @@ -975,7 +975,7 @@ stub, assumeNonNull(mTabModelSelectorSupplier.get()), mBrowserControlsManager, - assumeNonNull(mTabObscuringHandlerSupplier.get()), + mTabObscuringHandlerSupplier.get(), assumeNonNull(mSnackbarManagerSupplier.get())); } } @@ -1870,6 +1870,8 @@ if (mSidePanelContainerCoordinator != null) { mSidePanelContainerCoordinator.init(); } + + mCompositorViewHolderSupplier.get().setSideUiStateProvider(mSideUiCoordinator); } private void destroySideUi() { @@ -1878,6 +1880,10 @@ // Each SideUiContainer implementation that's registered with SideUiCoordinator should be // destroyed before SideUiCoordinator. + // The CompositorViewHolder itself is a SideUiObserver and queries its reference of the + // SideUiCoordinator. It's expected to be null by this point. + assert mCompositorViewHolderSupplier.get() == null; + if (mSidePanelContainerCoordinator != null) { mSidePanelContainerCoordinator.destroy(); mSidePanelContainerCoordinator = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java index fea5c10..71900a7 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -267,8 +267,7 @@ WindowFocusChangedObserver { private static final String TAG = "RootUiCoordinator"; - protected final SettableMonotonicObservableSupplier<TabObscuringHandler> - mTabObscuringHandlerSupplier = ObservableSuppliers.createMonotonic(); + protected final NonNullObservableSupplier<TabObscuringHandler> mTabObscuringHandlerSupplier; private final SettableMonotonicObservableSupplier<DeviceLockActivityLauncher> mDeviceLockActivityLauncherSupplier = ObservableSuppliers.createMonotonic(); @@ -569,6 +568,7 @@ ? stripLayoutHelperManager.getStripVisibilityStateSupplier() : null; }); + mTabObscuringHandlerSupplier = ObservableSuppliers.createNonNull(new TabObscuringHandler()); setupUnownedUserDataSuppliers(); mActivityLifecycleDispatcher.register(this); @@ -598,7 +598,6 @@ }; mLayoutManagerImplSupplier.addSyncObserverAndPostIfNonNull(layoutManagerSupplierCallback); - mTabObscuringHandlerSupplier.set(new TabObscuringHandler()); mDeviceLockActivityLauncherSupplier.set(DeviceLockActivityLauncherImpl.get()); new AccessibilityVisibilityHandler( mActivityLifecycleDispatcher, @@ -649,7 +648,7 @@ new PageZoomManager( new PageZoomManagerDelegate() { @Override - public WebContents getWebContents() { + public @Nullable WebContents getWebContents() { if (mActivityTabProvider.get() == null) { return null; } @@ -742,7 +741,7 @@ mExpandedBottomSheetHelper = new ExpandedSheetHelperImpl( - mModalDialogManagerSupplier.get(), getTabObscuringHandler()); + mModalDialogManagerSupplier.get(), mTabObscuringHandlerSupplier.get()); mBottomControlsStacker = new BottomControlsStacker(mBrowserControlsManager, mActivity, mWindowAndroid); mTopControlsStacker = @@ -1382,8 +1381,13 @@ return; } + WebContents webContents = tab.getWebContents(); + if (webContents == null) { + return; + } + WebContentsAccessibility wcax = - WebContentsAccessibility.fromWebContents(tab.getWebContents()); + WebContentsAccessibility.fromWebContents(webContents); if (wcax != null) { wcax.setOccludingRect(rect, viewId); } @@ -1401,8 +1405,13 @@ return; } + WebContents webContents = tab.getWebContents(); + if (webContents == null) { + return; + } + WebContentsAccessibility wcax = - WebContentsAccessibility.fromWebContents(tab.getWebContents()); + WebContentsAccessibility.fromWebContents(webContents); if (wcax != null) { wcax.setOccludingRect(null, viewId); } @@ -1531,14 +1540,16 @@ if (shareDelegate == null || tab == null) return; + WebContents webContents = tab.getWebContents(); + assert webContents != null; if (shareDirectly) { RecordUserAction.record("MobileMenuDirectShare"); - new UkmRecorder(tab.getWebContents(), "MobileMenu.DirectShare") + new UkmRecorder(webContents, "MobileMenu.DirectShare") .addBooleanMetric("HasOccurred") .record(); } else { RecordUserAction.record("MobileMenuShare"); - new UkmRecorder(tab.getWebContents(), "MobileMenu.Share") + new UkmRecorder(webContents, "MobileMenu.Share") .addBooleanMetric("HasOccurred") .record(); } @@ -1567,7 +1578,9 @@ if (fromMenu) { RecordUserAction.record("MobileMenuFindInPage"); - new UkmRecorder(tab.getWebContents(), "MobileMenu.FindInPage") + WebContents webContents = tab.getWebContents(); + assert webContents != null; + new UkmRecorder(webContents, "MobileMenu.FindInPage") .addBooleanMetric("HasOccurred") .record(); } else { @@ -1581,11 +1594,11 @@ DemoPaintPreview.showForTab(mActivityTabProvider.get()); return true; } else if (id == R.id.get_image_descriptions_id) { + WebContents webContents = mActivityTabProvider.get().getWebContents(); + assert webContents != null; ImageDescriptionsController.getInstance() .onImageDescriptionsMenuItemSelected( - mActivity, - mModalDialogManagerSupplier.get(), - mActivityTabProvider.get().getWebContents()); + mActivity, mModalDialogManagerSupplier.get(), webContents); return true; } else if (id == R.id.page_zoom_id) { Tab tab = mActivityTabProvider.get();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java index b4efc98..591e73d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
@@ -38,15 +38,12 @@ import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.test.util.DOMUtils; -import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate; import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.IntentRequestTracker; import org.chromium.ui.base.SelectFileDialog; import org.chromium.ui.insets.InsetObserver; -import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import java.io.File; -import java.lang.ref.WeakReference; /** Integration test for select file dialog used for <input type="file" /> */ @RunWith(ChromeJUnit4ClassRunner.class) @@ -80,9 +77,6 @@ super( activity, /* listenToActivityState= */ true, - new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), - new ActivityKeyboardVisibilityDelegate(new WeakReference<>(activity)), - /* activityTopResumedSupported= */ false, IntentRequestTracker.createFromActivity(activity), insetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ViewTransitionPixelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ViewTransitionPixelTest.java index 0555ff71..ad3e6462 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/ViewTransitionPixelTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ViewTransitionPixelTest.java
@@ -68,7 +68,7 @@ // controls works reliably; // Disable edge to edge as part of the test is measuring the keyboard's height, which differs // depending on whether Chrome is drawn e2e. - "disable-features=ResamplingScrollEvents,DrawCutoutEdgeToEdge,EdgeToEdgeBottomChin", + "disable-features=ResamplingScrollEvents", "hide-scrollbars" }) @Batch(Batch.PER_CLASS)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java index 29b0aa3..176c2cce 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java
@@ -187,7 +187,7 @@ () -> { mActivity = activityTestRule.getActivity(); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java index c0a6461..481ac97 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java
@@ -276,7 +276,7 @@ () -> { mActivity = activityTestRule.getActivity(); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java index ea54f33..ed2c6d8 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java
@@ -177,7 +177,7 @@ ThreadUtils.runOnUiThreadBlocking( () -> { mActivity = activityTestRule.getActivity(); - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java index 7ac778ac..855b1e38 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java
@@ -28,8 +28,10 @@ import org.chromium.chrome.browser.media.MediaCapturePickerTabObserver; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabObserver; +import org.chromium.chrome.browser.url_constants.UrlConstantResolver; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.content_public.browser.WebContents; +import org.chromium.url.GURL; /** Tests for the {@link MediaCapturePickerTabObserver}. */ @RunWith(ChromeJUnit4ClassRunner.class) @@ -74,13 +76,18 @@ } } - private Tab createMockTab(WebContents webContents, boolean isNative) { + private Tab createMockTab(WebContents webContents) { final Tab tab = mock(Tab.class); - when(tab.isNativePage()).thenReturn(isNative); when(tab.getWebContents()).thenReturn(webContents); return tab; } + private Tab createMockTab(WebContents webContents, GURL url) { + final Tab tab = createMockTab(webContents); + when(tab.getUrl()).thenReturn(url); + return tab; + } + @Test @SmallTest public void testRegularTab() { @@ -88,7 +95,7 @@ final var observer = new MediaCapturePickerTabObserver(mObserverDelegate, params, mFilterDelegate); - final Tab tab = createMockTab(params.webContents, /* isNative= */ false); + final Tab tab = createMockTab(params.webContents); observer.onTabAdded(tab); verify(mObserverDelegate).onTabAdded(tab); verify(tab).addObserver(any()); @@ -105,7 +112,8 @@ final var observer = new MediaCapturePickerTabObserver(mObserverDelegate, params, mFilterDelegate); - final Tab tab = createMockTab(/* webContents= */ null, /* isNative= */ true); + final Tab tab = + createMockTab(params.webContents, UrlConstantResolver.getOriginalNativeNtpGurl()); observer.onTabAdded(tab); verify(mObserverDelegate, never()).onTabAdded(tab); verify(tab).addObserver(any()); @@ -124,8 +132,8 @@ final var observer = new MediaCapturePickerTabObserver(mObserverDelegate, params, mFilterDelegate); - final Tab thisTab = createMockTab(webContents, /* isNative= */ false); - final Tab anotherTab = createMockTab(mock(WebContents.class), /* isNative= */ false); + final Tab thisTab = createMockTab(webContents); + final Tab anotherTab = createMockTab(mock(WebContents.class)); observer.onTabAdded(thisTab); verify(mObserverDelegate).onTabAdded(thisTab); @@ -154,7 +162,7 @@ // Tab that should be filtered. final WebContents filteredWebContents = mock(WebContents.class); - final Tab filteredTab = createMockTab(filteredWebContents, /* isNative= */ false); + final Tab filteredTab = createMockTab(filteredWebContents); when(mFilterDelegate.shouldFilterWebContents(filteredWebContents)).thenReturn(true); observer.onTabAdded(filteredTab); @@ -163,7 +171,7 @@ // Tab that should not be filtered. final WebContents allowedWebContents = mock(WebContents.class); - final Tab allowedTab = createMockTab(allowedWebContents, /* isNative= */ false); + final Tab allowedTab = createMockTab(allowedWebContents); when(mFilterDelegate.shouldFilterWebContents(allowedWebContents)).thenReturn(false); observer.onTabAdded(allowedTab); @@ -178,7 +186,7 @@ final var observer = new MediaCapturePickerTabObserver(mObserverDelegate, params, mFilterDelegate); - final Tab tab = createMockTab(params.webContents, /* isNative= */ false); + final Tab tab = createMockTab(params.webContents); final ArgumentCaptor<TabObserver> tabObserverCaptor = ArgumentCaptor.forClass(TabObserver.class);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java index 875fcf0c..33a6bdf 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
@@ -114,7 +114,7 @@ mWindowAndroid = ThreadUtils.runOnUiThreadBlocking( () -> { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( sActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(sActivity),
diff --git a/chrome/android/junit/BUILD.gn b/chrome/android/junit/BUILD.gn index 87c276b..1c1fb85b 100644 --- a/chrome/android/junit/BUILD.gn +++ b/chrome/android/junit/BUILD.gn
@@ -220,6 +220,7 @@ "//chrome/browser/ui/android/toolbar:java", "//chrome/browser/ui/browser_window/public/android:java", "//chrome/browser/ui/messages/android:java", + "//chrome/browser/ui/side_ui/public:java", "//chrome/browser/uid/android:java", "//chrome/browser/url_constants/android:java", "//chrome/browser/user_education:java", @@ -1317,6 +1318,7 @@ "//base:base_junit_test_support", "//base:holder_java", "//chrome/android:chrome_java", + "//chrome/browser/actor/android:java", "//chrome/browser/settings:search_java", "//chrome/browser/tab:java", "//chrome/browser/tab_group_suggestion:java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorViewHolderUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorViewHolderUnitTest.java index 9a881399..e882e0c2 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorViewHolderUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorViewHolderUnitTest.java
@@ -78,6 +78,8 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; import org.chromium.chrome.browser.theme.TopUiThemeColorProvider; import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer; +import org.chromium.chrome.browser.ui.side_ui.SideUiCoordinator.SideUiSpecs; +import org.chromium.chrome.browser.ui.side_ui.SideUiStateProvider; import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector; import org.chromium.components.browser_ui.widget.TouchEventObserver; import org.chromium.components.content_capture.ContentCaptureFeatures; @@ -186,6 +188,7 @@ @Mock private MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher; @Mock private InsetObserver mInsetObserver; @Mock private TopUiThemeColorProvider mTopUiThemeColorProvider; + @Mock private SideUiStateProvider mSideUiStateProvider; @Captor private ArgumentCaptor<TabObserver> mTabObserverCaptor; @@ -1208,4 +1211,42 @@ Rect expectedRect = new Rect(138, 3, 464, 59); assertEquals(expectedRect, actualRect); } + + @Test + @EnableFeatures(ChromeFeatureList.ENABLE_ANDROID_SIDE_PANEL) + public void testSetSideUiStateProvider() { + when(mSideUiStateProvider.getCurrentSideUiSpecs()) + .thenReturn(SideUiSpecs.EMPTY_SIDE_UI_SPECS); + mCompositorViewHolder.setSideUiStateProvider(mSideUiStateProvider); + + verify(mSideUiStateProvider).addObserver(mCompositorViewHolder); + } + + @Test + @EnableFeatures(ChromeFeatureList.ENABLE_ANDROID_SIDE_PANEL) + public void testOnSideUiSpecsChanged() { + // Setup. + reset(mWebContents); + + // Viewport dimensions when keyboard is hidden. + int viewportHeight = 941; + int viewportWidth = 1080; + when(mCompositorViewHolder.getWidth()).thenReturn(viewportWidth); + when(mCompositorViewHolder.getHeight()).thenReturn(viewportHeight); + + // Arbitrary Side UI width. + int startContainerWidth = 100; + int endContainerWidth = 200; + SideUiSpecs currentSideUiSpecs = new SideUiSpecs(startContainerWidth, endContainerWidth); + when(mSideUiStateProvider.getCurrentSideUiSpecs()).thenReturn(currentSideUiSpecs); + mCompositorViewHolder.setSideUiStateProvider(mSideUiStateProvider); + + // Act. Pass empty specs, as the CompositorViewHolder is expected to instead query from + // the set SideUiStateProvider. + mCompositorViewHolder.onSideUiSpecsChanged(SideUiSpecs.EMPTY_SIDE_UI_SPECS); + + // Verify. Once through #setSideUiStateProvider and once through #onSideUiSpecsChanged. + verify(mWebContents, times(2)) + .setSize(viewportWidth - (startContainerWidth + endContainerWidth), viewportHeight); + } }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java index f1635dd..4509d48 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java
@@ -203,7 +203,7 @@ doReturn(taskId).when(mActivity).getTaskId(); mIntentRequestTracker = IntentRequestTracker.createFromActivity(mActivity); mWindow = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ false, mIntentRequestTracker,
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java index ae06e3e1..2caa75cb 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
@@ -75,7 +75,7 @@ public void setup() { mActivity = Robolectric.buildActivity(Activity.class).get(); mWindow = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/app/app_management_strings.grdp b/chrome/app/app_management_strings.grdp index f1a8d1d..17ac27f 100644 --- a/chrome/app/app_management_strings.grdp +++ b/chrome/app/app_management_strings.grdp
@@ -150,4 +150,12 @@ <message name="IDS_APP_MANAGEMENT_APP_CONTENT_DIALOG_SUBTITLE" desc="Sub-label for App content dialog in the app settings page."> This app contains web content from </message> + + <!-- Sub-Apps API --> + <message name="IDS_APP_MANAGEMENT_IS_SUB_APP_PERMISSION_EXPLANATION" desc="Explanation text that appears above permissions on various dialogs and in app info. Any changes the user makes to this app's permissions also apply to the parent app and its other sub apps."> + Permissions you allow for <ph name="APP_NAME">$1<ex>Calculator App</ex></ph> will also be allowed for <ph name="PARENT_APP_NAME">$2<ex>Parent app</ex></ph> and its installed apps. + </message> + <message name="IDS_APP_MANAGEMENT_HAS_SUB_APPS_PERMISSION_EXPLANATION" desc="Explanation text that appears underneath the Permission header on an app's settings page. Any changes the user makes to the app's parent app's permissions also apply to this app. The 'Manage' link takes the user to the parent app's settings page where they can change the permissions."> + Permissions you allow for <ph name="APP_NAME">$1<ex>Parent app</ex></ph> will also be allowed for its installed apps. + </message> </grit-part>
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_HAS_SUB_APPS_PERMISSION_EXPLANATION.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_HAS_SUB_APPS_PERMISSION_EXPLANATION.png.sha1 new file mode 100644 index 0000000..fe5b945 --- /dev/null +++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_HAS_SUB_APPS_PERMISSION_EXPLANATION.png.sha1
@@ -0,0 +1 @@ +e3ff4f7c262ee30099967b9d2eedace3a2f67bdf \ No newline at end of file
diff --git a/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_IS_SUB_APP_PERMISSION_EXPLANATION.png.sha1 b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_IS_SUB_APP_PERMISSION_EXPLANATION.png.sha1 new file mode 100644 index 0000000..c1b416ca --- /dev/null +++ b/chrome/app/app_management_strings_grdp/IDS_APP_MANAGEMENT_IS_SUB_APP_PERMISSION_EXPLANATION.png.sha1
@@ -0,0 +1 @@ +3318962b048515b16b0b80406359cb9781e89995 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp index bb965be..6d1f53f 100644 --- a/chrome/app/os_settings_strings.grdp +++ b/chrome/app/os_settings_strings.grdp
@@ -7375,15 +7375,15 @@ Web app installed by your device administrator. </message> - <!-- Multi Apps API --> + <!-- Sub-Apps API --> <message name="IDS_APP_MANAGEMENT_SUB_APPS_LIST_HEADING" desc="Label for the sub-app section in an app's settings page. The section lists all sub-applications that got installed by the current app. These sub-applications might be installed locally, or streamed from a remote location via the parent app."> - Installed and streamed applications from <ph name="APP_NAME">$1<ex>Citrix</ex></ph> + Installed applications from <ph name="APP_NAME">$1<ex>Parent app</ex></ph> </message> <message name="IDS_APP_MANAGEMENT_PARENT_APP_PERMISSION_EXPLANATION" desc="Explanation text that appears underneath the Permission header on an app's settings page. Any changes the user makes to this app's permissions also apply to any installed ('sub') apps."> - Permissions that you allow for <ph name="APP_NAME">$1<ex>Citrix</ex></ph> will also be allowed for its installed and streamed apps. + Permissions you allow for <ph name="APP_NAME">$1<ex>Parent app</ex></ph> will also be allowed for its installed apps. </message> <message name="IDS_APP_MANAGEMENT_SUB_APP_PERMISSION_EXPLANATION" desc="Explanation text that appears underneath the Permission header on an app's settings page. Any changes the user makes to the app's parent app's permissions also apply to this app. The 'Manage' link takes the user to the parent app's settings page where they can change the permissions."> - Permissions that you allow for <ph name="APP_NAME">$1<ex>Citrix</ex></ph> will also be allowed for this app. <ph name="BEGIN_LINK"><a href="#"></ph>Manage<ph name="END_LINK"></a></ph> + Permissions you allow for <ph name="APP_NAME">$1<ex>Parent app</ex></ph> will also be allowed for this app. <ph name="BEGIN_LINK"><a href="#"></ph>Manage permissions for parent app<ph name="END_LINK"></a></ph> </message> <!-- Safety Hub -->
diff --git a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_PARENT_APP_PERMISSION_EXPLANATION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_PARENT_APP_PERMISSION_EXPLANATION.png.sha1 index a268e47..4bd4d86 100644 --- a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_PARENT_APP_PERMISSION_EXPLANATION.png.sha1 +++ b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_PARENT_APP_PERMISSION_EXPLANATION.png.sha1
@@ -1 +1 @@ -0f31987ce6456a8732f6ae9fa45a9616f3ae857d \ No newline at end of file +f62fadb7ad6137918c906b46dbd5fe3dde79ff49 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APPS_LIST_HEADING.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APPS_LIST_HEADING.png.sha1 index 1ea558ec2..5491ce8 100644 --- a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APPS_LIST_HEADING.png.sha1 +++ b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APPS_LIST_HEADING.png.sha1
@@ -1 +1 @@ -895fe2c3ac5ae1c489d17eaf1da7460c43cee01d \ No newline at end of file +7d969b95ab8a7cac1ddf368a37e533e8dec54ab4 \ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APP_PERMISSION_EXPLANATION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APP_PERMISSION_EXPLANATION.png.sha1 index 46e41d46..f8d710c 100644 --- a/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APP_PERMISSION_EXPLANATION.png.sha1 +++ b/chrome/app/os_settings_strings_grdp/IDS_APP_MANAGEMENT_SUB_APP_PERMISSION_EXPLANATION.png.sha1
@@ -1 +1 @@ -49a37941cd86e918e81f03aba9ef3b7b0bf81905 \ No newline at end of file +86349303d376a341941705ce4988013edbb53a63 \ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 261648a..4455d5d 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -496,14 +496,6 @@ "history_clusters/history_clusters_service_factory.h", "history_clusters/history_clusters_tab_helper.cc", "history_clusters/history_clusters_tab_helper.h", - "history_embeddings/chrome_history_embeddings_service.cc", - "history_embeddings/chrome_history_embeddings_service.h", - "history_embeddings/history_embeddings_service_factory.cc", - "history_embeddings/history_embeddings_service_factory.h", - "history_embeddings/history_embeddings_tab_helper.cc", - "history_embeddings/history_embeddings_tab_helper.h", - "history_embeddings/history_embeddings_utils.cc", - "history_embeddings/history_embeddings_utils.h", "icon_loader.cc", "icon_loader.h", "icon_manager.cc", @@ -1626,6 +1618,7 @@ ":chrome_content_browser_client_parts", "//chrome/browser/google", "//chrome/browser/headless", + "//chrome/browser/history_embeddings", "//chrome/browser/interstitials", "//chrome/browser/obsolete_system", "//chrome/browser/profiles", @@ -1758,6 +1751,7 @@ "//chrome/browser/hang_monitor", "//chrome/browser/heavy_ad_intervention", "//chrome/browser/history", + "//chrome/browser/history_embeddings:impl", "//chrome/browser/image_decoder", "//chrome/browser/image_fetcher", "//chrome/browser/interstitials:impl", @@ -2090,6 +2084,7 @@ "//components/enterprise/common/proto:connectors_proto", "//components/enterprise/common/proto:extensions_workflow_events_proto", "//components/enterprise/content", + "//components/enterprise/data_protection", "//components/enterprise/device_trust", "//components/enterprise/encryption/cache", "//components/enterprise/obfuscation/core:enterprise_obfuscation", @@ -2119,8 +2114,6 @@ "//components/history_clusters/core", "//components/history_clusters/history_clusters_internals/webui", "//components/history_clusters/history_clusters_internals/webui:constants", - "//components/history_embeddings/content", - "//components/history_embeddings/core", "//components/infobars/content", "//components/infobars/core", "//components/invalidation", @@ -3672,8 +3665,8 @@ "enterprise/data_protection/data_protection_page_user_data.h", "enterprise/data_protection/data_protection_ui_controller.cc", "enterprise/data_protection/data_protection_ui_controller.h", - "enterprise/data_protection/data_protection_url_lookup_service.cc", - "enterprise/data_protection/data_protection_url_lookup_service.h", + "enterprise/data_protection/data_protection_url_lookup_service_factory.cc", + "enterprise/data_protection/data_protection_url_lookup_service_factory.h", "enterprise/reporting/browser_report_generator_desktop.cc", "enterprise/reporting/browser_report_generator_desktop.h", "enterprise/reporting/extension_info.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 5e881b65..ec4632f 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -1117,7 +1117,6 @@ {"cookie_controls", "true"}, {"click_to_call", "true"}, {"collaboration_messaging", "true"}, - {"discounts", "true"}, {"file_system_access", "true"}, {"filled_card_information", "true"}, {"find", "true"}, @@ -4981,11 +4980,6 @@ {"enable-javascript-harmony", flag_descriptions::kJavascriptHarmonyName, flag_descriptions::kJavascriptHarmonyDescription, kOsAll, SINGLE_VALUE_TYPE(switches::kJavaScriptHarmony)}, - {"enable-enterprise-badging-for-ntp-footer", - flag_descriptions::kEnterpriseBadgingForNtpFooterName, - flag_descriptions::kEnterpriseBadgingForNtpFooterDescription, - kOsMac | kOsWin | kOsLinux, - FEATURE_VALUE_TYPE(features::kEnterpriseBadgingForNtpFooter)}, {"enable-experimental-webassembly-features", flag_descriptions::kExperimentalWebAssemblyFeaturesName, flag_descriptions::kExperimentalWebAssemblyFeaturesDescription, kOsAll, @@ -6462,6 +6456,11 @@ kAndroidAppIntegrationMultiDataSourceVariations, "AndroidAppIntegrationMultiDataSource")}, + {"android-history-clustering", + flag_descriptions::kAndroidHistoryClusteringName, + flag_descriptions::kAndroidHistoryClusteringDescription, kOsAndroid, + FEATURE_VALUE_TYPE(chrome::android::kAndroidHistoryClustering)}, + {"new-tab-page-customization-v2", flag_descriptions::kNewTabPageCustomizationV2Name, flag_descriptions::kNewTabPageCustomizationV2Description, kOsAndroid,
diff --git a/chrome/browser/actor/android/BUILD.gn b/chrome/browser/actor/android/BUILD.gn index db3968c..0a8b105 100644 --- a/chrome/browser/actor/android/BUILD.gn +++ b/chrome/browser/actor/android/BUILD.gn
@@ -36,6 +36,7 @@ "//build/android:build_java", "//chrome/app:java_strings_grd", "//chrome/browser/browser_controls/android:java", + "//chrome/browser/flags:java", "//chrome/browser/profiles/android:java", "//chrome/browser/tab:java", "//chrome/browser/tabmodel:java", @@ -96,6 +97,7 @@ sources = [ "java/res/drawable/actor_overlay_background.xml", "java/res/layout/actor_control_layout.xml", + "java/res/layout/actor_gts_tab_indicator.xml", "java/res/layout/actor_overlay.xml", ] deps = [
diff --git a/chrome/browser/actor/android/java/res/layout/actor_gts_tab_indicator.xml b/chrome/browser/actor/android/java/res/layout/actor_gts_tab_indicator.xml new file mode 100644 index 0000000..e8652a98 --- /dev/null +++ b/chrome/browser/actor/android/java/res/layout/actor_gts_tab_indicator.xml
@@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2026 The Chromium Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/actor_ui_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + tools:ignore="MergeRootFrame"> + + <ImageView + android:id="@+id/actor_center_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center" + android:src="@drawable/ic_spark_24dp" + android:tint="@macro/default_control_color_active" + android:importantForAccessibility="no" /> +</FrameLayout> \ No newline at end of file
diff --git a/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ui/ActorUiTabController.java b/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ui/ActorUiTabController.java index f02f355..df321d6 100644 --- a/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ui/ActorUiTabController.java +++ b/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ui/ActorUiTabController.java
@@ -4,6 +4,10 @@ package org.chromium.chrome.browser.actor.ui; +import static org.chromium.build.NullUtil.assumeNonNull; + +import androidx.annotation.VisibleForTesting; + import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; import org.jni_zero.NativeMethods; @@ -12,7 +16,9 @@ import org.chromium.base.UserData; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.tab.TabId; /** Java-side representation of the C++ ActorUiTabControllerAndroid. */ @JNINamespace("actor::ui") @@ -22,6 +28,7 @@ /** Represents visual state of the tab's actor components. */ public static class UiTabState { + public final @TabId int tabId; public final ActorOverlayState actorOverlay; public final HandoffButtonState handoffButton; public final @TabIndicatorStatus int tabIndicator; @@ -30,16 +37,20 @@ /** * Constructor for UiTabState. * + * @param tabId The tab Id. * @param actorOverlay The overlay configuration. * @param handoffButton The handoff button configuration. * @param tabIndicator The current {@link TabIndicatorStatus}. * @param borderGlowVisible Whether the glow effect is active. */ - UiTabState( + @VisibleForTesting + public UiTabState( + @TabId int tabId, ActorOverlayState actorOverlay, HandoffButtonState handoffButton, @TabIndicatorStatus int tabIndicator, boolean borderGlowVisible) { + this.tabId = tabId; this.actorOverlay = actorOverlay; this.handoffButton = handoffButton; this.tabIndicator = tabIndicator; @@ -60,7 +71,8 @@ * @param borderGlowVisible True if the boundary glow is active. * @param mouseDown True if a click is currently being simulated. */ - ActorOverlayState(boolean isActive, boolean borderGlowVisible, boolean mouseDown) { + @VisibleForTesting + public ActorOverlayState(boolean isActive, boolean borderGlowVisible, boolean mouseDown) { this.isActive = isActive; this.borderGlowVisible = borderGlowVisible; this.mouseDown = mouseDown; @@ -85,7 +97,8 @@ * @param isActive True if the handoff button should be visible. * @param controller The current {@link ControlOwnership} value. */ - HandoffButtonState(boolean isActive, @ControlOwnership int controller) { + @VisibleForTesting + public HandoffButtonState(boolean isActive, @ControlOwnership int controller) { this.isActive = isActive; this.controller = controller; } @@ -102,7 +115,9 @@ private @Nullable UiTabState mCurrentState; /** Returns the controller for a given tab, creating it if necessary. */ - public static ActorUiTabController from(Tab tab) { + public static @Nullable ActorUiTabController from(Tab tab) { + if (!ChromeFeatureList.isEnabled(ChromeFeatureList.GLIC)) return null; + ActorUiTabController controller = tab.getUserDataHost().getUserData(USER_DATA_KEY); if (controller == null) { controller = new ActorUiTabController(tab); @@ -136,7 +151,7 @@ } /** Returns the most recent UI state snapshot for this tab. */ - public @Nullable UiTabState getTabUiState() { + public @Nullable UiTabState getUiTabState() { return mCurrentState; } @@ -154,9 +169,10 @@ return false; } - from(tab) + assumeNonNull(from(tab)) .onUiTabStateChange( new UiTabState( + tab.getId(), new ActorOverlayState(overlayActive, overlayGlow, overlayClick), new HandoffButtonState(handoffActive, handoffOwner), indicator, @@ -174,7 +190,6 @@ @Override public void destroy() { - mTab.getUserDataHost().removeUserData(USER_DATA_KEY); mObservers.clear(); }
diff --git a/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.cc b/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.cc index 3f100322..f2d8fca 100644 --- a/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.cc +++ b/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.cc
@@ -24,7 +24,7 @@ ActorKeyedService* actor_keyed_service) : ActorUiTabControllerInterface(tab), tab_(tab), - actor_keyed_service_(CHECK_DEREF(actor_keyed_service)), + actor_keyed_service_(actor_keyed_service), task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), scoped_unowned_user_data_(tab.GetUnownedUserDataHost(), *this) {}
diff --git a/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.h b/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.h index 6f5ed07..0b7a918 100644 --- a/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.h +++ b/chrome/browser/actor/android/ui/actor_ui_tab_controller_android.h
@@ -39,7 +39,7 @@ private: const raw_ref<tabs::TabInterface> tab_; - const raw_ref<ActorKeyedService> actor_keyed_service_; + const raw_ptr<ActorKeyedService> actor_keyed_service_; UiTabState current_ui_tab_state_; scoped_refptr<base::SingleThreadTaskRunner> task_runner_; ::ui::ScopedUnownedUserData<ActorUiTabControllerAndroid>
diff --git a/chrome/browser/ai/BUILD.gn b/chrome/browser/ai/BUILD.gn index be38c69..4076bcd 100644 --- a/chrome/browser/ai/BUILD.gn +++ b/chrome/browser/ai/BUILD.gn
@@ -69,6 +69,7 @@ ] deps = [ ":ai", + "//chrome/browser/history_embeddings", "//chrome/browser/ui/tabs:tabs_public", "//components/autofill/content/browser", "//components/autofill/core/browser",
diff --git a/chrome/browser/android/BUILD.gn b/chrome/browser/android/BUILD.gn index 3decc9ed..ae506a9f 100644 --- a/chrome/browser/android/BUILD.gn +++ b/chrome/browser/android/BUILD.gn
@@ -36,6 +36,7 @@ deps = [ ":tabs_public", "//chrome/browser/actor", + "//chrome/browser/actor/android", "//chrome/browser/contextual_search", "//chrome/browser/glic", "//chrome/browser/sync",
diff --git a/chrome/browser/android/tab_features.cc b/chrome/browser/android/tab_features.cc index 54b3279..ab7e002 100644 --- a/chrome/browser/android/tab_features.cc +++ b/chrome/browser/android/tab_features.cc
@@ -5,11 +5,15 @@ #include "chrome/browser/android/tab_features.h" #include "chrome/browser/actor/actor_features.h" +#include "chrome/browser/actor/actor_keyed_service.h" #include "chrome/browser/actor/actor_tab_data.h" +#include "chrome/browser/actor/android/ui/actor_ui_tab_controller_android.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/glic/public/widget/glic_side_panel_coordinator_android.h" #include "chrome/browser/glic/service/glic_instance_helper.h" #include "chrome/browser/net/qwac_web_contents_observer.h" #include "chrome/browser/preloading/new_tab_page_preload/new_tab_page_preload_pipeline_manager.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/sessions/sync_sessions_router_tab_helper.h" #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router_factory.h" #include "chrome/browser/translate/chrome_translate_client.h" @@ -45,6 +49,15 @@ actor_tab_data_ = GetUserDataFactory().CreateInstance<actor::ActorTabData>(*tab, tab); } + + auto* actor_service = actor::ActorKeyedService::Get(profile); + if (glic::GlicEnabling::IsProfileEligible(profile) && actor_service) { + actor_ui_tab_controller_ = + GetUserDataFactory() + .CreateInstance<actor::ui::ActorUiTabControllerAndroid>( + *tab, *tab, actor_service); + } + tab_contextualization_controller_ = GetUserDataFactory().CreateInstance<lens::TabContextualizationController>( *tab, tab);
diff --git a/chrome/browser/android/tab_features.h b/chrome/browser/android/tab_features.h index 62ef02f8..66a7ad57 100644 --- a/chrome/browser/android/tab_features.h +++ b/chrome/browser/android/tab_features.h
@@ -18,6 +18,10 @@ class ActorTabData; } // namespace actor +namespace actor::ui { +class ActorUiTabControllerInterface; +} // namespace actor::ui + namespace content { class WebContents; } // namespace content @@ -70,6 +74,8 @@ std::unique_ptr<glic::GlicInstanceHelper> glic_instance_helper_; std::unique_ptr<glic::GlicSidePanelCoordinator> glic_side_panel_coordinator_; + std::unique_ptr<actor::ui::ActorUiTabControllerInterface> + actor_ui_tab_controller_; }; } // namespace tabs
diff --git a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc index 3a090d1..e7ddfd3f 100644 --- a/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc +++ b/chrome/browser/ash/child_accounts/on_device_controls/blocked_app_registry.cc
@@ -213,8 +213,8 @@ } int BlockedAppRegistry::GetUninstalledBlockedAppCount() const { - return std::count_if( - registry_.begin(), registry_.end(), + return std::ranges::count_if( + registry_, [](std::pair<std::string, BlockedAppDetails> const& blocked_app) { return !blocked_app.second.IsInstalled(); });
diff --git a/chrome/browser/ash/file_manager/file_tasks.cc b/chrome/browser/ash/file_manager/file_tasks.cc index 64350d1..7807fa9 100644 --- a/chrome/browser/ash/file_manager/file_tasks.cc +++ b/chrome/browser/ash/file_manager/file_tasks.cc
@@ -361,7 +361,7 @@ const std::vector<std::optional<apps::LaunchResult::State>>& opens) { const int num_opened = - std::count_if(opens.begin(), opens.end(), [](auto& o) { + std::ranges::count_if(opens, [](auto& o) { return o.has_value() && o.value() == apps::LaunchResult::State::kSuccess; });
diff --git a/chrome/browser/autocomplete/BUILD.gn b/chrome/browser/autocomplete/BUILD.gn index e4f620b..0760c61 100644 --- a/chrome/browser/autocomplete/BUILD.gn +++ b/chrome/browser/autocomplete/BUILD.gn
@@ -85,6 +85,7 @@ "//chrome/browser/bookmarks", "//chrome/browser/custom_handlers", "//chrome/browser/history", + "//chrome/browser/history_embeddings", "//chrome/browser/prefs", "//chrome/browser/profiles", "//chrome/browser/profiles:profile_io_data",
diff --git a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetContent.java b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetContent.java index 2ae068d..780f717 100644 --- a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetContent.java +++ b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetContent.java
@@ -75,12 +75,12 @@ @Override public float getHalfHeightRatio() { - return 0.4f; + return HeightMode.DISABLED; } @Override public float getFullHeightRatio() { - return HeightMode.DEFAULT; + return 0.7f; } @Override @@ -89,11 +89,6 @@ } @Override - public boolean skipHalfStateOnScrollingDown() { - return false; - } - - @Override public boolean hideOnScroll() { return false; }
diff --git a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetCoordinator.java b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetCoordinator.java index b31ecdd..14524e80 100644 --- a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetCoordinator.java +++ b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetCoordinator.java
@@ -119,6 +119,8 @@ return new EmptyBottomSheetObserver() { @Override public void onSheetOffsetChanged(float heightFraction, float offsetPx) { + if (!TabBottomSheetUtils.canResizeWebView()) return; + mMediator.onSheetOffsetChanged(offsetPx); } };
diff --git a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetUtils.java b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetUtils.java index 1471e5d..573849db 100644 --- a/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetUtils.java +++ b/chrome/browser/context_sharing/tab_bottom_sheet/android/java/src/org/chromium/chrome/browser/tab_bottom_sheet/TabBottomSheetUtils.java
@@ -23,6 +23,10 @@ return ChromeFeatureList.sTabBottomSheet.isEnabled(); } + public static boolean canResizeWebView() { + return ChromeFeatureList.sTabBottomSheetResizeWebview.getValue(); + } + public static boolean shouldShowFusebox() { return !ChromeFeatureList.sTabBottomSheetDontShowFusebox.getValue(); }
diff --git a/chrome/browser/contextual_search/contextual_search_web_contents_helper.h b/chrome/browser/contextual_search/contextual_search_web_contents_helper.h index cd43d4b..a09cc04d 100644 --- a/chrome/browser/contextual_search/contextual_search_web_contents_helper.h +++ b/chrome/browser/contextual_search/contextual_search_web_contents_helper.h
@@ -66,11 +66,11 @@ : nullptr; } - std::unique_ptr<contextual_search::InputStateModel> GetInputStateModelForTask( - const base::Uuid& task_id) { + std::unique_ptr<contextual_search::InputStateModel> + TakeInputStateModelForTask(const base::Uuid& task_id) { // Return and transfer ownership of the model if it matches the task. - if (input_state_model_ && task_id_ == task_id) { - return std::move(input_state_model_); + if (task_id_ == task_id) { + return TakeInputStateModel(); } return nullptr; }
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc index 96617d2..e6539258 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc
@@ -160,7 +160,7 @@ mojo::PendingReceiver<searchbox::mojom::PageHandler> pending_searchbox_handler, GetSessionHandleCallback get_session_callback, - GetInputStateModelCallback get_input_model_callback) + TakeInputStateModelCallback take_input_model_callback) : ComposeboxHandler( std::move(pending_handler), std::move(pending_page), @@ -172,7 +172,7 @@ web_contents, this)), std::move(get_session_callback)), - get_input_model_callback_(std::move(get_input_model_callback)), + take_input_model_callback_(std::move(take_input_model_callback)), web_ui_interface_(web_ui_interface), contextual_tasks_service_( contextual_tasks::ContextualTasksServiceFactory::GetForProfile( @@ -453,9 +453,9 @@ } void ContextualTasksComposeboxHandler::InitializeInputStateModel() { - if (get_input_model_callback_) { + if (take_input_model_callback_) { std::unique_ptr<contextual_search::InputStateModel> current_input_state = - std::move(get_input_model_callback_).Run(); + std::move(take_input_model_callback_).Run(); if (current_input_state) { ResetInputStateModel(); @@ -467,12 +467,29 @@ weak_ptr_factory_.GetWeakPtr())); input_state_model_->Initialize(); - return; + } else { + ResetInputStateModel(); + ContextualSearchboxHandler::InitializeInputStateModel(); } + } else { + ResetInputStateModel(); + ContextualSearchboxHandler::InitializeInputStateModel(); } - ResetInputStateModel(); - ContextualSearchboxHandler::InitializeInputStateModel(); + if (input_state_model_) { + // crbug.com/488112121: Temporary implementation to disable file and deep + // search when the aegc=1 URL parameter is present on the AI page. + // This is moved from the WebUI to C++ to avoid extra Mojo APIs. + GURL inner_frame_url = web_ui_interface_->GetInnerFrameUrl(); + std::string aegc_val; + if (net::GetValueForKeyInQuery(inner_frame_url, "aegc", &aegc_val) && + aegc_val == "1") { + input_state_model_->SetPermanentlyDisabledTools( + {omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH}); + input_state_model_->SetPermanentlyDisabledInputTypes( + {omnibox::InputType::INPUT_TYPE_LENS_FILE}); + } + } } void ContextualTasksComposeboxHandler::AddFileContextFromBrowser(
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.h b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.h index 822f1e48..3809fbe0 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.h +++ b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.h
@@ -50,7 +50,7 @@ public ui::SelectFileDialog::Listener { public: friend class ContextualTasksComposeboxHandlerTest; - using GetInputStateModelCallback = + using TakeInputStateModelCallback = base::OnceCallback<std::unique_ptr<contextual_search::InputStateModel>()>; ContextualTasksComposeboxHandler( @@ -62,7 +62,7 @@ mojo::PendingReceiver<searchbox::mojom::PageHandler> pending_searchbox_handler, GetSessionHandleCallback get_session_callback, - GetInputStateModelCallback get_input_model_callback); + TakeInputStateModelCallback take_input_model_callback); ~ContextualTasksComposeboxHandler() override; // composebox::mojom::PageHandler: @@ -85,7 +85,7 @@ void OnTaskChanged(); // We override this method to inject an existing `InputStateModel` if one is - // provided by the ContextualTasksUI via the `get_input_model_callback_`. + // provided by the ContextualTasksUI via the `take_input_model_callback_`. void InitializeInputStateModel() override; void AddFileContextFromBrowser( @@ -150,7 +150,7 @@ // Potentially submits query if no other context is uploading. void MarkContextUploadFinished(const base::UnguessableToken& token); - GetInputStateModelCallback get_input_model_callback_; + TakeInputStateModelCallback take_input_model_callback_; // Called when a delayed context upload (tab) has finished. // Potentially submits query if no other context is uploading.
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler_unittest.cc b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler_unittest.cc index 1dc7da6..6651927 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler_unittest.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler_unittest.cc
@@ -38,6 +38,7 @@ #include "components/contextual_search/contextual_search_session_handle.h" #include "components/contextual_search/fake_variations_client.h" #include "components/contextual_search/mock_contextual_search_context_controller.h" +#include "components/contextual_search/mock_contextual_search_session_handle.h" #include "components/contextual_tasks/public/contextual_task.h" #include "components/contextual_tasks/public/contextual_tasks_service.h" #include "components/contextual_tasks/public/features.h" @@ -122,6 +123,11 @@ MOCK_METHOD(const std::optional<base::Uuid>&, GetTaskId, (), (override)); MOCK_METHOD(BrowserWindowInterface*, GetBrowser, (), (override)); MOCK_METHOD(bool, IsLensOverlayShowing, (), (const, override)); + MOCK_METHOD(const GURL&, GetInnerFrameUrl, (), (const, override)); + MOCK_METHOD(std::unique_ptr<contextual_search::InputStateModel>, + TakeInputStateModel, + (), + (override)); }; class TestContextualTasksComposeboxHandler @@ -161,7 +167,7 @@ mock_contextual_tasks_service_ = contextual_tasks_service; } - contextual_search::InputStateModel* GetInputStateModelForTesting() { + contextual_search::InputStateModel* TakeInputStateModelForTesting() { return input_state_model_.get(); } @@ -264,6 +270,8 @@ ON_CALL(*mock_ui_, GetTaskId()) .WillByDefault(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); ON_CALL(*mock_ui_, GetBrowser()).WillByDefault(testing::Return(browser())); + ON_CALL(*mock_ui_, GetInnerFrameUrl()) + .WillByDefault(testing::ReturnRefOfCopy(GURL())); // Create mock controller directly. mock_contextual_tasks_service_owner_ = std::make_unique< @@ -282,7 +290,7 @@ base::BindRepeating( &ContextualTasksUI::GetOrCreateContextualSessionHandle, base::Unretained(mock_ui_.get())), - base::BindRepeating(&ContextualTasksUI::GetInputStateModel, + base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, base::Unretained(mock_ui_.get()))); handler_->SetMockContextualTasksService(mock_contextual_tasks_service_ptr_); @@ -833,6 +841,44 @@ base::RunLoop().RunUntilIdle(); } +// crbug.com/488112121: This test covers the temporary behavior of disabling +// tools when the aegc=1 URL parameter is present. Remove this test when the +// temporary workaround in ContextualTasksComposeboxHandler is removed. +TEST_F(ContextualTasksComposeboxHandlerTest, AegcParameterDisablesTools) { + omnibox::SearchboxConfig config; + auto* rule_set = config.mutable_rule_set(); + rule_set->add_allowed_input_types(omnibox::InputType::INPUT_TYPE_LENS_IMAGE); + rule_set->add_allowed_input_types(omnibox::InputType::INPUT_TYPE_LENS_FILE); + rule_set->add_allowed_tools(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH); + + auto session_handle = + std::make_unique<contextual_search::MockContextualSearchSessionHandle>(); + auto input_state_model = std::make_unique<contextual_search::InputStateModel>( + *session_handle, config, /*is_off_the_record=*/false); + + EXPECT_CALL(*mock_ui_, TakeInputStateModel()) + .WillOnce(testing::Return(testing::ByMove(std::move(input_state_model)))); + + GURL aegc_url("https://gemini.google.com/app?aegc=1"); + EXPECT_CALL(*mock_ui_, GetInnerFrameUrl()) + .WillRepeatedly(testing::ReturnRef(aegc_url)); + + // Re-initialize the model to pick up the URL change. + handler_->OnTaskChanged(); + + auto* model = handler_->TakeInputStateModelForTesting(); + ASSERT_TRUE(model); + + const auto& state = model->get_state_for_testing(); + + EXPECT_THAT(state.disabled_tools, + testing::Contains(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH)); + + EXPECT_THAT( + state.disabled_input_types, + testing::UnorderedElementsAre(omnibox::InputType::INPUT_TYPE_LENS_FILE)); +} + TEST_F(ContextualTasksComposeboxHandlerTest, CreateAndSendQueryMessage_RecontextualizeScreenshotChanged_SkBitmap) { ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) @@ -2684,7 +2730,7 @@ []() -> contextual_search::ContextualSearchSessionHandle* { return nullptr; }), - base::BindRepeating(&ContextualTasksUI::GetInputStateModel, + base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, base::Unretained(mock_ui_.get()))); auto file_info = searchbox::mojom::SelectedFileInfo::New(); std::vector<uint8_t> data = {0x1}; @@ -2743,8 +2789,8 @@ model->setActiveModel(omnibox::ModelMode::MODEL_MODE_GEMINI_PRO); return model; }); - mojo::PendingRemote<composebox::mojom::Page> page_remote; + auto page_receiver = page_remote.InitWithNewPipeAndPassReceiver(); auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( mock_ui_.get(), profile(), web_contents(), mojo::PendingReceiver<composebox::mojom::PageHandler>(), @@ -2761,7 +2807,7 @@ // 3. Assert: Verify the handler successfully took ownership of the model // and the internal state matches exactly what the callback provided. contextual_search::InputStateModel* handler_model = - custom_handler->GetInputStateModelForTesting(); + custom_handler->TakeInputStateModelForTesting(); ASSERT_NE(handler_model, nullptr); EXPECT_EQ(handler_model->get_state_for_testing().active_model,
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator_interactive_uitest.cc b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator_interactive_uitest.cc index 22f7a5cf..53b00b59 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator_interactive_uitest.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator_interactive_uitest.cc
@@ -53,7 +53,7 @@ mojo::PendingReceiver<searchbox::mojom::PageHandler> pending_searchbox_handler, GetSessionHandleCallback get_session_callback, - GetInputStateModelCallback get_inputstatemodel_callback) + TakeInputStateModelCallback get_inputstatemodel_callback) : ContextualTasksComposeboxHandler( ui_controller, profile, @@ -678,7 +678,7 @@ base::BindRepeating( &ContextualTasksUI::GetOrCreateContextualSessionHandle, base::Unretained(ui)), - base::BindRepeating(&ContextualTasksUI::GetInputStateModel, + base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, base::Unretained(ui))); MockContextualTasksComposeboxHandler* mock_handler = mock_composebox_handler.get();
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc index ba8859d7..528b3c8 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc
@@ -634,7 +634,7 @@ base::BindRepeating( &ContextualTasksUI::GetOrCreateContextualSessionHandle, base::Unretained(this)), - base::BindRepeating(&ContextualTasksUI::GetInputStateModel, + base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, base::Unretained(this))); composebox_handler_->SetPage(std::move(pending_searchbox_page)); } @@ -691,7 +691,7 @@ } std::unique_ptr<contextual_search::InputStateModel> -ContextualTasksUI::GetInputStateModel() { +ContextualTasksUI::TakeInputStateModel() { if (!task_id_.has_value()) { return nullptr; } @@ -700,7 +700,7 @@ auto* helper = ContextualSearchWebContentsHelper::GetOrCreateForWebContents( web_contents); - return helper->GetInputStateModelForTask(task_id_.value()); + return helper->TakeInputStateModelForTask(task_id_.value()); } void ContextualTasksUI::MoveTaskUiToNewTab() {
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui.h b/chrome/browser/contextual_tasks/contextual_tasks_ui.h index bc7653b..1dc21ad 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui.h +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui.h
@@ -155,7 +155,8 @@ void PostMessageToWebview(const lens::ClientToAimMessage& message) override; contextual_search::ContextualSearchSessionHandle* GetOrCreateContextualSessionHandle() override; - std::unique_ptr<contextual_search::InputStateModel> GetInputStateModel(); + std::unique_ptr<contextual_search::InputStateModel> TakeInputStateModel() + override; mojo::Remote<contextual_tasks::mojom::Page>& GetPageRemote() override; const GURL& GetInnerFrameUrl() const override;
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_interface.h b/chrome/browser/contextual_tasks/contextual_tasks_ui_interface.h index 3c78fd4..0d86220 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui_interface.h +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_interface.h
@@ -19,6 +19,7 @@ namespace contextual_search { class ContextualSearchSessionHandle; +class InputStateModel; } // namespace contextual_search namespace lens { @@ -94,6 +95,12 @@ // Returns the Mojo remote used to communicate with the WebUI page. virtual mojo::Remote<contextual_tasks::mojom::Page>& GetPageRemote() = 0; + // Fetches and assumes unique ownership of the pre-configured input state + // model attached to the WebContents for the current task. Subsequent calls + // for the same task will return nullptr. + virtual std::unique_ptr<contextual_search::InputStateModel> + TakeInputStateModel() = 0; + // Helpers. // Returns the URL of the page currently embedded in the WebUI's <webview>.
diff --git a/chrome/browser/default_browser/BUILD.gn b/chrome/browser/default_browser/BUILD.gn index 830acbf..a8fb04b5 100644 --- a/chrome/browser/default_browser/BUILD.gn +++ b/chrome/browser/default_browser/BUILD.gn
@@ -52,7 +52,10 @@ if (is_win) { sources += [ "default_browser_monitor_win.cc" ] - deps += [ "//chrome/browser/win:registry_watcher" ] + deps += [ + "//chrome/browser/win:registry_watcher", + "//chrome/install_static:install_static_util", + ] } else { sources += [ "default_browser_monitor_stub.cc" ] }
diff --git a/chrome/browser/default_browser/default_browser_notification_observer.cc b/chrome/browser/default_browser/default_browser_notification_observer.cc index 4b05e2f..ccf273d 100644 --- a/chrome/browser/default_browser/default_browser_notification_observer.cc +++ b/chrome/browser/default_browser/default_browser_notification_observer.cc
@@ -27,6 +27,10 @@ #include "ui/message_center/public/cpp/notification_types.h" #include "ui/message_center/public/cpp/notifier_id.h" +#if BUILDFLAG(IS_WIN) +#include "chrome/install_static/install_util.h" +#endif // BUILDFLAG(IS_WIN) + namespace default_browser { DefaultBrowserNotificationObserver::DefaultBrowserNotificationObserver( @@ -34,6 +38,13 @@ InitialStateCheckCallback initial_state_check_callback, DefaultBrowserManager& manager) : manager_(manager) { +#if BUILDFLAG(IS_WIN) + // On Windows, some install modes don't support being set as default. + if (!install_static::SupportsSetAsDefaultBrowser()) { + return; + } +#endif // BUILDFLAG(IS_WIN) + default_browser_change_subscription_ = std::move(register_callback) .Run(base::BindRepeating(
diff --git a/chrome/browser/desktop_to_mobile_promos/promos_utils.cc b/chrome/browser/desktop_to_mobile_promos/promos_utils.cc index ba9fd8ea..8cff2ba 100644 --- a/chrome/browser/desktop_to_mobile_promos/promos_utils.cc +++ b/chrome/browser/desktop_to_mobile_promos/promos_utils.cc
@@ -435,6 +435,12 @@ } } +bool IsIOSDesktopPromoAllowedByGlobalImpressions(Profile* profile) { + return VerifyMostRecentPromoTimestamp(profile) && + VerifyIOSDesktopPromoTotalImpressions(profile) && + VerifyIOSDesktopPromoTotalOptOuts(profile); +} + bool ShouldShowIOSDesktopPromo(Profile* profile, const syncer::SyncService* sync_service, PromoType promo_type) { @@ -450,10 +456,9 @@ profile->GetPrefs()->GetInteger( promo_prefs.promo_impressions_counter_pref_name) < kiOSDesktopPromoMaxImpressionCount && - VerifyMostRecentPromoTimestamp(profile) && - VerifyIOSDesktopPromoTotalImpressions(profile) && - VerifyIOSDesktopPromoTotalOptOuts(profile) && - !profile->GetPrefs()->GetBoolean(promo_prefs.promo_opt_out_pref_name); + !profile->GetPrefs()->GetBoolean( + promo_prefs.promo_opt_out_pref_name) && + IsIOSDesktopPromoAllowedByGlobalImpressions(profile); } bool UserNotClassifiedAsMobileDeviceSwitcher(
diff --git a/chrome/browser/desktop_to_mobile_promos/promos_utils.h b/chrome/browser/desktop_to_mobile_promos/promos_utils.h index b8c4218..7d0b819 100644 --- a/chrome/browser/desktop_to_mobile_promos/promos_utils.h +++ b/chrome/browser/desktop_to_mobile_promos/promos_utils.h
@@ -83,6 +83,11 @@ int impression_count, DesktopIOSPromoAction action); +// Checks if the user is eligible for the promo based on the total number of +// impressions, the cooldown period, and the opt-out status across all promo +// types. +bool IsIOSDesktopPromoAllowedByGlobalImpressions(Profile* profile); + // ShouldShowIOSDesktopPromo checks if the user should be shown the iOS desktop // promo (all criteria are met) depending on the given promo type, and returns // true if so.
diff --git a/chrome/browser/devtools/inspector_protocol_config.json b/chrome/browser/devtools/inspector_protocol_config.json index ba645de..33e9f711 100644 --- a/chrome/browser/devtools/inspector_protocol_config.json +++ b/chrome/browser/devtools/inspector_protocol_config.json
@@ -28,7 +28,7 @@ }, { "domain": "Emulation", - "include": ["disable", "setAutomationOverride", "getScreenInfos", "addScreen", "removeScreen", "setPrimaryScreen"] + "include": ["disable", "setAutomationOverride", "getScreenInfos", "addScreen", "removeScreen", "updateScreen", "setPrimaryScreen"] }, { "domain": "WindowManager"
diff --git a/chrome/browser/devtools/protocol/emulation_handler.cc b/chrome/browser/devtools/protocol/emulation_handler.cc index 7705f44..c0bdeb50 100644 --- a/chrome/browser/devtools/protocol/emulation_handler.cc +++ b/chrome/browser/devtools/protocol/emulation_handler.cc
@@ -11,6 +11,7 @@ #include "base/check_deref.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" +#include "build/build_config.h" #include "chrome/browser/infobars/confirm_infobar_creator.h" #include "chrome/browser/ui/startup/automation_infobar_delegate.h" #include "ui/display/display.h" @@ -83,6 +84,17 @@ display::ScreenInfo screen_info; display::DisplayUtil::DisplayToScreenInfo(&screen_info, display); + // The display::Display rotation is the physical display rotation, while the + // display::ScreenInfo orientation is the rotation required by the content to + // be shown properly on the screen, see DisplayUtil::DisplayToScreenInfo(). +#if defined(USE_AURA) + if (screen_info.orientation_angle == 90) { + screen_info.orientation_angle = 270; + } else if (screen_info.orientation_angle == 270) { + screen_info.orientation_angle = 90; + } +#endif + return protocol::Emulation::ScreenInfo::Create() .SetLeft(screen_info.rect.x()) .SetTop(screen_info.rect.y()) @@ -222,6 +234,90 @@ return Response::Success(); } +Response EmulationHandler::UpdateScreen( + const protocol::String& screen_id, + std::optional<int> left, + std::optional<int> top, + std::optional<int> width, + std::optional<int> height, + std::unique_ptr<protocol::Emulation::WorkAreaInsets> work_area_insets, + std::optional<double> device_pixel_ratio, + std::optional<int> rotation, + std::optional<int> color_depth, + std::optional<protocol::String> label, + std::optional<bool> is_internal, + std::unique_ptr<protocol::Emulation::ScreenInfo>* out_screen_info) { + if (!display::Screen::Get()->IsHeadless()) { + return Response::ServerError("Method is only available in headless mode"); + } + + int64_t display_id; + if (!base::StringToInt64(screen_id, &display_id)) { + return Response::InvalidParams("Invalid screen id: " + screen_id); + } + + std::optional<display::Display> display = GetDisplay(display_id); + if (!display) { + return Response::InvalidParams("Unknown screen id: " + screen_id); + } + + if (IsPrimaryDisplay(display_id) && + (left.value_or(0) != 0 || top.value_or(0) != 0)) { + return Response::InvalidParams("Primary screen origin must be at 0,0"); + } + + std::optional<int> top_work_area_inset; + std::optional<int> left_work_area_inset; + std::optional<int> bottom_work_area_inset; + std::optional<int> right_work_area_inset; + if (work_area_insets) { + top_work_area_inset = work_area_insets->GetTop(); + left_work_area_inset = work_area_insets->GetLeft(); + bottom_work_area_inset = work_area_insets->GetBottom(); + right_work_area_inset = work_area_insets->GetRight(); + } + + if (device_pixel_ratio) { + // Apply the same constrains as in Display::SetScale(). + if (*device_pixel_ratio < 0.5f) { + return Response::InvalidParams( + "Invalid device pixel ratio, must be >= 0.5"); + } +#if BUILDFLAG(IS_MAC) + if (*device_pixel_ratio != static_cast<int>(*device_pixel_ratio)) { + return Response::InvalidParams( + "Invalid device pixel ratio, must be integral"); + } +#endif + } + + if (rotation) { + if (!display::Display::IsValidRotation(*rotation)) { + return Response::InvalidParams("Invalid screen rotation: " + + base::NumberToString(*rotation)); + } + } + + headless::UpdateDisplay( + *display, left, top, width, height, top_work_area_inset, + left_work_area_inset, bottom_work_area_inset, right_work_area_inset, + device_pixel_ratio, rotation, color_depth, label, is_internal); + + display::HeadlessScreenManager::Get()->UpdateDisplay(*display); + + auto updated_display = GetDisplay(display_id); + if (!updated_display) { + return Response::InvalidParams("Failed to update screen id: " + + base::NumberToString(display_id)); + } + + CHECK_EQ(updated_display->id(), display_id); + + *out_screen_info = CreateScreenInfo(*updated_display); + + return Response::Success(); +} + Response EmulationHandler::RemoveScreen(const protocol::String& screen_id) { if (!display::Screen::Get()->IsHeadless()) { return Response::ServerError("Method is only available in headless mode");
diff --git a/chrome/browser/devtools/protocol/emulation_handler.h b/chrome/browser/devtools/protocol/emulation_handler.h index 6b0e74a..6ce565b 100644 --- a/chrome/browser/devtools/protocol/emulation_handler.h +++ b/chrome/browser/devtools/protocol/emulation_handler.h
@@ -40,6 +40,20 @@ std::optional<bool> is_internal, std::unique_ptr<protocol::Emulation::ScreenInfo>* out_screen_info) override; + protocol::Response UpdateScreen( + const protocol::String& screen_id, + std::optional<int> left, + std::optional<int> top, + std::optional<int> width, + std::optional<int> height, + std::unique_ptr<protocol::Emulation::WorkAreaInsets> work_area_insets, + std::optional<double> device_pixel_ratio, + std::optional<int> rotation, + std::optional<int> color_depth, + std::optional<protocol::String> label, + std::optional<bool> is_internal, + std::unique_ptr<protocol::Emulation::ScreenInfo>* out_screen_info) + override; protocol::Response RemoveScreen(const protocol::String& screen_id) override; protocol::Response SetPrimaryScreen( const protocol::String& screen_id) override;
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc index 3513935..20bb8bd5 100644 --- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc +++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
@@ -14,10 +14,11 @@ #include "chrome/browser/enterprise/connectors/connectors_service.h" #include "chrome/browser/enterprise/data_controls/chrome_rules_service.h" #include "chrome/browser/enterprise/data_protection/data_protection_features.h" -#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h" +#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h" #include "chrome/browser/interstitials/enterprise_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/webui_url_constants.h" +#include "components/enterprise/data_protection/data_protection_url_lookup_service.h" #include "components/safe_browsing/buildflags.h" #include "components/sessions/content/session_tab_helper.h" #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.cc b/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.cc new file mode 100644 index 0000000..40339988 --- /dev/null +++ b/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.cc
@@ -0,0 +1,41 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h" + +#include "chrome/browser/profiles/profile.h" +#include "components/enterprise/data_protection/data_protection_url_lookup_service.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" + +namespace enterprise_data_protection { + +// static +DataProtectionUrlLookupServiceFactory* +DataProtectionUrlLookupServiceFactory::GetInstance() { + static base::NoDestructor<DataProtectionUrlLookupServiceFactory> instance; + return instance.get(); +} + +// static +DataProtectionUrlLookupService* +DataProtectionUrlLookupServiceFactory::GetForBrowserContext( + content::BrowserContext* context) { + return static_cast<DataProtectionUrlLookupService*>( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +DataProtectionUrlLookupServiceFactory::DataProtectionUrlLookupServiceFactory() + : ProfileKeyedServiceFactory("DataProtectionUrlLookupService", + ProfileSelections::BuildForRegularProfile()) {} + +DataProtectionUrlLookupServiceFactory:: + ~DataProtectionUrlLookupServiceFactory() = default; + +std::unique_ptr<KeyedService> +DataProtectionUrlLookupServiceFactory::BuildServiceInstanceForBrowserContext( + content::BrowserContext* context) const { + return std::make_unique<DataProtectionUrlLookupService>(); +} + +} // namespace enterprise_data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h b/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h new file mode 100644 index 0000000..3327cd5a --- /dev/null +++ b/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h
@@ -0,0 +1,38 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_FACTORY_H_ +#define CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_FACTORY_H_ + +#include "base/no_destructor.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" + +namespace content { +class BrowserContext; +} + +namespace enterprise_data_protection { + +class DataProtectionUrlLookupService; + +class DataProtectionUrlLookupServiceFactory + : public ProfileKeyedServiceFactory { + public: + static DataProtectionUrlLookupServiceFactory* GetInstance(); + static DataProtectionUrlLookupService* GetForBrowserContext( + content::BrowserContext* context); + + private: + DataProtectionUrlLookupServiceFactory(); + ~DataProtectionUrlLookupServiceFactory() override; + friend base::NoDestructor<DataProtectionUrlLookupServiceFactory>; + + // BrowserContextKeyedServiceFactory: + std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext( + content::BrowserContext* context) const override; +}; + +} // namespace enterprise_data_protection + +#endif // CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_FACTORY_H_
diff --git a/chrome/browser/enterprise/util/managed_browser_utils.cc b/chrome/browser/enterprise/util/managed_browser_utils.cc index b499eb9..84c9661 100644 --- a/chrome/browser/enterprise/util/managed_browser_utils.cc +++ b/chrome/browser/enterprise/util/managed_browser_utils.cc
@@ -449,30 +449,11 @@ return BrowserManagementNoticeState::kEnabledByPolicy; } - size_t policies_count = g_browser_process->browser_policy_connector() - ->GetPolicyService() - ->GetPolicies(policy::PolicyNamespace( - policy::POLICY_DOMAIN_CHROME, std::string())) - .size(); const bool is_low_trust = management_service->GetManagementAuthorityTrustworthiness() <= policy::ManagementAuthorityTrustworthiness::LOW; - const bool show_for_high_trust = - !is_low_trust && - base::FeatureList::IsEnabled(features::kEnterpriseBadgingForNtpFooter); - const bool show_for_local_management = - is_low_trust && - base::FeatureList::IsEnabled( - features::kEnterpriseBadgingForLocalManagemenetNtpFooter); - const bool show_for_three_or_more_policies_local_management = - is_low_trust && - base::FeatureList::IsEnabled( - features::kEnterpriseBadgingForNtpFooterWithOverThreePolicies) && - policies_count > 3; - - if (show_for_high_trust || show_for_local_management || - show_for_three_or_more_policies_local_management) { + if (!is_low_trust) { return profile->GetPrefs()->GetBoolean(prefs::kNtpFooterVisible) ? BrowserManagementNoticeState::kEnabled : BrowserManagementNoticeState::kDisabled;
diff --git a/chrome/browser/enterprise/util/managed_browser_utils_browsertest.cc b/chrome/browser/enterprise/util/managed_browser_utils_browsertest.cc index 3ca33f0c..9361f05 100644 --- a/chrome/browser/enterprise/util/managed_browser_utils_browsertest.cc +++ b/chrome/browser/enterprise/util/managed_browser_utils_browsertest.cc
@@ -162,16 +162,11 @@ class EnterpriseBrowserBadgingTest : public InProcessBrowserTest, - public testing::WithParamInterface<std::tuple<bool, bool, bool>> { + public testing::WithParamInterface<std::tuple<bool, bool>> { public: void SetUp() override { std::vector<base::test::FeatureRef> enabled_features; std::vector<base::test::FeatureRef> disabled_features; - if (footer_management_notice_feature_enabled()) { - enabled_features.emplace_back(features::kEnterpriseBadgingForNtpFooter); - } else { - disabled_features.emplace_back(features::kEnterpriseBadgingForNtpFooter); - } if (policies_feature_enabled()) { enabled_features.emplace_back(features::kNTPFooterBadgingPolicies); } else { @@ -200,11 +195,8 @@ void TearDownOnMainThread() override { scoped_browser_management_.reset(); } - bool footer_management_notice_feature_enabled() { - return std::get<0>(GetParam()); - } - bool policies_feature_enabled() { return std::get<1>(GetParam()); } - bool managed_browser() { return std::get<2>(GetParam()); } + bool policies_feature_enabled() { return std::get<0>(GetParam()); } + bool managed_browser() { return std::get<1>(GetParam()); } private: std::unique_ptr<policy::ScopedManagementServiceOverrideForTesting> @@ -218,24 +210,17 @@ // When no custom policy is set, the visibility of the management notice in // the NTP footer depends on whether the browser is managed and // if the feature controlling the default behaviour is enabled. - EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), - footer_management_notice_feature_enabled() && managed_browser()); + EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), managed_browser()); g_browser_process->local_state()->SetString( prefs::kEnterpriseCustomLabelForBrowser, "some_label"); - EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), - (footer_management_notice_feature_enabled() || - policies_feature_enabled()) && - managed_browser()); + EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), managed_browser()); g_browser_process->local_state()->SetString( prefs::kEnterpriseCustomLabelForBrowser, ""); g_browser_process->local_state()->SetString( prefs::kEnterpriseLogoUrlForBrowser, "some_url"); - EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), - ((footer_management_notice_feature_enabled() || - policies_feature_enabled()) && - managed_browser())); + EXPECT_EQ(CanShowEnterpriseBadgingForNTPFooter(profile), managed_browser()); } IN_PROC_BROWSER_TEST_P(EnterpriseBrowserBadgingTest, @@ -250,9 +235,7 @@ // Default state: no policy or user setting is specified yet. BrowserManagementNoticeState expected_state; - expected_state = footer_management_notice_feature_enabled() - ? BrowserManagementNoticeState::kEnabled - : BrowserManagementNoticeState::kNotApplicable; + expected_state = BrowserManagementNoticeState::kEnabled; EXPECT_EQ(GetManagementNoticeStateForNTPFooter(profile), expected_state); // Notice is disabled by policy. @@ -265,9 +248,7 @@ // Footer is disabled by user pref. profile->GetPrefs()->SetBoolean(prefs::kNtpFooterVisible, false); - expected_state = footer_management_notice_feature_enabled() - ? BrowserManagementNoticeState::kDisabled - : BrowserManagementNoticeState::kNotApplicable; + expected_state = BrowserManagementNoticeState::kDisabled; EXPECT_EQ(GetManagementNoticeStateForNTPFooter(profile), expected_state); profile->GetPrefs()->SetBoolean(prefs::kNtpFooterVisible, true); @@ -277,9 +258,7 @@ if (policies_feature_enabled()) { expected_state = BrowserManagementNoticeState::kEnabledByPolicy; } else { - expected_state = footer_management_notice_feature_enabled() - ? BrowserManagementNoticeState::kEnabled - : BrowserManagementNoticeState::kNotApplicable; + expected_state = BrowserManagementNoticeState::kEnabled; } EXPECT_EQ(GetManagementNoticeStateForNTPFooter(profile), expected_state); @@ -288,9 +267,7 @@ if (policies_feature_enabled()) { expected_state = BrowserManagementNoticeState::kEnabledByPolicy; } else { - expected_state = footer_management_notice_feature_enabled() - ? BrowserManagementNoticeState::kDisabled - : BrowserManagementNoticeState::kNotApplicable; + expected_state = BrowserManagementNoticeState::kDisabled; } EXPECT_EQ(GetManagementNoticeStateForNTPFooter(profile), expected_state); profile->GetPrefs()->SetBoolean(prefs::kNtpFooterVisible, true); @@ -303,18 +280,14 @@ if (policies_feature_enabled()) { expected_state = BrowserManagementNoticeState::kEnabledByPolicy; } else { - expected_state = footer_management_notice_feature_enabled() - ? BrowserManagementNoticeState::kEnabled - : BrowserManagementNoticeState::kNotApplicable; + expected_state = BrowserManagementNoticeState::kEnabled; } EXPECT_EQ(GetManagementNoticeStateForNTPFooter(profile), expected_state); } INSTANTIATE_TEST_SUITE_P(, EnterpriseBrowserBadgingTest, - testing::Combine(testing::Bool(), - testing::Bool(), - testing::Bool())); + testing::Combine(testing::Bool(), testing::Bool())); #endif // !BUILDFLAG(IS_CHROMEOS) using ManagedBrowserUtilsDeviceSignalsBrowserTest = InProcessBrowserTest;
diff --git a/chrome/browser/extensions/api/image_writer_private/operation.h b/chrome/browser/extensions/api/image_writer_private/operation.h index 30125c73..74fcdf4 100644 --- a/chrome/browser/extensions/api/image_writer_private/operation.h +++ b/chrome/browser/extensions/api/image_writer_private/operation.h
@@ -212,7 +212,16 @@ // Callbacks for Extractor. void OnExtractOpenComplete(const base::FilePath& image_path); + + // Note: `total_bytes` and `progress_bytes` are signed `int64_t `because + // `ZipExtractor` and `zip::ZipReader` deal in `int64_t` size values. + // TODO(crbug.com/489798713): Consider refactoring ZipReader to use unsigned + // values if possible. + // Extractors that provide `uint64_t` values (like `TarExtractor`) must + // validate they do not exceed `INT64_MAX` before passing them here to prevent + // overflow. void OnExtractProgress(int64_t total_bytes, int64_t progress_bytes); + void OnExtractFailure(const std::string& error); // Runs all cleanup functions.
diff --git a/chrome/browser/extensions/api/image_writer_private/tar_extractor.cc b/chrome/browser/extensions/api/image_writer_private/tar_extractor.cc index 58310404..ed55089 100644 --- a/chrome/browser/extensions/api/image_writer_private/tar_extractor.cc +++ b/chrome/browser/extensions/api/image_writer_private/tar_extractor.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/extensions/api/image_writer_private/tar_extractor.h" #include <array> +#include <limits> #include <utility> #include "base/files/file.h" @@ -72,6 +73,11 @@ if (total_bytes == 0) { return; } + if (total_bytes > std::numeric_limits<int64_t>::max() || + progress_bytes > total_bytes) { + listener_.ReportBadMessage("invalid extraction progress values"); + return; + } properties_.progress_callback.Run(total_bytes, progress_bytes); }
diff --git a/chrome/browser/extensions/api/image_writer_private/tar_extractor_unittest.cc b/chrome/browser/extensions/api/image_writer_private/tar_extractor_unittest.cc index e3b14d81..c8f486b 100644 --- a/chrome/browser/extensions/api/image_writer_private/tar_extractor_unittest.cc +++ b/chrome/browser/extensions/api/image_writer_private/tar_extractor_unittest.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/image_writer_private/tar_extractor.h" +#include <limits> #include <utility> #include "base/files/scoped_temp_dir.h" @@ -12,6 +13,8 @@ #include "chrome/browser/extensions/api/image_writer_private/error_constants.h" #include "chrome/browser/extensions/api/image_writer_private/extraction_properties.h" #include "content/public/test/browser_task_environment.h" +#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h" +#include "mojo/public/cpp/test_support/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions::image_writer { @@ -41,6 +44,7 @@ const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); } private: + content::BrowserTaskEnvironment task_environment_; base::ScopedTempDir temp_dir_; }; @@ -68,7 +72,88 @@ TarExtractor* extractor = CreateExtractor(std::move(properties)); // Call OnProgress with 0 total bytes. - RunOnProgress(extractor, 0, 0); + RunOnProgress(extractor, /*total=*/0, /*progress=*/0); + + // Clean up extractor to satisfy leak checks. + DeleteExtractor(extractor); +} + +// Tests that the TarExtractor fails gracefully and reports a bad message if it +// receives a progress update with total bytes exceeding INT64_MAX. This guards +// against a potential underflow when the value is implicitly cast to int64_t +// in Operation::OnExtractProgress. +TEST_F(TarExtractorTest, ExceedsInt64MaxTotalBytes) { + ExtractionProperties properties; + properties.temp_dir_path = temp_dir(); + + base::MockCallback<ExtractionProperties::FailureCallback> failure_callback; + properties.failure_callback = failure_callback.Get(); + + base::MockCallback<ExtractionProperties::ProgressCallback> progress_callback; + properties.progress_callback = progress_callback.Get(); + + // We expect no failure callback since the bad message will terminate the + // utility process, and the mojo disconnect handler will handle cleanup. + EXPECT_CALL(failure_callback, Run(::testing::_)).Times(0); + + // We expect no progress callback because total_bytes exceeds INT64_MAX. + EXPECT_CALL(progress_callback, Run(::testing::_, ::testing::_)).Times(0); + + TarExtractor* extractor = CreateExtractor(std::move(properties)); + + // Simulate a mojo message dispatch context so that ReportBadMessage can hook + // into it without crashing the test. + mojo::FakeMessageDispatchContext fake_dispatch_context; + mojo::test::BadMessageObserver bad_message_observer; + + // Call OnProgress with total bytes exceeding INT64_MAX. + RunOnProgress( + extractor, + /*total=*/static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1, + /*progress=*/0); + + // Verify that ReportBadMessage was called with the expected error string. + EXPECT_EQ("invalid extraction progress values", + bad_message_observer.WaitForBadMessage()); + + // Clean up extractor to satisfy leak checks. + DeleteExtractor(extractor); +} + +// Tests that the TarExtractor fails gracefully and reports a bad message if it +// receives a progress update with progress_bytes exceeding total_bytes. +// This guards against a compromised utility process sending spoofed progress +// values. +TEST_F(TarExtractorTest, InvalidProgressBytes) { + ExtractionProperties properties; + properties.temp_dir_path = temp_dir(); + + base::MockCallback<ExtractionProperties::FailureCallback> failure_callback; + properties.failure_callback = failure_callback.Get(); + + base::MockCallback<ExtractionProperties::ProgressCallback> progress_callback; + properties.progress_callback = progress_callback.Get(); + + // We expect no failure callback since the bad message will terminate the + // utility process. + EXPECT_CALL(failure_callback, Run(::testing::_)).Times(0); + + // We expect no progress callback because progress_bytes exceeds total_bytes. + EXPECT_CALL(progress_callback, Run(::testing::_, ::testing::_)).Times(0); + + // Create extractor. + TarExtractor* extractor = CreateExtractor(std::move(properties)); + + // Simulate a mojo message dispatch context. + mojo::FakeMessageDispatchContext fake_dispatch_context; + mojo::test::BadMessageObserver bad_message_observer; + + // Call OnProgress with progress_bytes exceeding total_bytes. + RunOnProgress(extractor, /*total=*/100, /*progress=*/101); + + // Verify that ReportBadMessage was called with the expected error string. + EXPECT_EQ("invalid extraction progress values", + bad_message_observer.WaitForBadMessage()); // Clean up extractor to satisfy leak checks. DeleteExtractor(extractor);
diff --git a/chrome/browser/extensions/api/image_writer_private/xz_extractor.cc b/chrome/browser/extensions/api/image_writer_private/xz_extractor.cc index d0d0a712..b900c0f 100644 --- a/chrome/browser/extensions/api/image_writer_private/xz_extractor.cc +++ b/chrome/browser/extensions/api/image_writer_private/xz_extractor.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/image_writer_private/xz_extractor.h" +#include <limits> #include <utility> #include "base/files/file.h" @@ -65,6 +66,11 @@ if (total_bytes == 0) { return; } + if (total_bytes > std::numeric_limits<int64_t>::max() || + progress_bytes > total_bytes) { + listener_.ReportBadMessage("invalid extraction progress values"); + return; + } properties_.progress_callback.Run(total_bytes, progress_bytes); }
diff --git a/chrome/browser/extensions/api/image_writer_private/xz_extractor_unittest.cc b/chrome/browser/extensions/api/image_writer_private/xz_extractor_unittest.cc index 87e69b96..70ad60fd 100644 --- a/chrome/browser/extensions/api/image_writer_private/xz_extractor_unittest.cc +++ b/chrome/browser/extensions/api/image_writer_private/xz_extractor_unittest.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/extensions/api/image_writer_private/xz_extractor.h" +#include <limits> #include <utility> #include "base/files/scoped_temp_dir.h" @@ -12,6 +13,8 @@ #include "chrome/browser/extensions/api/image_writer_private/error_constants.h" #include "chrome/browser/extensions/api/image_writer_private/extraction_properties.h" #include "content/public/test/browser_task_environment.h" +#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h" +#include "mojo/public/cpp/test_support/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions::image_writer { @@ -41,6 +44,7 @@ const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); } private: + content::BrowserTaskEnvironment task_environment_; base::ScopedTempDir temp_dir_; }; @@ -68,7 +72,88 @@ XzExtractor* extractor = CreateExtractor(std::move(properties)); // Call OnProgress with 0 total bytes. - RunOnProgress(extractor, 0, 0); + RunOnProgress(extractor, /*total=*/0, /*progress=*/0); + + // Clean up extractor to satisfy leak checks. + DeleteExtractor(extractor); +} + +// Tests that the XzExtractor fails gracefully and reports a bad message if it +// receives a progress update with total bytes exceeding INT64_MAX. This guards +// against a potential underflow when the value is implicitly cast to int64_t +// in Operation::OnExtractProgress. +TEST_F(XzExtractorTest, ExceedsInt64MaxTotalBytes) { + ExtractionProperties properties; + properties.temp_dir_path = temp_dir(); + + base::MockCallback<ExtractionProperties::FailureCallback> failure_callback; + properties.failure_callback = failure_callback.Get(); + + base::MockCallback<ExtractionProperties::ProgressCallback> progress_callback; + properties.progress_callback = progress_callback.Get(); + + // We expect no failure callback since the bad message will terminate the + // utility process, and the mojo disconnect handler will handle cleanup. + EXPECT_CALL(failure_callback, Run(::testing::_)).Times(0); + + // We expect no progress callback because total_bytes exceeds INT64_MAX. + EXPECT_CALL(progress_callback, Run(::testing::_, ::testing::_)).Times(0); + + XzExtractor* extractor = CreateExtractor(std::move(properties)); + + // Simulate a mojo message dispatch context so that ReportBadMessage can hook + // into it without crashing the test. + mojo::FakeMessageDispatchContext fake_dispatch_context; + mojo::test::BadMessageObserver bad_message_observer; + + // Call OnProgress with total bytes exceeding INT64_MAX. + RunOnProgress( + extractor, + /*total=*/static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1, + /*progress=*/0); + + // Verify that ReportBadMessage was called with the expected error string. + EXPECT_EQ("invalid extraction progress values", + bad_message_observer.WaitForBadMessage()); + + // Clean up extractor to satisfy leak checks. + DeleteExtractor(extractor); +} + +// Tests that the XzExtractor fails gracefully and reports a bad message if it +// receives a progress update with progress_bytes exceeding total_bytes. +// This guards against a compromised utility process sending spoofed progress +// values. +TEST_F(XzExtractorTest, InvalidProgressBytes) { + ExtractionProperties properties; + properties.temp_dir_path = temp_dir(); + + base::MockCallback<ExtractionProperties::FailureCallback> failure_callback; + properties.failure_callback = failure_callback.Get(); + + base::MockCallback<ExtractionProperties::ProgressCallback> progress_callback; + properties.progress_callback = progress_callback.Get(); + + // We expect no failure callback since the bad message will terminate the + // utility process. + EXPECT_CALL(failure_callback, Run(::testing::_)).Times(0); + + // We expect no progress callback because progress_bytes exceeds total_bytes. + EXPECT_CALL(progress_callback, Run(::testing::_, ::testing::_)).Times(0); + + // Create extractor. + XzExtractor* extractor = CreateExtractor(std::move(properties)); + + // Simulate a mojo message dispatch context. + mojo::FakeMessageDispatchContext fake_dispatch_context; + mojo::test::BadMessageObserver bad_message_observer; + + // Call OnProgress with progress_bytes exceeding total_bytes. + RunOnProgress(extractor, /*total=*/100, /*progress=*/101); + + // Verify that ReportBadMessage was called with the expected error string. + EXPECT_EQ("invalid extraction progress values", + bad_message_observer.WaitForBadMessage()); // Clean up extractor to satisfy leak checks. DeleteExtractor(extractor);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 481ab6d..d416515 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -310,6 +310,11 @@ "expiry_milestone": 137 }, { + "name": "android-history-clustering", + "owners": [ "wylieb@google.com", "clank-tab-dev@google.com" ], + "expiry_milestone": 160 + }, + { "name": "android-hub-search-tab-groups", "owners": [ "wylieb@google.com", "bjfong@google.com", "clank-tab-dev@google.com" ], "expiry_milestone": 150 @@ -3221,11 +3226,6 @@ "expiry_milestone": 150 }, { - "name": "enable-enterprise-badging-for-ntp-footer", - "owners": ["esalma@google.com","cbe-magic@google.com"], - "expiry_milestone": 141 - }, - { "name": "enable-eol-notification-reset-dismissed-prefs", "owners": [ "mmourgos@chromium.org", "tbarzic@chromium.org"], "expiry_milestone": 116 @@ -5064,6 +5064,11 @@ "expiry_milestone": 155 }, { + "name": "gemini-binary-migration", + "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ], + "expiry_milestone": 160 + }, + { "name": "gemini-chat-persistence", "owners": [ "joemerramos@google.com", @@ -5126,6 +5131,11 @@ "expiry_milestone": 148 }, { + "name": "gemini-maps-rich-ui", + "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ], + "expiry_milestone": 160 + }, + { "name": "gemini-navigation-promo", "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ], "expiry_milestone": 148 @@ -5162,6 +5172,11 @@ "expiry_milestone": 155 }, { + "name": "gemini-unary-migration", + "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ], + "expiry_milestone": 160 + }, + { "name": "gemini-updated-eligibility", "owners": [ "fbeauchamp@google.com",
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 304c963..f66eb3a 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -210,6 +210,11 @@ "If enabled, allows Chrome to integrate with the Android App Search with " "multiple data sources, e.g. custom Tabs."; +inline constexpr char kAndroidHistoryClusteringName[] = + "Android History Clustering"; +inline constexpr char kAndroidHistoryClusteringDescription[] = + "Enables history clustering UI on Android."; + inline constexpr char kAndroidBottomToolbarV2Name[] = "Bottom Toolbar V2"; inline constexpr char kAndroidBottomToolbarV2Description[] = "If enabled, allows the Omnibox to be persistently anchored to the bottom " @@ -1546,12 +1551,6 @@ "When adding automatic captions to images, use a different route to " "acquire descriptions."; -inline constexpr char kEnterpriseBadgingForNtpFooterName[] = - "Enable enterprise badging on the New Tab Page"; -inline constexpr char kEnterpriseBadgingForNtpFooterDescription[] = - "Enable enterprise profile badging in the footer on the New Tab Page. This " - "includes showing the enterprise logo and the management disclaimer"; - inline constexpr char kEnableClientCertificateProvisioningOnAndroidName[] = "Enable client certificate provisioning on Android"; inline constexpr char
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc index d6bdd5b..f8d9d53e 100644 --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -227,6 +227,7 @@ &kAndroidDesktopDensity, &kAndroidElegantTextHeight, &kAndroidFirstRunLaunchBounds, + &kAndroidHistoryClustering, &kAndroidNewMediaPicker, &kAndroidNoVisibleHintForDifferentTLD, &kAndroidOmniboxFocusedNewTabPage, @@ -557,6 +558,7 @@ BASE_FEATURE(kAndroidDesktopDensity, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kAndroidElegantTextHeight, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kAndroidFirstRunLaunchBounds, base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kAndroidHistoryClustering, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kAndroidNewMediaPicker, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kAndroidNoVisibleHintForDifferentTLD, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kAndroidOmniboxFocusedNewTabPage, base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h index ae7cb24..01fc0f8c 100644 --- a/chrome/browser/flags/android/chrome_feature_list.h +++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -44,6 +44,7 @@ BASE_DECLARE_FEATURE(kAndroidDesktopDensity); BASE_DECLARE_FEATURE(kAndroidElegantTextHeight); BASE_DECLARE_FEATURE(kAndroidFirstRunLaunchBounds); +BASE_DECLARE_FEATURE(kAndroidHistoryClustering); BASE_DECLARE_FEATURE(kAndroidNewMediaPicker); BASE_DECLARE_FEATURE(kAndroidNoVisibleHintForDifferentTLD); BASE_DECLARE_FEATURE(kAndroidOmniboxFocusedNewTabPage);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java index 6a02f99..269ecf5 100644 --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -190,6 +190,7 @@ public static final String ANDROID_DESKTOP_DENSITY = "AndroidDesktopDensity"; public static final String ANDROID_ELEGANT_TEXT_HEIGHT = "AndroidElegantTextHeight"; public static final String ANDROID_FIRST_RUN_LAUNCH_BOUNDS = "AndroidFirstRunLaunchBounds"; + public static final String ANDROID_HISTORY_CLUSTERING = "AndroidHistoryClustering"; public static final String ANDROID_NEW_MEDIA_PICKER = "AndroidNewMediaPicker"; public static final String ANDROID_NO_VISIBLE_HINT_FOR_DIFFERENT_TLD = "AndroidNoVisibleHintForDifferentTLD"; @@ -746,9 +747,7 @@ /* defaultValueInTests= */ true); public static final CachedFlag sAndroidSetupList = newCachedFlag( - ANDROID_SETUP_LIST, - /* defaultValue= */ false, - /* defaultValueInTests= */ false); + ANDROID_SETUP_LIST, /* defaultValue= */ false, /* defaultValueInTests= */ true); public static final CachedFlag sAndroidSurfaceColorUpdate = newCachedFlag( ANDROID_SURFACE_COLOR_UPDATE, @@ -1876,6 +1875,8 @@ public static final MutableBooleanParamWithSafeDefault sTabBottomSheetDontShowFusebox = sTabBottomSheet.newBooleanParam("dont_show_fusebox", false); + public static final MutableBooleanParamWithSafeDefault sTabBottomSheetResizeWebview = + sTabBottomSheet.newBooleanParam("resize_webview", false); public static final MutableBooleanParamWithSafeDefault sRobustWindowManagementBulkClose = sRobustWindowManagement.newBooleanParam("bulk_close", false);
diff --git a/chrome/browser/glic/android/glic_keyed_service_factory_android.cc b/chrome/browser/glic/android/glic_keyed_service_factory_android.cc index 724a1b3..a33c4fa 100644 --- a/chrome/browser/glic/android/glic_keyed_service_factory_android.cc +++ b/chrome/browser/glic/android/glic_keyed_service_factory_android.cc
@@ -18,6 +18,10 @@ DCHECK(profile); GlicKeyedService* service = GlicKeyedServiceFactory::GetGlicKeyedService(profile); + if (!service) { + return nullptr; + } + return GlicKeyedService::GetJavaObject(service); }
diff --git a/chrome/browser/glic/glic_enums.h b/chrome/browser/glic/glic_enums.h index e3557be..e6c1e60 100644 --- a/chrome/browser/glic/glic_enums.h +++ b/chrome/browser/glic/glic_enums.h
@@ -41,6 +41,18 @@ }; // LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicFilteringResult) +// LINT.IfChange(GeminiNavigationCaptureResult) +enum class GeminiNavigationCaptureResult { + kSuccess = 0, + kInvalidUrl = 1, + kNonHttpsScheme = 2, + kCIDTooLong = 3, + kTargetUrlTooLong = 4, + kNoTargetUrl = 5, + kMaxValue = kNoTargetUrl, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GeminiNavigationCaptureResult) + } // namespace glic #endif // CHROME_BROWSER_GLIC_GLIC_ENUMS_H_
diff --git a/chrome/browser/glic/glic_navigation_throttle.cc b/chrome/browser/glic/glic_navigation_throttle.cc index 6bc5278..347098e 100644 --- a/chrome/browser/glic/glic_navigation_throttle.cc +++ b/chrome/browser/glic/glic_navigation_throttle.cc
@@ -6,6 +6,8 @@ #include "base/functional/bind.h" #include "base/json/json_reader.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/user_metrics.h" #include "base/strings/escape.h" #include "base/task/single_thread_task_runner.h" #include "build/buildflag.h" @@ -143,6 +145,10 @@ std::optional<std::string> cid; std::optional<std::string> target_url_str; + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + bool is_glic_enabled = GlicEnabling::IsEnabledForProfile(profile); + size_t max_cid_length = features::kGlicWebContinuityMaxCIDLength.Get(); size_t max_target_url_length = features::kGlicWebContinuityMaxTargetUrlLength.Get(); @@ -151,61 +157,79 @@ if (it.GetKey() == "cid") { std::string unescaped_cid = it.GetUnescapedValue(); if (unescaped_cid.length() > max_cid_length) { + LogCaptureResult(is_glic_enabled, + GeminiNavigationCaptureResult::kCIDTooLong); return PROCEED; } cid = unescaped_cid; } else if (it.GetKey() == "targetUrl") { target_url_str = it.GetUnescapedValue(); if (target_url_str->length() > max_target_url_length) { + LogCaptureResult(is_glic_enabled, + GeminiNavigationCaptureResult::kTargetUrlTooLong); return PROCEED; } } } - Profile* profile = - Profile::FromBrowserContext(web_contents->GetBrowserContext()); + if (!target_url_str) { + LogCaptureResult(is_glic_enabled, + GeminiNavigationCaptureResult::kNoTargetUrl); + return PROCEED; + } + GURL target_url(*target_url_str); + if (!target_url.is_valid()) { + LogCaptureResult(is_glic_enabled, + GeminiNavigationCaptureResult::kInvalidUrl); + return PROCEED; + } + if (!target_url.SchemeIs(url::kHttpsScheme)) { + LogCaptureResult(is_glic_enabled, + GeminiNavigationCaptureResult::kNonHttpsScheme); + return PROCEED; + } - if (target_url_str) { - GURL target_url(*target_url_str); - // TODO (b/484408637): Add support for non-HTTPS schemes. - CHECK(target_url.is_valid() && target_url.SchemeIs(url::kHttpsScheme)); - if (target_url.is_valid() && target_url.SchemeIs(url::kHttpsScheme)) { - if (GlicEnabling::IsEnabledForProfile( - Profile::FromBrowserContext(web_contents->GetBrowserContext())) && - base::FeatureList::IsEnabled(features::kGlicWebContinuity)) { - GlicKeyedService* glic_service = GlicKeyedService::Get(profile); - if (glic_service) { - tabs::TabInterface* tab = - tabs::TabInterface::MaybeGetFromContents(web_contents); - if (tab && tab->GetBrowserWindowInterface()) { + GlicKeyedService* glic_service = GlicKeyedService::Get(profile); + if (is_glic_enabled && glic_service && + base::FeatureList::IsEnabled(features::kGlicWebContinuity)) { + tabs::TabInterface* tab = + tabs::TabInterface::MaybeGetFromContents(web_contents); + if (tab && tab->GetBrowserWindowInterface()) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - glic_service->ShowUiWithConversationID( - tab->GetBrowserWindowInterface(), - glic::mojom::InvocationSource::kNavigationCapture, *cid); + glic_service->ShowUiWithConversationID( + tab->GetBrowserWindowInterface(), + glic::mojom::InvocationSource::kNavigationCapture, cid ? *cid : ""); #pragma clang diagnostic pop - } - } - } - - // Navigate to the target URL. - NavigateParams params(profile, target_url, - ui::PAGE_TRANSITION_AUTO_TOPLEVEL); - params.disposition = WindowOpenDisposition::CURRENT_TAB; - params.source_contents = web_contents; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce([](NavigateParams params) { Navigate(¶ms); }, - std::move(params))); - return CANCEL; } } - return PROCEED; + // Navigate to the target URL. + NavigateParams params(profile, target_url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL); + params.disposition = WindowOpenDisposition::CURRENT_TAB; + params.source_contents = web_contents; + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, + base::BindOnce([](NavigateParams params) { Navigate(¶ms); }, + std::move(params))); + LogCaptureResult(is_glic_enabled, GeminiNavigationCaptureResult::kSuccess); + return CANCEL; } const char* GlicNavigationThrottle::GetNameForLogging() { return "GlicNavigationThrottle"; } +void GlicNavigationThrottle::LogCaptureResult( + bool is_glic_enabled, + glic::GeminiNavigationCaptureResult result) { + bool is_web_continuity_enabled = + base::FeatureList::IsEnabled(features::kGlicWebContinuity); + std::string_view status_string = is_glic_enabled && is_web_continuity_enabled + ? "GlicWebContinuityFeatureEnabled" + : "GlicWebContinuityFeatureDisabled"; + base::UmaHistogramEnumeration( + base::StrCat({"Glic.NavigationCapture.", status_string}), result); +} + } // namespace glic
diff --git a/chrome/browser/glic/glic_navigation_throttle.h b/chrome/browser/glic/glic_navigation_throttle.h index a7e34ce..fec2a04 100644 --- a/chrome/browser/glic/glic_navigation_throttle.h +++ b/chrome/browser/glic/glic_navigation_throttle.h
@@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_GLIC_GLIC_NAVIGATION_THROTTLE_H_ #define CHROME_BROWSER_GLIC_GLIC_NAVIGATION_THROTTLE_H_ +#include "chrome/browser/glic/glic_enums.h" +#include "chrome/browser/glic/public/glic_keyed_service.h" #include "content/public/browser/navigation_throttle.h" namespace content { @@ -28,6 +30,10 @@ // content::NavigationThrottle implementation: ThrottleCheckResult WillStartRequest() override; const char* GetNameForLogging() override; + + private: + void LogCaptureResult(bool is_glic_enabled, + glic::GeminiNavigationCaptureResult result); }; } // namespace glic
diff --git a/chrome/browser/glic/glic_navigation_throttle_browsertest.cc b/chrome/browser/glic/glic_navigation_throttle_browsertest.cc index 2ec308e..1d3ba65 100644 --- a/chrome/browser/glic/glic_navigation_throttle_browsertest.cc +++ b/chrome/browser/glic/glic_navigation_throttle_browsertest.cc
@@ -5,10 +5,12 @@ #include "chrome/browser/glic/glic_navigation_throttle.h" #include "base/memory/raw_ptr.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "chrome/browser/actor/actor_keyed_service_factory.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/contextual_cueing/contextual_cueing_service_factory.h" +#include "chrome/browser/glic/glic_enums.h" #include "chrome/browser/glic/glic_profile_manager.h" #include "chrome/browser/glic/public/glic_keyed_service.h" #include "chrome/browser/glic/public/glic_keyed_service_factory.h" @@ -55,6 +57,20 @@ return std::make_unique<MockGlicKeyedService>(context); } +void NavigateToURL(Browser* browser, const GURL& url) { + content::TestNavigationObserver observer( + browser->tab_strip_model()->GetActiveWebContents()); + content::NavigationController::LoadURLParams params(url); + params.initiator_origin = url::Origin::Create( + GURL(features::kGlicWebContinuityOriginatingHost.Get())); + params.transition_type = ui::PAGE_TRANSITION_LINK; + browser->tab_strip_model() + ->GetActiveWebContents() + ->GetController() + .LoadURLWithParams(params); + observer.Wait(); +} + class GlicNavigationThrottleBrowserTest : public InProcessBrowserTest { public: GlicNavigationThrottleBrowserTest() { @@ -94,6 +110,7 @@ IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, InterceptGlicContinueUrlFromGeminiAndOpenGlicUi) { + base::HistogramTester histogram_tester; MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), /*create=*/true)); @@ -108,21 +125,137 @@ GURL continue_url(features::kGlicWebContinuityUrl.Get() + "?cid=123&targetUrl=" + target_url.spec()); - content::TestNavigationObserver observer( - browser()->tab_strip_model()->GetActiveWebContents()); - content::NavigationController::LoadURLParams params(continue_url); - params.initiator_origin = url::Origin::Create( - GURL(features::kGlicWebContinuityOriginatingHost.Get())); - params.transition_type = ui::PAGE_TRANSITION_LINK; - browser() - ->tab_strip_model() - ->GetActiveWebContents() - ->GetController() - .LoadURLWithParams(params); - observer.Wait(); + NavigateToURL(browser(), continue_url); EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(), target_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kSuccess, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, Metrics_CIDTooLong) { + base::HistogramTester histogram_tester; + MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), + /*create=*/true)); + ASSERT_TRUE(mock_service); + + EXPECT_CALL(*mock_service, ShowUiWithConversationID(_, _, _)).Times(0); + + GURL target_url("https://www.google.com/"); + std::string long_cid(features::kGlicWebContinuityMaxCIDLength.Get() + 1, 'a'); + GURL continue_url(features::kGlicWebContinuityUrl.Get() + "?cid=" + long_cid + + "&targetUrl=" + target_url.spec()); + + NavigateToURL(browser(), continue_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kCIDTooLong, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, + Metrics_TargetUrlTooLong) { + base::HistogramTester histogram_tester; + MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), + /*create=*/true)); + ASSERT_TRUE(mock_service); + + EXPECT_CALL(*mock_service, ShowUiWithConversationID(_, _, _)).Times(0); + + std::string long_target_url( + features::kGlicWebContinuityMaxTargetUrlLength.Get() + 1, 'a'); + GURL target_url("https://" + long_target_url + ".com/"); + GURL continue_url(features::kGlicWebContinuityUrl.Get() + + "?cid=123&targetUrl=" + target_url.spec()); + + NavigateToURL(browser(), continue_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kTargetUrlTooLong, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, Metrics_InvalidUrl) { + base::HistogramTester histogram_tester; + MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), + /*create=*/true)); + ASSERT_TRUE(mock_service); + + EXPECT_CALL(*mock_service, ShowUiWithConversationID(_, _, _)).Times(0); + + GURL continue_url(features::kGlicWebContinuityUrl.Get() + + "?cid=123&targetUrl=invalidurl"); + + NavigateToURL(browser(), continue_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kInvalidUrl, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, + InterceptGlicContinueUrlFromGemini_NonHttpsTargetUrl) { + base::HistogramTester histogram_tester; + MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), + /*create=*/true)); + ASSERT_TRUE(mock_service); + + EXPECT_CALL(*mock_service, ShowUiWithConversationID(_, _, _)).Times(0); + + GURL target_url("http://www.example.com/"); + GURL continue_url(features::kGlicWebContinuityUrl.Get() + + "?cid=123&targetUrl=" + target_url.spec()); + + NavigateToURL(browser(), continue_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kNonHttpsScheme, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, + InterceptGlicContinueUrlFromGemini_NoTargetURL) { + base::HistogramTester histogram_tester; + MockGlicKeyedService* mock_service = static_cast<MockGlicKeyedService*>( + GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile(), + /*create=*/true)); + ASSERT_TRUE(mock_service); + + EXPECT_CALL(*mock_service, ShowUiWithConversationID(_, _, _)).Times(0); + + GURL continue_url(features::kGlicWebContinuityUrl.Get() + "?cid=123"); + + NavigateToURL(browser(), continue_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureEnabled", + GeminiNavigationCaptureResult::kNoTargetUrl, 1); +} + +IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTest, Incognito) { + base::HistogramTester histogram_tester; + + GURL target_url("https://www.google.com/"); + GURL continue_url(features::kGlicWebContinuityUrl.Get() + + "?cid=123&targetUrl=" + target_url.spec()); + + Browser* incognito_browser = CreateIncognitoBrowser(); + + NavigateToURL(incognito_browser, continue_url); + + EXPECT_EQ( + incognito_browser->tab_strip_model()->GetActiveWebContents()->GetURL(), + target_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureDisabled", + GeminiNavigationCaptureResult::kSuccess, 1); } class GlicNavigationThrottleBrowserTestWithNoFeatures @@ -151,25 +284,19 @@ IN_PROC_BROWSER_TEST_F(GlicNavigationThrottleBrowserTestWithNoFeatures, InterceptGlicContinueUrlFromGemini) { + base::HistogramTester histogram_tester; GURL target_url("https://www.google.com/"); GURL continue_url(features::kGlicWebContinuityUrl.Get() + "?cid=123&targetUrl=" + target_url.spec()); - content::TestNavigationObserver observer( - browser()->tab_strip_model()->GetActiveWebContents()); - content::NavigationController::LoadURLParams params(continue_url); - params.initiator_origin = url::Origin::Create( - GURL(features::kGlicWebContinuityOriginatingHost.Get())); - params.transition_type = ui::PAGE_TRANSITION_LINK; - browser() - ->tab_strip_model() - ->GetActiveWebContents() - ->GetController() - .LoadURLWithParams(params); - observer.Wait(); + NavigateToURL(browser(), continue_url); EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents()->GetURL(), target_url); + + histogram_tester.ExpectUniqueSample( + "Glic.NavigationCapture.GlicWebContinuityFeatureDisabled", + GeminiNavigationCaptureResult::kSuccess, 1); } } // namespace glic
diff --git a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc index 66ea621..a667f0d 100644 --- a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc +++ b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
@@ -28,6 +28,7 @@ #if !BUILDFLAG(IS_ANDROID) // NEEDS_ANDROID_IMPL #include "chrome/browser/actor/actor_keyed_service.h" #include "chrome/browser/glic/media/glic_media_integration.h" +#include "chrome/browser/glic/selection/selection_overlay_controller.h" #endif namespace glic { @@ -92,6 +93,26 @@ task_id = journal_entry->GetTaskId(); } if (page_context.screenshot_result.has_value()) { +#if !BUILDFLAG(IS_ANDROID) // NEEDS_ANDROID_IMPL + if (tab) { + // TODO(b/489714641): Remove this code we shouldn't be sending + // the screenshot this way. + auto encoded_data = + SelectionOverlayController::FromTabWebContents(tab->GetContents()) + ->GetEncodedData(); + if (encoded_data.has_value()) { + page_context.screenshot_result->screenshot_data = + std::move(encoded_data.value()); + } + + if (page_context.annotated_page_content_result.has_value()) { + page_context.annotated_page_content_result->proto + .mutable_gemini_in_chrome_page_metadata() + ->mutable_screenshot_info() + ->set_has_selection_region_in_screenshot(true); + } + } +#endif if (journal) { journal->LogScreenshot(tab_context->tab_data->url, task_id, page_context.screenshot_result->mime_type,
diff --git a/chrome/browser/glic/host/glic_api_browsertest.cc b/chrome/browser/glic/host/glic_api_browsertest.cc index 0ef8ed9..d5940c1 100644 --- a/chrome/browser/glic/host/glic_api_browsertest.cc +++ b/chrome/browser/glic/host/glic_api_browsertest.cc
@@ -2524,7 +2524,7 @@ // 4. Verify no action yet. histogram_tester->ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.GlicContents", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.GlicContents", 0); // 5. Trigger "createTab" (recursive) from the second tab's panel. ExecuteJsTest({.params = base::Value("createTab")}); @@ -2536,7 +2536,7 @@ // 7. Verify recursive metric for the second tab (which was daisy chained). ASSERT_TRUE(base::test::RunUntil([&]() { return histogram_tester->GetBucketCount( - "Glic.Instance.FirstActionInDaisyChainPanel.GlicContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.GlicContents", DaisyChainFirstAction::kRecursiveDaisyChain) == 1; })); @@ -2550,7 +2550,7 @@ // 10. Verify inputSubmitted metric for the third tab. ASSERT_TRUE(base::test::RunUntil([&]() { return histogram_tester->GetBucketCount( - "Glic.Instance.FirstActionInDaisyChainPanel.GlicContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.GlicContents", DaisyChainFirstAction::kInputSubmitted) == 1; })); } @@ -2578,7 +2578,7 @@ // 5. Verify Metric. ASSERT_TRUE(base::test::RunUntil([&]() { return histogram_tester->GetBucketCount( - "Glic.Instance.FirstActionInDaisyChainPanel.NewTab", + "Glic.Instance.AutoOpenedPanel.FirstAction.NewTab", DaisyChainFirstAction::kInputSubmitted) == 1; })); }
diff --git a/chrome/browser/glic/public/widget/glic_side_panel_coordinator_android.h b/chrome/browser/glic/public/widget/glic_side_panel_coordinator_android.h index ef2fbc39..0f8866fc 100644 --- a/chrome/browser/glic/public/widget/glic_side_panel_coordinator_android.h +++ b/chrome/browser/glic/public/widget/glic_side_panel_coordinator_android.h
@@ -8,6 +8,7 @@ #include "base/android/scoped_java_ref.h" #include "base/callback_list.h" #include "base/memory/raw_ref.h" +#include "base/memory/weak_ptr.h" #include "chrome/browser/glic/public/glic_side_panel_coordinator.h" namespace tabs { @@ -46,6 +47,7 @@ State state_ = State::kClosed; base::RepeatingCallbackList<void(State)> state_callbacks_; const raw_ref<tabs::TabInterface> tab_; + base::WeakPtr<content::WebContents> last_web_contents_; base::CallbackListSubscription did_activate_subscription_; base::CallbackListSubscription will_deactivate_subscription_; base::android::ScopedJavaGlobalRef<jobject> java_interface_;
diff --git a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc index eac2834c..3a7be8b 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc +++ b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc
@@ -878,4 +878,25 @@ EXPECT_EQ(error_future.Get(), GlicInvokeError::kInvalidConversationId); } +IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest, + InvokeWhileInvokeInProgress) { + tabs::TabInterface* tab = GetTabListInterface()->GetActiveTab(); + + base::test::TestFuture<GlicInvokeError> error_future1; + GlicInvokeOptions options1(mojom::InvocationSource::kOsButton); + + coordinator().Invoke(tab, std::move(options1)); + + base::test::TestFuture<GlicInvokeError> error_future2; + GlicInvokeOptions options2(mojom::InvocationSource::kOsButton); + options2.on_error = error_future2.GetCallback(); + + // Try to invoke again while the first one is still in progress for the same + // instance. + coordinator().Invoke(tab, std::move(options2)); + + // The second invoke should fail synchronously. + EXPECT_EQ(error_future2.Get(), GlicInvokeError::kInvokeInProgress); +} + } // namespace glic
diff --git a/chrome/browser/glic/service/glic_instance_helper_unittest.cc b/chrome/browser/glic/service/glic_instance_helper_unittest.cc index 0271a0b..35e7b24 100644 --- a/chrome/browser/glic/service/glic_instance_helper_unittest.cc +++ b/chrome/browser/glic/service/glic_instance_helper_unittest.cc
@@ -116,7 +116,7 @@ // Destructor triggers logging. } histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", DaisyChainFirstAction::kNoAction, 1); } @@ -127,7 +127,7 @@ helper.OnDaisyChainAction(DaisyChainFirstAction::kInputSubmitted); } histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.GlicContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.GlicContents", DaisyChainFirstAction::kInputSubmitted, 1); } @@ -136,14 +136,14 @@ GlicInstanceHelper helper(&mock_tab_); helper.SetIsDaisyChained(DaisyChainSource::kActorAddTab); helper.OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); - // Should not log immediately. + // Should not log immediately since kSidePanelClosed is ambiguous. histogram_tester_.ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.ActorAddTab", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.ActorAddTab", 0); // Fast forward to trigger timeout. task_environment_.FastForwardBy(base::Seconds(6)); } histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.ActorAddTab", + "Glic.Instance.AutoOpenedPanel.FirstAction.ActorAddTab", DaisyChainFirstAction::kSidePanelClosed, 1); } @@ -151,36 +151,53 @@ { GlicInstanceHelper helper(&mock_tab_); helper.SetIsDaisyChained(DaisyChainSource::kTabContents); - helper.OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); + helper.OnDaisyChainAction(DaisyChainFirstAction::kTabSwitched); // Ambiguous action start timer. histogram_tester_.ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", 0); // Terminal action happens before timeout. helper.OnDaisyChainAction(DaisyChainFirstAction::kRecursiveDaisyChain); } - // Should log terminal action immediately and ignore side panel closed. + // Should log terminal action immediately and ignore intermediate ambiguous + // action. histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", DaisyChainFirstAction::kRecursiveDaisyChain, 1); } -TEST_F(GlicInstanceHelperTest, - LogsDaisyChainOutcomeSidePanelClosedDestruction) { +TEST_F(GlicInstanceHelperTest, LogsDaisyChainOutcomeTabSwitchedDestruction) { { GlicInstanceHelper helper(&mock_tab_); helper.SetIsDaisyChained(DaisyChainSource::kNewTab); - helper.OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); + helper.OnDaisyChainAction(DaisyChainFirstAction::kTabSwitched); // Should not log immediately. histogram_tester_.ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.NewTab", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.NewTab", 0); // Destruction happens before timeout (timer was 5s). task_environment_.FastForwardBy(base::Seconds(2)); } // Should log on destruction. histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.NewTab", - DaisyChainFirstAction::kSidePanelClosed, 1); + "Glic.Instance.AutoOpenedPanel.FirstAction.NewTab", + DaisyChainFirstAction::kTabSwitched, 1); +} + +TEST_F(GlicInstanceHelperTest, LogsDaisyChainOutcomeTabSwitchedDelayed) { + { + GlicInstanceHelper helper(&mock_tab_); + helper.SetIsDaisyChained(DaisyChainSource::kActorAddTab); + helper.OnDaisyChainAction(DaisyChainFirstAction::kTabSwitched); + // Should not log immediately. + histogram_tester_.ExpectTotalCount( + "Glic.Instance.AutoOpenedPanel.FirstAction.ActorAddTab", 0); + // Fast forward to trigger timeout. + task_environment_.FastForwardBy(base::Seconds(6)); + } + // New metric keeps kTabSwitched as is. + histogram_tester_.ExpectUniqueSample( + "Glic.Instance.AutoOpenedPanel.FirstAction.ActorAddTab", + DaisyChainFirstAction::kTabSwitched, 1); } TEST_F(GlicInstanceHelperTest, LogsNewTabOutcome) { @@ -190,7 +207,29 @@ helper.OnDaisyChainAction(DaisyChainFirstAction::kInputSubmitted); } histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.NewTab", + "Glic.Instance.AutoOpenedPanel.FirstAction.NewTab", + DaisyChainFirstAction::kInputSubmitted, 1); +} + +TEST_F(GlicInstanceHelperTest, LogsWebHandoffBluebirdOutcome) { + { + GlicInstanceHelper helper(&mock_tab_); + helper.SetIsDaisyChained(DaisyChainSource::kWebHandoff); + helper.OnDaisyChainAction(DaisyChainFirstAction::kInputSubmitted); + } + histogram_tester_.ExpectUniqueSample( + "Glic.Instance.AutoOpenedPanel.FirstAction.WebHandoff", + DaisyChainFirstAction::kInputSubmitted, 1); +} + +TEST_F(GlicInstanceHelperTest, LogsAutoOpenPdfOutcome) { + { + GlicInstanceHelper helper(&mock_tab_); + helper.SetIsDaisyChained(DaisyChainSource::kAutoOpenPdf); + helper.OnDaisyChainAction(DaisyChainFirstAction::kInputSubmitted); + } + histogram_tester_.ExpectUniqueSample( + "Glic.Instance.AutoOpenedPanel.FirstAction.AutoOpenPdf", DaisyChainFirstAction::kInputSubmitted, 1); } @@ -198,23 +237,23 @@ { GlicInstanceHelper helper(&mock_tab_); helper.SetIsDaisyChained(DaisyChainSource::kTabContents); - helper.OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); + helper.OnDaisyChainAction(DaisyChainFirstAction::kTabSwitched); // Ambiguous action start timer. histogram_tester_.ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", 0); // NoAction should be ignored if we already have an action. helper.OnDaisyChainAction(DaisyChainFirstAction::kNoAction); histogram_tester_.ExpectTotalCount( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", 0); + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", 0); // Fast forward to trigger timeout. task_environment_.FastForwardBy(base::Seconds(6)); } - // Should log SidePanelClosed. + // Should log TabSwitched histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", - DaisyChainFirstAction::kSidePanelClosed, 1); + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", + DaisyChainFirstAction::kTabSwitched, 1); } TEST_F(GlicInstanceHelperTest, LogsDaisyChainOutcomeNoActionOnDestruction) { @@ -225,7 +264,7 @@ // Destructor triggers logging. } histogram_tester_.ExpectUniqueSample( - "Glic.Instance.FirstActionInDaisyChainPanel.TabContents", + "Glic.Instance.AutoOpenedPanel.FirstAction.TabContents", DaisyChainFirstAction::kNoAction, 1); }
diff --git a/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.cc b/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.cc index a551f351..cf89712 100644 --- a/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.cc +++ b/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.cc
@@ -61,7 +61,11 @@ current_metric_action_ = action; - if (action == DaisyChainFirstAction::kSidePanelClosed) { + // Switching tabs or explicitly closing the side panel can be ambiguous, e.g. + // user may cycle through tabs or quickly re-open the panel to finish + // something. We start a timer to wait for a more definitive action. + if (action == DaisyChainFirstAction::kTabSwitched || + action == DaisyChainFirstAction::kSidePanelClosed) { // Ambiguous action. Start/restart timer. flush_timer_.Start(FROM_HERE, base::Seconds(5), this, &GlicInstanceHelperMetrics::FlushMetric); @@ -76,8 +80,9 @@ return; } std::string source_str = GetDaisyChainSourceString(daisy_chain_source_); + base::UmaHistogramEnumeration( - base::StrCat({"Glic.Instance.FirstActionInDaisyChainPanel.", source_str}), + base::StrCat({"Glic.Instance.AutoOpenedPanel.FirstAction.", source_str}), current_metric_action_); metric_finalized_ = true; flush_timer_.Stop();
diff --git a/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.h b/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.h index 30cd3b5a..6f3a341 100644 --- a/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.h +++ b/chrome/browser/glic/service/metrics/glic_instance_helper_metrics.h
@@ -19,7 +19,10 @@ kSwitchedConversation = 2, kRecursiveDaisyChain = 3, kSidePanelClosed = 4, - kMaxValue = kSidePanelClosed, + kTabSwitched = 5, + kMaxValue = kTabSwitched, + // TODO(crbug.com/487306585): Add FRE-related first actions too. + }; // LINT.ThenChange(//tools/metrics/histograms/metadata/glic/enums.xml:GlicDaisyChainFirstAction) @@ -39,7 +42,9 @@ void OnBoundToInstance(const InstanceId& instance_id); void OnPinnedByInstance(const InstanceId& instance_id); - // Marks the tab as being part of a daisy chain session. + // TODO(crbug.com/489758590) Overhaul names with "DaisyChain" and maybe rename + // to "AutoOpenPanel"; + // Marks the tab as being part of a daisy chain session. void SetIsDaisyChained(DaisyChainSource source); // Records a significant user action during a daisy chain session.
diff --git a/chrome/browser/glic/service/metrics/glic_instance_metrics.cc b/chrome/browser/glic/service/metrics/glic_instance_metrics.cc index 2bdbffb..23e991f 100644 --- a/chrome/browser/glic/service/metrics/glic_instance_metrics.cc +++ b/chrome/browser/glic/service/metrics/glic_instance_metrics.cc
@@ -315,6 +315,16 @@ base::RecordAction(base::UserMetricsAction("Glic.Instance.Show.SidePanel")); LogEvent(GlicInstanceEvent::kSidePanelShown); LogEvent(GlicInstanceEvent::kShown); + + if (auto* helper = GlicInstanceHelper::From(tab)) { + if (last_invocation_source_ == + mojom::InvocationSource::kNavigationCapture) { + helper->SetIsDaisyChained(DaisyChainSource::kWebHandoff); + } else if (last_invocation_source_ == + mojom::InvocationSource::kAutoOpenedForPdf) { + helper->SetIsDaisyChained(DaisyChainSource::kAutoOpenPdf); + } + } } void GlicInstanceMetrics::OnShowInFloaty(const ShowOptions& options) { @@ -352,13 +362,19 @@ floaty_open_time_ = base::TimeTicks(); } -void GlicInstanceMetrics::OnSidePanelClosed(tabs::TabInterface* tab) { +void GlicInstanceMetrics::OnSidePanelClosed( + tabs::TabInterface* tab, + GlicInstanceMetrics::CloseReason reason) { if (!tab) { return; } if (auto* helper = GlicInstanceHelper::From(tab)) { - helper->OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); + if (reason == GlicInstanceMetrics::CloseReason::kTabSwitched) { + helper->OnDaisyChainAction(DaisyChainFirstAction::kTabSwitched); + } else { + helper->OnDaisyChainAction(DaisyChainFirstAction::kSidePanelClosed); + } } tabs::TabHandle tab_handle = tab->GetHandle();
diff --git a/chrome/browser/glic/service/metrics/glic_instance_metrics.h b/chrome/browser/glic/service/metrics/glic_instance_metrics.h index c1cabeb..b1d580a 100644 --- a/chrome/browser/glic/service/metrics/glic_instance_metrics.h +++ b/chrome/browser/glic/service/metrics/glic_instance_metrics.h
@@ -185,8 +185,10 @@ // Called when the floaty is hidden. void OnFloatyClosed(); + enum class CloseReason { kExplicitlyClosed, kTabSwitched }; + // Called when the side panel is closed. - void OnSidePanelClosed(tabs::TabInterface* tab); + void OnSidePanelClosed(tabs::TabInterface* tab, CloseReason reason); // Called when an embedder is unbound from this instance. void OnUnbindEmbedder(EmbedderKey key);
diff --git a/chrome/browser/glic/service/metrics/glic_instance_metrics_unittest.cc b/chrome/browser/glic/service/metrics/glic_instance_metrics_unittest.cc index f32cf2b9..6fb5022 100644 --- a/chrome/browser/glic/service/metrics/glic_instance_metrics_unittest.cc +++ b/chrome/browser/glic/service/metrics/glic_instance_metrics_unittest.cc
@@ -121,7 +121,9 @@ } TEST_F(GlicInstanceMetricsTest, OnSidePanelClosed_WithoutOpening_LogsError) { - metrics_.OnSidePanelClosed(static_cast<tabs::TabInterface*>(&mock_tab_)); + metrics_.OnSidePanelClosed( + static_cast<tabs::TabInterface*>(&mock_tab_), + GlicInstanceMetrics::CloseReason::kExplicitlyClosed); histogram_tester_.ExpectUniqueSample( "Glic.Instance.Metrics.Error", GlicInstanceMetricsError::kSidePanelClosedWithoutOpen, 1); @@ -205,7 +207,8 @@ TEST_F(GlicInstanceMetricsTest, ValidSidePanelFlow_DoesNotLogError) { EXPECT_CALL(mock_tab_, GetTabHandle()).WillRepeatedly(testing::Return(1)); metrics_.OnShowInSidePanel(&mock_tab_); - metrics_.OnSidePanelClosed(&mock_tab_); + metrics_.OnSidePanelClosed( + &mock_tab_, GlicInstanceMetrics::CloseReason::kExplicitlyClosed); histogram_tester_.ExpectTotalCount("Glic.Instance.Metrics.Error", 0); } @@ -261,8 +264,10 @@ TEST_F(GlicInstanceMetricsTest, SidePanel_OpenCloseClose_LogsError) { EXPECT_CALL(mock_tab_, GetTabHandle()).WillRepeatedly(testing::Return(1)); metrics_.OnShowInSidePanel(&mock_tab_); - metrics_.OnSidePanelClosed(&mock_tab_); - metrics_.OnSidePanelClosed(&mock_tab_); + metrics_.OnSidePanelClosed( + &mock_tab_, GlicInstanceMetrics::CloseReason::kExplicitlyClosed); + metrics_.OnSidePanelClosed( + &mock_tab_, GlicInstanceMetrics::CloseReason::kExplicitlyClosed); histogram_tester_.ExpectUniqueSample( "Glic.Instance.Metrics.Error", GlicInstanceMetricsError::kSidePanelClosedWithoutOpen, 1);
diff --git a/chrome/browser/glic/service/metrics/metrics_types.cc b/chrome/browser/glic/service/metrics/metrics_types.cc index 7455fab..e7af8c4d 100644 --- a/chrome/browser/glic/service/metrics/metrics_types.cc +++ b/chrome/browser/glic/service/metrics/metrics_types.cc
@@ -16,6 +16,10 @@ return "ActorAddTab"; case DaisyChainSource::kNewTab: return "NewTab"; + case DaisyChainSource::kWebHandoff: + return "WebHandoff"; + case DaisyChainSource::kAutoOpenPdf: + return "AutoOpenPdf"; default: return "Unknown"; }
diff --git a/chrome/browser/glic/service/metrics/metrics_types.h b/chrome/browser/glic/service/metrics/metrics_types.h index 5c68c22..d8aa3dde 100644 --- a/chrome/browser/glic/service/metrics/metrics_types.h +++ b/chrome/browser/glic/service/metrics/metrics_types.h
@@ -15,7 +15,9 @@ kTabContents = 2, kActorAddTab = 3, kNewTab = 4, - kMaxValue = kNewTab, + kWebHandoff = 5, + kAutoOpenPdf = 6, + kMaxValue = kAutoOpenPdf, }; std::string GetDaisyChainSourceString(DaisyChainSource source);
diff --git a/chrome/browser/glic/widget/glic_side_panel_coordinator_android.cc b/chrome/browser/glic/widget/glic_side_panel_coordinator_android.cc index 1dc77d0..5d9ba42 100644 --- a/chrome/browser/glic/widget/glic_side_panel_coordinator_android.cc +++ b/chrome/browser/glic/widget/glic_side_panel_coordinator_android.cc
@@ -58,6 +58,7 @@ void GlicSidePanelCoordinatorAndroid::SetWebContents( content::WebContents* web_contents) { if (!web_contents) { + last_web_contents_.reset(); co_browse_views_.Reset(); return; } @@ -70,6 +71,13 @@ if (!window_android) { return; } + + if (last_web_contents_ && last_web_contents_.get() == web_contents) { + // The web contents didn't change, so we don't need to do anything. + return; + } + + last_web_contents_ = web_contents->GetWeakPtr(); JNIEnv* env = base::android::AttachCurrentThread(); // Call Factory to get CoBrowseViews and save it co_browse_views_.Reset(Java_CoBrowseViewFactory_getCoBrowseViews(
diff --git a/chrome/browser/glic/widget/glic_side_panel_ui.cc b/chrome/browser/glic/widget/glic_side_panel_ui.cc index 9014fddb..fc7c8bb 100644 --- a/chrome/browser/glic/widget/glic_side_panel_ui.cc +++ b/chrome/browser/glic/widget/glic_side_panel_ui.cc
@@ -170,7 +170,11 @@ // Showing only happens through glic entrypoint, hiding can also be triggered // by side panel coordinator when replacing glic with another entry. if (state != GlicSidePanelCoordinator::State::kShown && tab_) { - instance_metrics_->OnSidePanelClosed(tab_.get()); + GlicInstanceMetrics::CloseReason reason = + state == GlicSidePanelCoordinator::State::kBackgrounded + ? GlicInstanceMetrics::CloseReason::kTabSwitched + : GlicInstanceMetrics::CloseReason::kExplicitlyClosed; + instance_metrics_->OnSidePanelClosed(tab_.get(), reason); panel_state_.kind = mojom::PanelStateKind::kHidden; delegate_->NotifyPanelStateChanged(); // NOTE: `this` will be destroyed after this call.
diff --git a/chrome/browser/glic/widget/glic_side_panel_ui_android.cc b/chrome/browser/glic/widget/glic_side_panel_ui_android.cc index b2b9b8f..f80b36e 100644 --- a/chrome/browser/glic/widget/glic_side_panel_ui_android.cc +++ b/chrome/browser/glic/widget/glic_side_panel_ui_android.cc
@@ -207,7 +207,11 @@ void GlicSidePanelUi::SidePanelStateChanged( GlicSidePanelCoordinator::State state) { if (state != GlicSidePanelCoordinator::State::kShown && tab_) { - instance_metrics_->OnSidePanelClosed(tab_.get()); + GlicInstanceMetrics::CloseReason reason = + state == GlicSidePanelCoordinator::State::kBackgrounded + ? GlicInstanceMetrics::CloseReason::kTabSwitched + : GlicInstanceMetrics::CloseReason::kExplicitlyClosed; + instance_metrics_->OnSidePanelClosed(tab_.get(), reason); panel_state_.kind = mojom::PanelStateKind::kHidden; delegate_->NotifyPanelStateChanged(); // NOTE: `this` will be destroyed after this call.
diff --git a/chrome/browser/headless/headless_mode_protocol_browsertest.cc b/chrome/browser/headless/headless_mode_protocol_browsertest.cc index a91b16a..36a9eff 100644 --- a/chrome/browser/headless/headless_mode_protocol_browsertest.cc +++ b/chrome/browser/headless/headless_mode_protocol_browsertest.cc
@@ -483,6 +483,32 @@ HEADLESS_MODE_PROTOCOL_TEST(AddRemoveScreen, "shared/add-remove-screen.js") +// Emulation.UpdateScreen is not yet supported on macOS and Windows. +#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC) +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenBounds, + "shared/update-screen-bounds.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenWorkArea, + "shared/update-screen-work-area.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenDevicePixelRatio, + "shared/update-screen-device-pixel-ratio.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenRotationPortrait, + "shared/update-screen-rotation-portrait.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenRotationLandscape, + "shared/update-screen-rotation-landscape.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenColorDepth, + "shared/update-screen-color-depth.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenLabel, "shared/update-screen-label.js") + +HEADLESS_MODE_PROTOCOL_TEST(UpdateScreenIsInternal, + "shared/update-screen-is-internal.js") +#endif + // Emulation.SetPrimaryScreen is not yet supported on Windows. #if !BUILDFLAG(IS_WIN) HEADLESS_MODE_PROTOCOL_TEST(SetPrimaryScreen, "shared/set-primary-screen.js")
diff --git a/chrome/browser/history_embeddings/BUILD.gn b/chrome/browser/history_embeddings/BUILD.gn new file mode 100644 index 0000000..ac76839 --- /dev/null +++ b/chrome/browser/history_embeddings/BUILD.gn
@@ -0,0 +1,106 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//components/optimization_guide/features.gni") + +source_set("history_embeddings") { + sources = [ + "chrome_history_embeddings_service.h", + "history_embeddings_service_factory.h", + "history_embeddings_tab_helper.h", + "history_embeddings_utils.h", + ] + + public_deps = [ + "//base", + "//chrome/browser/profiles:profile", + "//chrome/browser/resource_coordinator", + "//components/history/core/browser", + "//components/history_embeddings/content", + "//content/public/browser", + ] +} + +source_set("impl") { + sources = [ + "chrome_history_embeddings_service.cc", + "history_embeddings_service_factory.cc", + "history_embeddings_tab_helper.cc", + "history_embeddings_utils.cc", + ] + + public_deps = [ "//chrome/browser:browser_public_dependencies" ] + + deps = [ + ":history_embeddings", + "//chrome/app:generated_resources", + "//chrome/browser:browser_process", + "//chrome/browser/app_mode", + "//chrome/browser/history", + "//chrome/browser/optimization_guide", + "//chrome/browser/page_content_annotations", + "//chrome/browser/passage_embeddings", + "//chrome/browser/profiles:profile", + "//chrome/browser/profiles:profile_util", + "//chrome/browser/resource_coordinator", + "//chrome/common:constants", + "//components/history/core/browser", + "//components/history_embeddings/content", + "//components/history_embeddings/core", + "//components/keyed_service/core", + "//components/optimization_guide/core", + "//components/optimization_guide/proto:optimization_guide_proto", + "//components/passage_embeddings/core", + "//components/prefs", + "//components/strings", + "//components/variations/service", + "//content/public/browser", + "//mojo/public/cpp/bindings", + "//services/service_manager/public/cpp", + "//ui/base", + "//url", + ] + + if (is_chromeos) { + deps += [ + "//chrome/browser/ash/profiles", + "//chromeos/constants", + ] + } +} + +if (!is_android) { + source_set("browser_tests") { + testonly = true + + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + + sources = [ "history_embeddings_service_browsertest.cc" ] + + deps = [ + ":history_embeddings", + "//base", + "//base/test:test_support", + "//chrome/browser/optimization_guide:test_support", + "//chrome/browser/page_content_annotations", + "//chrome/browser/passage_embeddings", + "//chrome/browser/profiles:profile", + "//chrome/browser/ui", + "//chrome/browser/ui/tabs:tabs_public", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//components/history_embeddings/content", + "//components/history_embeddings/core", + "//components/keyed_service/content", + "//components/optimization_guide/core:test_support", + "//components/page_content_annotations/core", + "//components/page_content_annotations/core:test_support", + "//components/passage_embeddings/core:test_support", + "//content/public/browser", + "//content/test:test_support", + "//testing/gmock", + "//testing/gtest", + ] + } +}
diff --git a/chrome/browser/history_embeddings/DEPS b/chrome/browser/history_embeddings/DEPS new file mode 100644 index 0000000..1665a7d --- /dev/null +++ b/chrome/browser/history_embeddings/DEPS
@@ -0,0 +1,72 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +include_rules = [ + "+chrome/browser/app_mode/app_mode_utils.h", + "+chrome/browser/ash/profiles/profile_helper.h", + "+chrome/browser/browser_process.h", + "+chrome/browser/history/history_service_factory.h", + "+chrome/browser/optimization_guide/browser_test_util.h", + "+chrome/browser/optimization_guide/optimization_guide_keyed_service.h", + "+chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h", + "+chrome/browser/page_content_annotations/page_content_annotations_service_factory.h", + "+chrome/browser/passage_embeddings/chrome_passage_embeddings_service_controller.h", + "+chrome/browser/passage_embeddings/page_embeddings_service_factory.h", + "+chrome/browser/passage_embeddings/passage_embedder_model_observer_factory.h", + "+chrome/browser/profiles/profile.h", + "+chrome/browser/profiles/profile_attributes_entry.h", + "+chrome/browser/profiles/profile_attributes_storage.h", + "+chrome/browser/profiles/profile_keyed_service_factory.h", + "+chrome/browser/profiles/profile_manager.h", + "+chrome/browser/resource_coordinator/tab_load_tracker.h", + "+chrome/browser/ui/browser.h", + "+chrome/browser/ui/tabs/tab_strip_model.h", + "+chrome/common/url_constants.h", + "+chrome/grit/generated_resources.h", + "+chrome/test/base/in_process_browser_test.h", + "+chrome/test/base/ui_test_utils.h", + "+chromeos/constants/chromeos_features.h", + "+components/history/core/browser/history_service.h", + "+components/history/core/browser/history_types.h", + "+components/history/core/browser/url_row.h", + "+components/history_embeddings/content/history_embeddings_service.h", + "+components/history_embeddings/core/history_embeddings_features.h", + "+components/history_embeddings/core/ml_answerer.h", + "+components/history_embeddings/core/ml_intent_classifier.h", + "+components/history_embeddings/core/mock_answerer.h", + "+components/history_embeddings/core/mock_intent_classifier.h", + "+components/keyed_service/content/browser_context_dependency_manager.h", + "+components/keyed_service/core/service_access_type.h", + "+components/optimization_guide/core/delivery/test_model_info_builder.h", + "+components/optimization_guide/core/feature_registry/feature_registration.h", + "+components/optimization_guide/core/model_execution/feature_keys.h", + "+components/optimization_guide/core/model_execution/model_execution_features.h", + "+components/optimization_guide/core/model_execution/model_execution_prefs.h", + "+components/optimization_guide/core/model_execution/model_execution_util.h", + "+components/optimization_guide/core/model_quality/model_quality_log_entry.h", + "+components/optimization_guide/core/optimization_guide_features.h", + "+components/optimization_guide/proto/model_quality_service.pb.h", + "+components/page_content_annotations/core/page_content_annotations_features.h", + "+components/page_content_annotations/core/page_content_annotations_service.h", + "+components/page_content_annotations/core/test_page_content_annotator.h", + "+components/passage_embeddings/core/passage_embeddings_test_util.h", + "+components/passage_embeddings/core/passage_embeddings_types.h", + "+components/prefs/pref_service.h", + "+components/strings/grit/components_strings.h", + "+components/variations/service/variations_service.h", + "+content/public/browser/browser_context.h", + "+content/public/browser/navigation_handle.h", + "+content/public/browser/render_frame_host.h", + "+content/public/browser/weak_document_ptr.h", + "+content/public/browser/web_contents.h", + "+content/public/browser/web_contents_observer.h", + "+content/public/browser/web_contents_user_data.h", + "+content/public/browser/web_ui_data_source.h", + "+content/public/test/browser_test.h", + "+mojo/public/cpp/bindings/callback_helpers.h", + "+mojo/public/cpp/bindings/remote.h", + "+services/service_manager/public/cpp/interface_provider.h", + "+third_party/blink/public/mojom/content_extraction/inner_text.mojom.h", + "+url/gurl.h", +]
diff --git a/chrome/browser/history_embeddings/chrome_history_embeddings_service.cc b/chrome/browser/history_embeddings/chrome_history_embeddings_service.cc index 6bb9de79d..e00dee995 100644 --- a/chrome/browser/history_embeddings/chrome_history_embeddings_service.cc +++ b/chrome/browser/history_embeddings/chrome_history_embeddings_service.cc
@@ -8,15 +8,13 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/history_embeddings/history_embeddings_utils.h" -#include "chrome/browser/optimization_guide/chrome_model_quality_logs_uploader_service.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" #include "chrome/browser/profiles/profile.h" -#include "components/history_embeddings/core/history_embeddings_features.h" #include "components/optimization_guide/core/model_execution/feature_keys.h" #include "components/optimization_guide/core/model_quality/model_quality_log_entry.h" -#include "components/optimization_guide/proto/features/history_query.pb.h" #include "components/optimization_guide/proto/model_quality_service.pb.h" +#include "components/passage_embeddings/core/passage_embeddings_types.h" namespace history_embeddings {
diff --git a/chrome/browser/history_embeddings/chrome_history_embeddings_service.h b/chrome/browser/history_embeddings/chrome_history_embeddings_service.h index e5b7ba7..8ede51a6 100644 --- a/chrome/browser/history_embeddings/chrome_history_embeddings_service.h +++ b/chrome/browser/history_embeddings/chrome_history_embeddings_service.h
@@ -6,14 +6,27 @@ #define CHROME_BROWSER_HISTORY_EMBEDDINGS_CHROME_HISTORY_EMBEDDINGS_SERVICE_H_ #include "base/memory/raw_ptr.h" -#include "base/no_destructor.h" -#include "chrome/browser/profiles/profile_keyed_service_factory.h" #include "components/history_embeddings/content/history_embeddings_service.h" -#include "components/passage_embeddings/core/passage_embeddings_types.h" -#include "content/public/browser/browser_context.h" class Profile; +namespace history { +class HistoryService; +} + +namespace optimization_guide { +class OptimizationGuideDecider; +} + +namespace page_content_annotations { +class PageContentAnnotationsService; +} + +namespace passage_embeddings { +class Embedder; +class EmbedderMetadataProvider; +} // namespace passage_embeddings + namespace history_embeddings { // Specialization of HistoryEmbeddingsService with implementation details
diff --git a/chrome/browser/history_embeddings/history_embeddings_service_factory.cc b/chrome/browser/history_embeddings/history_embeddings_service_factory.cc index 78cdf23b..0a0cfeb4 100644 --- a/chrome/browser/history_embeddings/history_embeddings_service_factory.cc +++ b/chrome/browser/history_embeddings/history_embeddings_service_factory.cc
@@ -19,8 +19,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_attributes_entry.h" #include "chrome/browser/profiles/profile_attributes_storage.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "components/history/core/browser/history_service.h" +#include "chrome/browser/profiles/profile_manager.h" // nogncheck #include "components/history_embeddings/content/history_embeddings_service.h" #include "components/history_embeddings/core/history_embeddings_features.h" #include "components/history_embeddings/core/ml_answerer.h"
diff --git a/chrome/browser/history_embeddings/history_embeddings_service_factory.h b/chrome/browser/history_embeddings/history_embeddings_service_factory.h index 958b2db..68dbbe28 100644 --- a/chrome/browser/history_embeddings/history_embeddings_service_factory.h +++ b/chrome/browser/history_embeddings/history_embeddings_service_factory.h
@@ -9,7 +9,12 @@ #include "base/no_destructor.h" #include "chrome/browser/profiles/profile_keyed_service_factory.h" -#include "content/public/browser/browser_context.h" + +class Profile; + +namespace content { +class BrowserContext; +} namespace history_embeddings { class Answerer;
diff --git a/chrome/browser/history_embeddings/history_embeddings_tab_helper.cc b/chrome/browser/history_embeddings/history_embeddings_tab_helper.cc index 3d55334..c05f8d0 100644 --- a/chrome/browser/history_embeddings/history_embeddings_tab_helper.cc +++ b/chrome/browser/history_embeddings/history_embeddings_tab_helper.cc
@@ -24,18 +24,9 @@ #include "content/public/browser/web_contents.h" HistoryEmbeddingsTabHelper::HistoryEmbeddingsTabHelper( - content::WebContents* web_contents, - HistoryTabHelper* history_tab_helper) + content::WebContents* web_contents) : content::WebContentsObserver(web_contents), - content::WebContentsUserData<HistoryEmbeddingsTabHelper>(*web_contents) { - if (history_tab_helper) { - history_tab_helper_subscription_ = - history_tab_helper->RegisterOnUpdatedHistoryForNavigationCallback( - base::BindRepeating( - &HistoryEmbeddingsTabHelper::OnUpdatedHistoryForNavigation, - weak_ptr_factory_.GetWeakPtr())); - } -} + content::WebContentsUserData<HistoryEmbeddingsTabHelper>(*web_contents) {} HistoryEmbeddingsTabHelper::~HistoryEmbeddingsTabHelper() = default; @@ -102,6 +93,16 @@ std::nullopt); } +void HistoryEmbeddingsTabHelper::SetHistoryTabHelperSubscription( + base::CallbackListSubscription subscription) { + history_tab_helper_subscription_ = std::move(subscription); +} + +base::WeakPtr<HistoryEmbeddingsTabHelper> +HistoryEmbeddingsTabHelper::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + void HistoryEmbeddingsTabHelper::UpdateEmbeddingsServiceWithHistoryData( history::QueryURLAndVisitsResult result) { // `visits` can be empty for navigations that don't result in a
diff --git a/chrome/browser/history_embeddings/history_embeddings_tab_helper.h b/chrome/browser/history_embeddings/history_embeddings_tab_helper.h index f49c0ed..6478e59a 100644 --- a/chrome/browser/history_embeddings/history_embeddings_tab_helper.h +++ b/chrome/browser/history_embeddings/history_embeddings_tab_helper.h
@@ -11,7 +11,6 @@ #include "base/memory/weak_ptr.h" #include "base/task/cancelable_task_tracker.h" #include "base/time/time.h" -#include "chrome/browser/history/history_tab_helper.h" #include "components/history/core/browser/history_types.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" @@ -20,9 +19,11 @@ namespace history { class HistoryService; } + namespace history_embeddings { class HistoryEmbeddingsService; } + namespace passage_embeddings { class PassageEmbedderModelObserver; } @@ -51,9 +52,13 @@ const GURL& validated_url) override; void WebContentsDestroyed() override; + void SetHistoryTabHelperSubscription( + base::CallbackListSubscription subscription); + + base::WeakPtr<HistoryEmbeddingsTabHelper> GetWeakPtr(); + protected: - HistoryEmbeddingsTabHelper(content::WebContents* web_contents, - HistoryTabHelper* history_tab_helper); + explicit HistoryEmbeddingsTabHelper(content::WebContents* web_contents); private: friend class content::WebContentsUserData<HistoryEmbeddingsTabHelper>;
diff --git a/chrome/browser/interest_group/DIR_METADATA b/chrome/browser/interest_group/DIR_METADATA deleted file mode 100644 index 245f2acd..0000000 --- a/chrome/browser/interest_group/DIR_METADATA +++ /dev/null
@@ -1,2 +0,0 @@ -mixins: "//content/browser/interest_group/COMMON_METADATA" -team_email: "privacy-sandbox-dev@chromium.org" \ No newline at end of file
diff --git a/chrome/browser/interest_group/OWNERS b/chrome/browser/interest_group/OWNERS deleted file mode 100644 index af17a31..0000000 --- a/chrome/browser/interest_group/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -file://content/browser/interest_group/OWNERS
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java index 0d80babf..6ccce2f 100644 --- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java +++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java
@@ -8,10 +8,12 @@ import androidx.annotation.VisibleForTesting; +import org.chromium.base.DeviceInfo; import org.chromium.base.ObserverList; import org.chromium.base.ResettersForTesting; import org.chromium.base.shared_preferences.SharedPreferencesManager; import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; @@ -86,7 +88,10 @@ * @param moduleType {@link ModuleType} needed to be notified to the listeners. */ public boolean getPrefModuleTypeEnabled(@ModuleType int moduleType) { - return mSharedPreferencesManager.readBoolean(getSettingsPreferenceKey(moduleType), true); + boolean defaultEnabled = + !ChromeFeatureList.sNtpSimplification.isEnabled() || !DeviceInfo.isDesktop(); + return mSharedPreferencesManager.readBoolean( + getSettingsPreferenceKey(moduleType), defaultEnabled); } /**
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManagerUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManagerUnitTest.java index ed6acdc8..b17ecc3 100644 --- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManagerUnitTest.java +++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManagerUnitTest.java
@@ -26,8 +26,11 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.chromium.base.DeviceInfo; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.Features.DisableFeatures; import org.chromium.base.test.util.Features.EnableFeatures; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager.HomeModulesStateListener; import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType; @@ -134,4 +137,28 @@ .readBoolean(ChromePreferenceKeys.HOME_MODULE_CARDS_ENABLED, false)); verify(mListener).allCardsConfigChanged(eq(true)); } + + @Test + @DisableFeatures(ChromeFeatureList.NTP_SIMPLIFICATION) + public void testGetPrefModuleTypeEnabledOnDesktop_NtpSimplificationDisabled() { + DeviceInfo.setIsDesktopForTesting(true); + assertTrue(mHomeModulesConfigManager.getPrefModuleTypeEnabled(ModuleType.SINGLE_TAB)); + assertTrue(mHomeModulesConfigManager.getPrefModuleTypeEnabled(ModuleType.SAFETY_HUB)); + assertTrue( + mHomeModulesConfigManager.getPrefModuleTypeEnabled( + ModuleType.DEFAULT_BROWSER_PROMO)); + } + + @Test + public void testGetPrefModuleTypeEnabledOnDesktop_NtpSimplificationEnabled() { + DeviceInfo.setIsDesktopForTesting(true); + assertFalse(mHomeModulesConfigManager.getPrefModuleTypeEnabled(ModuleType.SINGLE_TAB)); + assertFalse(mHomeModulesConfigManager.getPrefModuleTypeEnabled(ModuleType.SAFETY_HUB)); + assertFalse( + mHomeModulesConfigManager.getPrefModuleTypeEnabled( + ModuleType.DEFAULT_BROWSER_PROMO)); + + mHomeModulesConfigManager.setPrefModuleTypeEnabled(ModuleType.SINGLE_TAB, true); + assertTrue(mHomeModulesConfigManager.getPrefModuleTypeEnabled(ModuleType.SINGLE_TAB)); + } }
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter.cc b/chrome/browser/metrics/process_memory_metrics_emitter.cc index 6f2ee6c70..5b51cb563 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter.cc
@@ -244,13 +244,26 @@ "DurableMessages.AggregateMemoryUsage", MetricSize::kLarge, kSize, EmitTo::kSizeInUmaOnly, nullptr}, {"devtools/durable_message_collectors", - "DurableMessages.AggregateMessageCount", MetricSize::kTiny, - MemoryAllocatorDump::kNameObjectCount, EmitTo::kSizeInUmaOnly, nullptr}, - {"devtools/durable_message_collectors", "DurableMessages.CollectorCount", - MetricSize::kTiny, MemoryAllocatorDump::kNameObjectCount, - EmitTo::kSizeInUmaOnly, nullptr}, - {"devtools/sessions", "DevTools.Sessions.ActiveCount", MetricSize::kTiny, - MemoryAllocatorDump::kNameObjectCount, EmitTo::kSizeInUmaOnly, nullptr}, + "DurableMessages.AggregateMessageCount", + MetricSize::kCustom, + "message_count", + EmitTo::kSizeInUmaOnly, + nullptr, + {1, 1000000}}, + {"devtools/durable_message_collectors", + "DurableMessages.CollectorCount", + MetricSize::kCustom, + "collector_count", + EmitTo::kSizeInUmaOnly, + nullptr, + {1, 10000}}, + {"devtools/sessions", + "DevTools.Sessions.ActiveCount", + MetricSize::kCustom, + MemoryAllocatorDump::kNameObjectCount, + EmitTo::kSizeInUmaOnly, + nullptr, + {1, 10000}}, {"discardable", "Discardable", MetricSize::kLarge, kEffectiveSize, EmitTo::kSizeInUkmAndUma, &Memory_Experimental::SetDiscardable}, {"discardable", "Discardable.FreelistSize", MetricSize::kSmall,
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc index 3ffe50a3..5c83755 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
@@ -25,6 +25,7 @@ #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" #include "services/metrics/public/cpp/ukm_source.h" +#include "services/network/public/mojom/network_service.mojom.h" #include "services/resource_coordinator/public/cpp/memory_instrumentation/browser_metrics.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" @@ -465,9 +466,11 @@ void PopulateUtilityMetrics(GlobalMemoryDumpPtr& global_dump, MetricMap& metrics_mb, - const std::optional<std::string>& service_name) { + const std::optional<std::string>& service_name, + base::ProcessId pid = base::kNullProcessId) { auto pmd(memory_instrumentation::mojom::ProcessMemoryDump::New()); pmd->process_type = ProcessType::UTILITY; + pmd->pid = pid; if (service_name.has_value()) { pmd->service_name = service_name.value(); } @@ -479,6 +482,20 @@ global_dump->process_dumps.push_back(std::move(pmd)); } +MetricMap GetExpectedNetworkServiceMetrics() { + return MetricMap({ + {"ProcessType", static_cast<int64_t>(ProcessType::UTILITY)}, + {"Resident", 0}, + {"Malloc", 0}, + {"PrivateMemoryFootprint", 0}, + {"SharedMemoryFootprint", 0}, + {"Uptime", 42}, +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) + {"PrivateSwapFootprint", 0}, +#endif + }); +} + MetricMap GetExpectedAudioServiceMetrics() { return MetricMap({ {"ProcessType", static_cast<int64_t>(ProcessType::UTILITY)}, @@ -1017,6 +1034,53 @@ EXPECT_EQ(1u, entries.size()); } +TEST_F(ProcessMemoryMetricsEmitterTest, NetworkServiceHistogramsAreRecorded) { + base::HistogramTester histograms; + + GlobalMemoryDumpPtr global_dump( + memory_instrumentation::mojom::GlobalMemoryDump::New()); + global_dump->aggregated_metrics = + memory_instrumentation::mojom::AggregatedMetrics::New(); + + MetricMap expected_network_metrics = GetExpectedNetworkServiceMetrics(); + PopulateUtilityMetrics(global_dump, expected_network_metrics, + network::mojom::NetworkService::Name_, 200 /* pid */); + + constexpr uint64_t kMiB = 1024 * 1024; + SetAllocatorDumpMetric(global_dump->process_dumps[0], + "devtools/durable_message_collectors", "message_count", + 10); + SetAllocatorDumpMetric(global_dump->process_dumps[0], + "devtools/durable_message_collectors", + "collector_count", 2); + SetAllocatorDumpMetric(global_dump->process_dumps[0], + "devtools/durable_message_collectors", "size", + 14 * kMiB); + + std::vector<AnyNodeWrapper> graph_nodes = CreateTestGraphNodes(); + auto emitter = + base::MakeRefCounted<ProcessMemoryMetricsEmitterFake>(test_ukm_recorder_); + emitter->ReceivedMemoryDump( + emitter->GetProcessToPageInfoMap(graph()), + memory_instrumentation::mojom::RequestOutcome::kSuccess, + GlobalMemoryDump::MoveFrom(std::move(global_dump))); + + histograms.ExpectBucketCount( + "Memory.Experimental.NetworkService2.DurableMessages." + "AggregateMemoryUsage", + 14, 1); + + histograms.ExpectBucketCount( + "Memory.Experimental.NetworkService2.Custom.DurableMessages." + "AggregateMessageCount", + 10, 1); + + histograms.ExpectBucketCount( + "Memory.Experimental.NetworkService2.Custom.DurableMessages." + "CollectorCount", + 2, 1); +} + TEST_F(ProcessMemoryMetricsEmitterTest, RendererAndTotalHistogramsAreRecorded) { // Take a snapshot of the current state of the histograms. base::HistogramTester histograms;
diff --git a/chrome/browser/optimization_guide/BUILD.gn b/chrome/browser/optimization_guide/BUILD.gn index a47c62f..f76c4a1d 100644 --- a/chrome/browser/optimization_guide/BUILD.gn +++ b/chrome/browser/optimization_guide/BUILD.gn
@@ -41,7 +41,10 @@ "//ui/webui", ] if (is_android) { - public_deps += [ "//chrome/browser/bookmarks/android" ] + public_deps += [ + "//chrome/browser/bookmarks/android", + "//chrome/browser/optimization_guide/android:jni_headers", + ] } else { sources += [ "private_ai_model_execution_fetcher.h" ] public_deps += [ "//components/private_ai" ]
diff --git a/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc b/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc index 6a95b236..fa94d03 100644 --- a/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc +++ b/chrome/browser/optimization_guide/android/optimization_guide_bridge.cc
@@ -202,13 +202,14 @@ OptimizationGuideBridge::~OptimizationGuideBridge() = default; -ScopedJavaLocalRef<jobject> OptimizationGuideBridge::GetJavaObject() { +ScopedJavaLocalRef<JOptimizationGuideBridge> +OptimizationGuideBridge::GetJavaObject() { JNIEnv* env = AttachCurrentThread(); if (!java_ref_) { java_ref_.Reset(Java_OptimizationGuideBridge_Constructor( env, reinterpret_cast<intptr_t>(this))); } - return ScopedJavaLocalRef<jobject>(java_ref_); + return ScopedJavaLocalRef<JOptimizationGuideBridge>(java_ref_); } void OptimizationGuideBridge::RegisterOptimizationTypes(
diff --git a/chrome/browser/optimization_guide/android/optimization_guide_bridge.h b/chrome/browser/optimization_guide/android/optimization_guide_bridge.h index 70d87c9..35c44fc 100644 --- a/chrome/browser/optimization_guide/android/optimization_guide_bridge.h +++ b/chrome/browser/optimization_guide/android/optimization_guide_bridge.h
@@ -12,6 +12,7 @@ #include "base/android/scoped_java_ref.h" #include "base/containers/flat_set.h" #include "base/memory/raw_ptr.h" +#include "chrome/browser/optimization_guide/android/jni_headers/OptimizationGuideBridge_shared_jni.h" #include "components/optimization_guide/proto/hints.pb.h" #include "components/optimization_guide/proto/push_notification.pb.h" #include "third_party/jni_zero/jni_zero.h" @@ -42,7 +43,7 @@ OptimizationGuideBridge& operator=(const OptimizationGuideBridge&) = delete; ~OptimizationGuideBridge(); - base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); + base::android::ScopedJavaLocalRef<JOptimizationGuideBridge> GetJavaObject(); void RegisterOptimizationTypes( JNIEnv* env, @@ -70,7 +71,7 @@ private: raw_ptr<OptimizationGuideKeyedService> optimization_guide_keyed_service_; - base::android::ScopedJavaGlobalRef<jobject> java_ref_; + base::android::ScopedJavaGlobalRef<JOptimizationGuideBridge> java_ref_; }; } // namespace android
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc index c337f73..f04b10d 100644 --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -92,6 +92,7 @@ #if BUILDFLAG(IS_ANDROID) #include "chrome/browser/commerce/price_tracking/android/price_tracking_notification_bridge.h" +#include "chrome/browser/optimization_guide/android/jni_headers/OptimizationGuideBridge_shared_jni.h" #include "chrome/browser/optimization_guide/android/optimization_guide_bridge.h" #include "chrome/browser/optimization_guide/android/optimization_guide_tab_url_provider_android.h" #else @@ -260,7 +261,8 @@ } #if BUILDFLAG(IS_ANDROID) -base::android::ScopedJavaLocalRef<jobject> +base::android::ScopedJavaLocalRef< + optimization_guide::android::JOptimizationGuideBridge> OptimizationGuideKeyedService::GetJavaObject() { if (!android_bridge_) { android_bridge_ =
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h index 2708da1..85e8aae9 100644 --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -33,6 +33,7 @@ #if BUILDFLAG(IS_ANDROID) #include "base/android/scoped_java_ref.h" #include "chrome/browser/bookmarks/android/bookmark_bridge.h" +#include "chrome/browser/optimization_guide/android/jni_headers/OptimizationGuideBridge_shared_jni.h" #endif // BUILDFLAG(IS_ANDROID) namespace content { @@ -111,7 +112,9 @@ ~OptimizationGuideKeyedService() override; #if BUILDFLAG(IS_ANDROID) - base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); + base::android::ScopedJavaLocalRef< + optimization_guide::android::JOptimizationGuideBridge> + GetJavaObject(); #endif // Constructs a ModelBrokerClient with remote fallback capability.
diff --git a/chrome/browser/passage_embeddings/BUILD.gn b/chrome/browser/passage_embeddings/BUILD.gn index f159fd1..abe12ce 100644 --- a/chrome/browser/passage_embeddings/BUILD.gn +++ b/chrome/browser/passage_embeddings/BUILD.gn
@@ -21,6 +21,7 @@ deps = [ "//chrome/browser:browser_process", + "//chrome/browser/history_embeddings", "//chrome/browser/optimization_guide", "//chrome/browser/page_content_annotations:extraction_service", "//chrome/browser/profiles",
diff --git a/chrome/browser/password_manager/actor_login/internal/BUILD.gn b/chrome/browser/password_manager/actor_login/internal/BUILD.gn index ff70b2f..c7a16c3 100644 --- a/chrome/browser/password_manager/actor_login/internal/BUILD.gn +++ b/chrome/browser/password_manager/actor_login/internal/BUILD.gn
@@ -9,6 +9,8 @@ "actor_login_delegate_impl.h", "actor_login_federated_credentials_fetcher.cc", "actor_login_federated_credentials_fetcher.h", + "actor_login_metrics_helper.cc", + "actor_login_metrics_helper.h", "actor_login_siwg_controller.cc", "actor_login_siwg_controller.h", "siwg_button_finder.cc", @@ -37,6 +39,7 @@ "//components/url_formatter", "//content/public/browser", "//content/public/common", + "//services/metrics/public/cpp:ukm_builders", "//third_party/re2", ] }
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.cc index e84b7a5..045e3bd 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.cc
@@ -17,6 +17,7 @@ #include "chrome/browser/glic/public/glic_keyed_service.h" #include "chrome/browser/glic/public/glic_keyed_service_factory.h" #include "chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.h" +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" #include "chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.h" #include "chrome/browser/translate/chrome_translate_client.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" @@ -115,6 +116,10 @@ return; } + metrics_helper_ = std::make_unique<ActorLoginMetricsHelper>( + GetWebContents().GetPrimaryMainFrame()->GetPageUkmSourceId()); + metrics_helper_->OnGetCredentialsStarted(); + PasswordManagerDriver* driver = driver_supplier_.Run(&GetWebContents()); CHECK(driver); @@ -128,18 +133,21 @@ fetchers.push_back(std::make_unique<ActorLoginPasswordCredentialsFetcher>( request_origin, client_, driver->GetPasswordManager(), mqls_logger)); - fetchers.push_back(std::make_unique<ActorLoginFederatedCredentialsFetcher>( - request_origin, - base::BindRepeating( - [](base::WeakPtr<content::WebContents> web_contents) - -> content::webid::IdentityCredentialSource* { - if (!web_contents) { - return nullptr; - } - return content::webid::IdentityCredentialSource::FromPage( - web_contents->GetPrimaryPage()); - }, - GetWebContents().GetWeakPtr()))); + auto federated_fetcher = + std::make_unique<ActorLoginFederatedCredentialsFetcher>( + request_origin, + base::BindRepeating( + [](base::WeakPtr<content::WebContents> web_contents) + -> content::webid::IdentityCredentialSource* { + if (!web_contents) { + return nullptr; + } + return content::webid::IdentityCredentialSource::FromPage( + web_contents->GetPrimaryPage()); + }, + GetWebContents().GetWeakPtr())); + federated_fetcher->SetMetricsHelper(metrics_helper_.get()); + fetchers.push_back(std::move(federated_fetcher)); get_credentials_helper_ = std::make_unique<ActorLoginGetCredentialsHelper>( std::move(fetchers), @@ -186,11 +194,19 @@ mqls_logger->SetDomainAndLanguage( ChromeTranslateClient::GetManagerFromWebContents(&GetWebContents()), origin.GetURL()); + + if (!metrics_helper_) { + metrics_helper_ = std::make_unique<ActorLoginMetricsHelper>( + GetWebContents().GetPrimaryMainFrame()->GetPageUkmSourceId()); + } + RecordAttemptLoginMetrics(credential); + if (credential.type == CredentialType::kFederated) { siwg_controller_ = std::make_unique<ActorLoginSiwgController>( &GetWebContents(), base::BindPostTaskToCurrentDefault(base::BindOnce( &ActorLoginDelegateImpl::OnAttemptLoginCompleted, weak_ptr_factory_.GetWeakPtr()))); + siwg_controller_->SetMetricsHelper(metrics_helper_.get()); siwg_controller_->StartFederatedLogin(credential); return; } @@ -249,6 +265,9 @@ CredentialsOrErrorReply callback, CredentialsOrError result) { get_credentials_helper_.reset(); + + RecordGetCredentialsMetricsAndResetHelper(result); + std::move(callback).Run(std::move(result)); } @@ -258,7 +277,59 @@ CHECK(pending_attempt_login_callback_); credential_filler_.reset(); siwg_controller_.reset(); + + // Record metrics by resetting the metrics helper. + metrics_helper_.reset(); + std::move(pending_attempt_login_callback_).Run(std::move(result)); } +void ActorLoginDelegateImpl::RecordGetCredentialsMetricsAndResetHelper( + const CredentialsOrError& result) { + if (!metrics_helper_) { + return; + } + + metrics_helper_->OnGetCredentialsCompleted(); + if (result.has_value()) { + bool has_password = false; + bool has_federated = false; + for (const auto& credential : result.value()) { + if (credential.type == CredentialType::kPassword) { + has_password = true; + } else if (credential.type == CredentialType::kFederated) { + has_federated = true; + } + } + ActorLoginAccountTypes types = ActorLoginAccountTypes::kNone; + if (has_password && has_federated) { + types = ActorLoginAccountTypes::kPasswordAndFederated; + } else if (has_password) { + types = ActorLoginAccountTypes::kPassword; + } else if (has_federated) { + types = ActorLoginAccountTypes::kFederated; + } + metrics_helper_->RecordAccountTypesShown(types); + metrics_helper_->RecordNumAccountsShown(result.value().size()); + } else { + metrics_helper_->RecordAccountTypesShown(ActorLoginAccountTypes::kNone); + } + + if (!result.has_value() || result.value().empty()) { + metrics_helper_.reset(); + } +} + +void ActorLoginDelegateImpl::RecordAttemptLoginMetrics( + const Credential& credential) { + CHECK(metrics_helper_); + metrics_helper_->OnAccountChosen(); + metrics_helper_->RecordSelectedAccountType( + credential.type == CredentialType::kFederated + ? ActorLoginSelectedAccountType::kFederated + : ActorLoginSelectedAccountType::kPassword); + metrics_helper_->RecordAccountAutoSelected( + credential.has_persistent_permission); +} + } // namespace actor_login
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.h b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.h index 5d5264fd..4b98f72c 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.h +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl.h
@@ -22,6 +22,7 @@ class ActorLoginCredentialFiller; class ActorLoginGetCredentialsHelper; +class ActorLoginMetricsHelper; // Delegate implementation, scoped to `WebContents` as its functionality is // intrinsically tied to a specific browser tab. @@ -84,6 +85,11 @@ void OnAttemptLoginCompleted( base::expected<LoginStatusResult, ActorLoginError> result); + // Helper methods for recording metrics. + void RecordGetCredentialsMetricsAndResetHelper( + const CredentialsOrError& result); + void RecordAttemptLoginMetrics(const Credential& credential); + // Store the pending callback. A non-null callback indicates an active // request. LoginStatusResultOrErrorReply pending_attempt_login_callback_; @@ -97,6 +103,13 @@ raw_ptr<password_manager::PasswordManagerClient> client_ = nullptr; + // Helper for recording Actor.Login metrics. The helper is created at the + // beginning of a `GetCredentials` or `AttemptLogin` request, and it's + // reset (recording metrics) when the request is completed. If credentials + // are found, it's kept alive until an `AttemptLogin` request is made or + // until the flow is considered finished. + std::unique_ptr<ActorLoginMetricsHelper> metrics_helper_; + // Fills credentials into a form. Scoped to one `AttemptLogin` request. std::unique_ptr<ActorLoginCredentialFiller> credential_filler_;
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl_unittest.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl_unittest.cc index d87b923..24bc899e 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl_unittest.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_delegate_impl_unittest.cc
@@ -7,7 +7,9 @@ #include "base/memory/raw_ptr.h" #include "base/run_loop.h" #include "base/test/bind.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/test_future.h" +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" #include "chrome/browser/ui/bookmarks/bookmark_bar_controller.h" #include "chrome/browser/ui/browser_window/test/mock_browser_window_interface.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -30,12 +32,15 @@ #include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/stub_password_manager_driver.h" #include "components/tabs/public/mock_tab_interface.h" +#include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/web_contents.h" +#include "content/public/browser/webid/federated_embedder_login_request.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/navigation_simulator.h" #include "content/public/test/test_renderer_host.h" #include "content/public/test/test_web_contents_factory.h" #include "content/public/test/web_contents_tester.h" +#include "services/metrics/public/cpp/ukm_builders.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/unowned_user_data/unowned_user_data_host.h" @@ -537,4 +542,206 @@ LoginStatusResult::kErrorDeviceReauthRequired); } +TEST_F(ActorLoginDelegateImplTest, RecordActorLoginMetricsNoCredentials) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kActorLogin); + base::HistogramTester histogram_tester; + ukm::TestAutoSetUkmRecorder ukm_recorder; + + SetUpActorCredentialFillerDeps(); + + base::test::TestFuture<CredentialsOrError> get_creds_future; + delegate_->GetCredentials(mqls_logger(), get_creds_future.GetCallback()); + ASSERT_TRUE(get_creds_future.Get().has_value()); + + histogram_tester.ExpectUniqueSample( + "Actor.Login.AccountTypesShown", + static_cast<int>(ActorLoginAccountTypes::kNone), 1); + histogram_tester.ExpectUniqueSample("Actor.Login.NumAccountsShown", 0, 1); + histogram_tester.ExpectTotalCount("Actor.Login.GetCredentialsLatency", 1); + + // UKM should be recorded because no credentials were returned and the + // delegate resets metrics_helper_ in that case. + auto entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(1u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kAccountTypesShownName, + static_cast<int>(ActorLoginAccountTypes::kNone)); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kNumAccountsShownName, 0); +} + +TEST_F(ActorLoginDelegateImplTest, + RecordActorLoginMetricsWithCredentialsNotShown) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kActorLogin); + base::HistogramTester histogram_tester; + ukm::TestAutoSetUkmRecorder ukm_recorder; + + GURL url = GURL(kTestUrl); + url::Origin origin = url::Origin::Create(url); + Credential credential = CreateTestCredential(u"username", url, origin); + + SetUpActorCredentialFillerDeps(); + + base::test::TestFuture<CredentialsOrError> future; + delegate_->GetCredentials(mqls_logger(), future.GetCallback()); + + ASSERT_TRUE(future.Get().has_value()); + + // Currently no sign in form means the account is not displayed. + histogram_tester.ExpectBucketCount( + "Actor.Login.AccountTypesShown", + static_cast<int>(ActorLoginAccountTypes::kNone), 1); + histogram_tester.ExpectBucketCount("Actor.Login.NumAccountsShown", 0, 1); + + // UKM should be recorded because no credentials were could be displayed. + auto entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(1u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kAccountTypesShownName, + static_cast<int>(ActorLoginAccountTypes::kNone)); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kNumAccountsShownName, 0); +} + +TEST_F(ActorLoginDelegateImplTest, RecordActorLoginMetricsOnAttemptLogin) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kActorLogin); + base::HistogramTester histogram_tester; + ukm::TestAutoSetUkmRecorder ukm_recorder; + + GURL url = GURL(kTestUrl); + url::Origin origin = url::Origin::Create(url); + Credential credential = CreateTestCredential(u"username", url, origin); + credential.type = CredentialType::kPassword; + credential.has_persistent_permission = true; + + SetUpActorCredentialFillerDeps(); + EXPECT_CALL(mock_form_cache_, GetFormManagers()).Times(1); + + base::test::TestFuture<LoginStatusResultOrError> future; + delegate_->AttemptLogin(credential, false, mqls_logger(), + base::TimeTicks::Now(), future.GetCallback()); + + ASSERT_TRUE(future.Get().has_value()); + + histogram_tester.ExpectUniqueSample( + "Actor.Login.SelectedAccountType", + static_cast<int>(ActorLoginSelectedAccountType::kPassword), 1); + histogram_tester.ExpectUniqueSample("Actor.Login.AccountAutoSelected", true, + 1); + + auto entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(1u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kSelectedAccountTypeName, + static_cast<int>(ActorLoginSelectedAccountType::kPassword)); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kAccountAutoSelectedName, true); +} + +TEST_F(ActorLoginDelegateImplTest, + RecordActorLoginMetricsOnFederatedAttemptLogin) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kActorLogin); + base::HistogramTester histogram_tester; + ukm::TestAutoSetUkmRecorder ukm_recorder; + + content::WebContents* test_contents = tab_strip_model_->GetWebContentsAt(0); + GURL url = GURL(kTestUrl); + url::Origin origin = url::Origin::Create(url); + Credential credential = CreateTestCredential(u"username", url, origin); + credential.type = CredentialType::kFederated; + credential.federation_detail = FederationDetail{ + .idp_origin = url::Origin::Create(GURL("https://accounts.google.com")), + .account_id = "12345"}; + + SetUpActorCredentialFillerDeps(); + + base::test::TestFuture<LoginStatusResultOrError> future; + delegate_->AttemptLogin(credential, false, mqls_logger(), + base::TimeTicks::Now(), future.GetCallback()); + + // Trigger completion for federated login. + auto* request = + content::webid::FederatedEmbedderLoginRequest::Get(test_contents); + ASSERT_TRUE(request); + request->OnFederatedResultReceived( + content::webid::FederatedLoginResult::kSuccess); + + ASSERT_TRUE(future.Get().has_value()); + + histogram_tester.ExpectUniqueSample( + "Actor.Login.SelectedAccountType", + static_cast<int>(ActorLoginSelectedAccountType::kFederated), 1); + + auto entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(1u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kSelectedAccountTypeName, + static_cast<int>(ActorLoginSelectedAccountType::kFederated)); +} + +TEST_F(ActorLoginDelegateImplTest, + RecordActorLoginMetricsGetCredentialsAndAttemptLogin) { + base::test::ScopedFeatureList feature_list( + password_manager::features::kActorLogin); + base::HistogramTester histogram_tester; + ukm::TestAutoSetUkmRecorder ukm_recorder; + + SetUpActorCredentialFillerDeps(); + EXPECT_CALL(mock_form_cache_, GetFormManagers()) + .WillRepeatedly(Return(base::span(form_managers_))); + + base::test::TestFuture<CredentialsOrError> get_creds_future; + delegate_->GetCredentials(mqls_logger(), get_creds_future.GetCallback()); + ASSERT_TRUE(get_creds_future.Get().has_value()); + + histogram_tester.ExpectUniqueSample( + "Actor.Login.AccountTypesShown", + static_cast<int>(ActorLoginAccountTypes::kNone), 1); + histogram_tester.ExpectUniqueSample("Actor.Login.NumAccountsShown", 0, 1); + histogram_tester.ExpectTotalCount("Actor.Login.GetCredentialsLatency", 1); + + // UKM should be recorded because no credentials were returned. + auto entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(1u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kAccountTypesShownName, + static_cast<int>(ActorLoginAccountTypes::kNone)); + ukm_recorder.ExpectEntryMetric( + entries[0], ukm::builders::Actor_Login::kNumAccountsShownName, 0); + + GURL url = GURL(kTestUrl); + url::Origin origin = url::Origin::Create(url); + Credential credential = CreateTestCredential(u"username", url, origin); + credential.has_persistent_permission = true; + + base::test::TestFuture<LoginStatusResultOrError> attempt_login_future; + delegate_->AttemptLogin(credential, false, mqls_logger(), + base::TimeTicks::Now(), + attempt_login_future.GetCallback()); + ASSERT_TRUE(attempt_login_future.Wait()); + + histogram_tester.ExpectUniqueSample( + "Actor.Login.SelectedAccountType", + static_cast<int>(ActorLoginSelectedAccountType::kPassword), 1); + histogram_tester.ExpectUniqueSample("Actor.Login.AccountAutoSelected", 1, 1); + + entries = + ukm_recorder.GetEntriesByName(ukm::builders::Actor_Login::kEntryName); + ASSERT_EQ(2u, entries.size()); + ukm_recorder.ExpectEntryMetric( + entries[1], ukm::builders::Actor_Login::kSelectedAccountTypeName, + static_cast<int>(ActorLoginSelectedAccountType::kPassword)); + ukm_recorder.ExpectEntryMetric( + entries[1], ukm::builders::Actor_Login::kAccountAutoSelectedName, true); +} + } // namespace actor_login
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc index f81fd94..84eae9f3 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc
@@ -51,13 +51,24 @@ return; } + if (metrics_helper_) { + metrics_helper_->RecordFederatedHangingFedCmRequestExists( + source->HasPendingRequest()); + } + std::vector<GURL> supported_idps = {GURL(kSupportedIdentityProvider)}; + source->GetIdentityCredentialSuggestions( supported_idps, base::BindOnce(&ActorLoginFederatedCredentialsFetcher:: OnGetIdentityCredentialSuggestions, weak_ptr_factory_.GetWeakPtr())); } +void ActorLoginFederatedCredentialsFetcher::SetMetricsHelper( + ActorLoginMetricsHelper* metrics_helper) { + metrics_helper_ = metrics_helper; +} + void ActorLoginFederatedCredentialsFetcher::OnGetIdentityCredentialSuggestions( const std::optional< std::vector<scoped_refptr<content::IdentityRequestAccount>>>&
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.h b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.h index 12e5780..275acf75 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.h +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.h
@@ -10,6 +10,7 @@ #include "base/functional/callback.h" #include "base/memory/weak_ptr.h" +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" #include "components/password_manager/core/browser/actor_login/actor_login_types.h" #include "components/password_manager/core/browser/actor_login/internal/actor_login_credentials_fetcher.h" #include "content/public/browser/webid/identity_request_account.h" @@ -35,6 +36,8 @@ // ActorLoginCredentialsFetcher: void Fetch(FetchResultCallback callback) override; + void SetMetricsHelper(ActorLoginMetricsHelper* metrics_helper); + private: void OnGetIdentityCredentialSuggestions( const std::optional< @@ -52,6 +55,9 @@ // TODO(crbug.com/480004512): Check the origin of the source before using it. IdentityCredentialSourceCallback get_source_callback_; + // Owned by ActorLoginDelegateImpl. + raw_ptr<ActorLoginMetricsHelper> metrics_helper_ = nullptr; + base::WeakPtrFactory<ActorLoginFederatedCredentialsFetcher> weak_ptr_factory_{ this}; };
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc index ca78f8c..368a91c 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc
@@ -48,6 +48,7 @@ const std::string&, base::RepeatingCallback<void(content::webid::FederatedLoginResult)>), (override)); + MOCK_METHOD(bool, HasPendingRequest, (), (override)); }; scoped_refptr<content::IdentityRequestAccount> CreateTestIdentityRequestAccount(
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.cc new file mode 100644 index 0000000..26f91c4 --- /dev/null +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.cc
@@ -0,0 +1,108 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" + +#include "base/metrics/histogram_functions.h" +#include "services/metrics/public/cpp/metrics_utils.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_recorder.h" + +namespace actor_login { + +ActorLoginMetricsHelper::ActorLoginMetricsHelper(ukm::SourceId source_id) + : source_id_(source_id), builder_(source_id) {} + +ActorLoginMetricsHelper::~ActorLoginMetricsHelper() { + RecordUkm(); +} + +void ActorLoginMetricsHelper::RecordAccountTypesShown( + ActorLoginAccountTypes types) { + builder_.SetAccountTypesShown(static_cast<int64_t>(types)); + base::UmaHistogramEnumeration("Actor.Login.AccountTypesShown", types); +} + +void ActorLoginMetricsHelper::RecordNumAccountsShown(int count) { + builder_.SetNumAccountsShown(count); + base::UmaHistogramExactLinear("Actor.Login.NumAccountsShown", count, + /*exclusive_max=*/4); +} + +void ActorLoginMetricsHelper::RecordAccountAutoSelected(bool auto_selected) { + if (auto_selected) { + builder_.SetAccountAutoSelected(true); + base::UmaHistogramBoolean("Actor.Login.AccountAutoSelected", true); + } +} + +void ActorLoginMetricsHelper::RecordSelectedAccountType( + ActorLoginSelectedAccountType type) { + builder_.SetSelectedAccountType(static_cast<int64_t>(type)); + base::UmaHistogramEnumeration("Actor.Login.SelectedAccountType", type); +} + +void ActorLoginMetricsHelper::OnGetCredentialsStarted() { + get_credentials_start_time_ = base::TimeTicks::Now(); +} + +void ActorLoginMetricsHelper::OnGetCredentialsCompleted() { + get_credentials_completed_time_ = base::TimeTicks::Now(); + CHECK(!get_credentials_start_time_.is_null()); + base::TimeDelta duration = + get_credentials_completed_time_ - get_credentials_start_time_; + base::UmaHistogramMediumTimes("Actor.Login.GetCredentialsLatency", duration); + builder_.SetGetCredentialsLatency(duration.InMilliseconds()); +} + +void ActorLoginMetricsHelper::OnAccountChosen() { + account_chosen_time_ = base::TimeTicks::Now(); + // `get_credentials_completed_time_` is null if `OnGetCredentialsCompleted()` + // was never called (e.g. if the metrics helper was created late in the flow, + // or if credentials were never fetched). + if (!get_credentials_completed_time_.is_null()) { + base::TimeDelta duration = + account_chosen_time_ - get_credentials_completed_time_; + base::UmaHistogramMediumTimes( + "Actor.Login.GetCredentialsCompletedToAccountChosen", duration); + builder_.SetGetCredentialsCompletedToAccountChosen( + duration.InMilliseconds()); + } +} + +void ActorLoginMetricsHelper::RecordFederatedContinuationShown() { + builder_.SetFederatedContinuationShown(true); + base::UmaHistogramBoolean("Actor.Login.Federated.ContinuationShown", true); +} + +void ActorLoginMetricsHelper::RecordFederatedLoginResult( + content::webid::FederatedLoginResult result) { + if (result == content::webid::FederatedLoginResult::kContinuation) { + return; + } + builder_.SetFederatedLoginResult(static_cast<int64_t>(result)); + base::UmaHistogramEnumeration( + "Actor.Login.Federated.LoginResult", + static_cast<ActorLoginFederatedLoginResult>(result)); +} + +void ActorLoginMetricsHelper::RecordFederatedHangingFedCmRequestExists( + bool exists) { + if (exists) { + builder_.SetFederatedHangingFedCmRequestExists(true); + base::UmaHistogramBoolean("Actor.Login.Federated.HangingFedCmRequestExists", + true); + } +} + +void ActorLoginMetricsHelper::RecordUkm() { + if (ukm_recorded_ || source_id_ == ukm::kInvalidSourceId) { + return; + } + ukm_recorded_ = true; + + builder_.Record(ukm::UkmRecorder::Get()); +} + +} // namespace actor_login
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h b/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h new file mode 100644 index 0000000..6131cc3 --- /dev/null +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h
@@ -0,0 +1,82 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_ACTOR_LOGIN_INTERNAL_ACTOR_LOGIN_METRICS_HELPER_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_ACTOR_LOGIN_INTERNAL_ACTOR_LOGIN_METRICS_HELPER_H_ + +#include <optional> + +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h" +#include "content/public/browser/webid/identity_credential_source.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_source_id.h" + +namespace actor_login { + +// Helper class for recording Actor.Login metrics such as the number and types +// of accounts shown to the user in an actor login flow. Metrics are recorded in +// the destructor, using the data collected during the lifetime of this object. +class ActorLoginMetricsHelper { + public: + explicit ActorLoginMetricsHelper(ukm::SourceId source_id); + ~ActorLoginMetricsHelper(); + + ActorLoginMetricsHelper(const ActorLoginMetricsHelper&) = delete; + ActorLoginMetricsHelper& operator=(const ActorLoginMetricsHelper&) = delete; + + // Records the types of accounts (password, federated, both) shown to the + // user. + void RecordAccountTypesShown(ActorLoginAccountTypes types); + + // Records the number of accounts shown to the user. + void RecordNumAccountsShown(int count); + + // Records whether an account was automatically selected due to previously + // having been selected as 'always allow'. + void RecordAccountAutoSelected(bool auto_selected); + + // Records the type of account selected by the user (password, federated). + void RecordSelectedAccountType(ActorLoginSelectedAccountType type); + + // Marks the start of a "get credentials" request. Computes the time at which + // the request starts. + void OnGetCredentialsStarted(); + + // Marks that a "get credentials" request has completed. Records the duration + // of the request (end - start). + void OnGetCredentialsCompleted(); + + // Marks that an account has been chosen by the user. Records the time from + // request start to when the account was chosen. + void OnAccountChosen(); + + // Records that a FedCM continuation API dialog was shown. + void RecordFederatedContinuationShown(); + + // Records the result of a federated actor login request. + void RecordFederatedLoginResult(content::webid::FederatedLoginResult result); + + // Records whether a hanging FedCM request exists during an actor login + // request. + void RecordFederatedHangingFedCmRequestExists(bool exists); + + private: + // Metrics recording should only happen during destruction, so this method is + // private. + void RecordUkm(); + + base::TimeTicks get_credentials_start_time_; + base::TimeTicks get_credentials_completed_time_; + base::TimeTicks account_chosen_time_; + + ukm::SourceId source_id_ = ukm::kInvalidSourceId; + ukm::builders::Actor_Login builder_; + bool ukm_recorded_ = false; +}; + +} // namespace actor_login + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_ACTOR_LOGIN_INTERNAL_ACTOR_LOGIN_METRICS_HELPER_H_
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.cc index c3868d1..9629cd9 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.cc
@@ -104,6 +104,10 @@ // There may be an existing FedCM dialog; if so, select an account in that // dialog instead of clicking the signin button. + if (metrics_helper_) { + metrics_helper_->RecordFederatedHangingFedCmRequestExists( + source->HasPendingRequest()); + } if (!source->SelectAccount(credential.federation_detail->idp_origin, credential.federation_detail->account_id)) { ClickSiwgButton(); @@ -119,8 +123,21 @@ weak_ptr_factory_.GetWeakPtr())); } +void ActorLoginSiwgController::SetMetricsHelper( + ActorLoginMetricsHelper* metrics_helper) { + metrics_helper_ = metrics_helper; +} + void ActorLoginSiwgController::OnFederatedLoginResultReceived( content::webid::FederatedLoginResult result) { + if (metrics_helper_) { + if (result == content::webid::FederatedLoginResult::kContinuation) { + metrics_helper_->RecordFederatedContinuationShown(); + } else { + metrics_helper_->RecordFederatedLoginResult(result); + } + } + if (!on_finished_callback_) { return; }
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.h b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.h index da94ec2..5735967 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.h +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller.h
@@ -11,6 +11,7 @@ #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" #include "chrome/browser/password_manager/actor_login/internal/siwg_button_finder.h" #include "chrome/common/actor.mojom-forward.h" #include "chrome/common/chrome_render_frame.mojom.h" @@ -69,6 +70,8 @@ // clicks the first one found. void ClickSiwgButton(); + void SetMetricsHelper(ActorLoginMetricsHelper* metrics_helper); + private: void OnPageContentReceived( optimization_guide::AIPageContentResultOrError content); @@ -98,6 +101,9 @@ std::unique_ptr<SiwgButtonFinder> siwg_finder_; LoginStatusResultOrErrorReply on_finished_callback_; + // Owned by ActorLoginDelegateImpl. + raw_ptr<ActorLoginMetricsHelper> metrics_helper_ = nullptr; + // Remote for the `ChromeRenderFrame` in the local root of the frame where the // SiwG button was found. Keeps the remote alive for the duration of the click // action.
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller_unittest.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller_unittest.cc index 53a1166..76eb66f 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller_unittest.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_siwg_controller_unittest.cc
@@ -12,11 +12,14 @@ #include "base/functional/bind.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/bind.h" #include "base/test/gmock_callback_support.h" #include "base/test/gmock_expected_support.h" #include "base/test/gmock_move_support.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" #include "chrome/browser/autofill/mock_autofill_agent.h" +#include "chrome/browser/password_manager/actor_login/internal/actor_login_metrics_helper.h" #include "chrome/common/actor.mojom.h" #include "chrome/common/chrome_render_frame.mojom.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" @@ -140,6 +143,7 @@ void TearDown() override { ChromeRenderViewHostTestHarness::TearDown(); } protected: + base::HistogramTester histogram_tester_; autofill::TestAutofillClientInjector<autofill::TestContentAutofillClient> autofill_client_injector_; autofill::TestAutofillDriverInjector<autofill::ContentAutofillDriver> @@ -160,12 +164,18 @@ base::RunLoop run_loop; base::MockCallback<LoginStatusResultOrErrorReply> finished_callback; optimization_guide::OnAIPageContentDone page_content_callback; + + auto metrics_helper_owned = + std::make_unique<ActorLoginMetricsHelper>(ukm::kInvalidSourceId); + ActorLoginSiwgController controller( web_contents(), base::BindRepeating(&ActorLoginSiwgControllerTest::SaveCallback, &page_content_callback), finished_callback.Get()); + controller.SetMetricsHelper(metrics_helper_owned.get()); + Credential credential; credential.federation_detail = FederationDetail(); controller.StartFederatedLogin(credential);
diff --git a/chrome/browser/privacy_sandbox/BUILD.gn b/chrome/browser/privacy_sandbox/BUILD.gn index 00a750f..850b16d 100644 --- a/chrome/browser/privacy_sandbox/BUILD.gn +++ b/chrome/browser/privacy_sandbox/BUILD.gn
@@ -124,6 +124,29 @@ "//third_party/blink/public/common:headers", ] } + + source_set("interest_group_permissions_browser_test") { + testonly = true + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + sources = [ "interest_group_permissions_browsertest.cc" ] + deps = [ + "//base/test:test_support", + "//chrome/browser/content_settings:content_settings_factory", + "//chrome/browser/profiles:profile", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//components/content_settings/core/browser:cookie_settings", + "//components/content_settings/core/common", + "//components/prefs", + "//components/privacy_sandbox:privacy_sandbox_settings_headers", + "//components/privacy_sandbox/privacy_sandbox_attestations", + "//content/public/common", + "//content/test:test_support", + "//net:test_support", + "//services/network/public/cpp", + "//third_party/blink/public/common:headers", + ] + } } else { source_set("attestations_component_installer_android_browsertest") { testonly = true @@ -156,6 +179,7 @@ if (!is_android) { deps += [ ":attestations_browser_test", + ":interest_group_permissions_browser_test", ":notice_confirmation_browser_test", ":settings_browser_test", ]
diff --git a/chrome/browser/interest_group/interest_group_permissions_browsertest.cc b/chrome/browser/privacy_sandbox/interest_group_permissions_browsertest.cc similarity index 100% rename from chrome/browser/interest_group/interest_group_permissions_browsertest.cc rename to chrome/browser/privacy_sandbox/interest_group_permissions_browsertest.cc
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn index 1cf2d7fa..28194de 100644 --- a/chrome/browser/profiles/BUILD.gn +++ b/chrome/browser/profiles/BUILD.gn
@@ -74,7 +74,10 @@ "refcounted_profile_keyed_service_factory.h", ] - public_deps = [ "//base" ] + public_deps = [ + "//base", + "//components/media_router/common", + ] # Cannot depend on //chrome/browser:browser. deps = [ @@ -85,7 +88,6 @@ "//components/keyed_service/content", "//components/language/core/browser", "//components/live_caption:constants", - "//components/media_router/common", "//components/pref_registry", "//components/prefs", "//components/profile_metrics", @@ -315,6 +317,7 @@ "//chrome/browser/glic", "//chrome/browser/heavy_ad_intervention", "//chrome/browser/history", + "//chrome/browser/history_embeddings", "//chrome/browser/k_anonymity_service", "//chrome/browser/language_detection", "//chrome/browser/lookalikes",
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc index e23308a5..7c999b9 100644 --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -79,7 +79,7 @@ #include "chrome/browser/enterprise/connectors/connectors_service.h" #include "chrome/browser/enterprise/connectors/reporting/browser_crash_event_router.h" #include "chrome/browser/enterprise/connectors/reporting/reporting_event_router_factory.h" -#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h" +#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_factory.h" #include "chrome/browser/enterprise/identifiers/profile_id_service_factory.h" #include "chrome/browser/enterprise/remote_commands/user_remote_commands_service_factory.h" #include "chrome/browser/enterprise/reporting/cloud_profile_reporting_service_factory.h"
diff --git a/chrome/browser/resources/contextual_tasks/app.css b/chrome/browser/resources/contextual_tasks/app.css index 3fe9f91..0afdd12 100644 --- a/chrome/browser/resources/contextual_tasks/app.css +++ b/chrome/browser/resources/contextual_tasks/app.css
@@ -282,10 +282,12 @@ top: 76px; } +:host([is-loading-zero-state-from-results_]) #threadFrame, :host([is-error-page-visible_]) #threadFrame, :host([is-error-page-visible_]) #composebox, :host([is-error-page-visible_]) #flexCenterContainer, :host([is-ghost-loader-visible_]) #threadFrame, +:host([is-zero-state_]) #ghostLoader, :host([is-shown-in-tab_]) #ghostLoader, :host(:not([is-ghost-loader-visible_])) #ghostLoader, :host([is-error-page-visible_]) #ghostLoader {
diff --git a/chrome/browser/resources/contextual_tasks/app.ts b/chrome/browser/resources/contextual_tasks/app.ts index 664a344..1288e260 100644 --- a/chrome/browser/resources/contextual_tasks/app.ts +++ b/chrome/browser/resources/contextual_tasks/app.ts
@@ -195,6 +195,10 @@ isInputLocked_: { type: Boolean, }, + isLoadingZeroStateFromResults_: { + type: Boolean, + reflect: true, + }, // <if expr="not is_android"> forcedComposeboxBounds_: {type: Object}, // </if> @@ -236,6 +240,7 @@ loadTimeData.getBoolean('enableNativeZeroStateSuggestions'); protected accessor isGhostLoaderVisible_: boolean = false; protected accessor isInputLocked_: boolean = false; + protected accessor isLoadingZeroStateFromResults_: boolean = false; // The bounds of the composebox that are forced by the embedded page. These // bounds are relative to the <webview> and not the viewport. // <if expr="not is_android"> @@ -596,6 +601,7 @@ // Set frame loading to true initially to avoid race conditions. this.isFrameLoading = true; const wasAiPage = this.isAiPage_; + const wasZeroState = this.isZeroState_; const {isAiPage} = await this.browserProxy_.handler.isAiPage(ev.url); const {isZeroState} = await this.browserProxy_.handler.isZeroState(ev.url); @@ -613,6 +619,10 @@ this.playZeroStateAnimations_(); } + if (!wasZeroState && isZeroState) { + this.isLoadingZeroStateFromResults_ = true; + } + if (!isAiPage) { // If this is not an AI page, show the ghost loader. // Update the isAiPage_ property so the ghost loader doesn't jump when @@ -650,12 +660,14 @@ private onThreadFrameContentLoad() { this.isFrameLoading = false; + this.isLoadingZeroStateFromResults_ = false; this.setIsGhostLoaderVisible(false); this.updateBasicModeAfterNavigation(); } private onThreadFrameLoadAbort() { this.isFrameLoading = false; + this.isLoadingZeroStateFromResults_ = false; this.setIsGhostLoaderVisible(false); this.updateBasicModeAfterNavigation(); }
diff --git a/chrome/browser/resources/contextual_tasks/composebox.html.ts b/chrome/browser/resources/contextual_tasks/composebox.html.ts index 69eede2..780db6a 100644 --- a/chrome/browser/resources/contextual_tasks/composebox.html.ts +++ b/chrome/browser/resources/contextual_tasks/composebox.html.ts
@@ -27,7 +27,10 @@ .result="${this.zeroStateSuggestions_}" .maxSuggestions="${5}" .overrideClampLineNum="${3}" - ?hidden="${!this.showSuggestions_}"> + .selectedMatchIndex="${this.selectedMatchIndex_}" + ?hidden="${!this.showSuggestions_}" + @match-focusin="${this.onMatchFocusin_}" + @keydown="${this.onDropdownKeydown_}"> </cr-composebox-dropdown> ${this.showSuggestionsActivityLink_ && this.showSuggestions_ ? html` <div id="suggestionActivity"> @@ -77,7 +80,10 @@ .result="${this.zeroStateSuggestions_}" .maxSuggestions="${5}" .overrideClampLineNum="${3}" - ?hidden="${!this.showSuggestions_}"> + .selectedMatchIndex="${this.selectedMatchIndex_}" + ?hidden="${!this.showSuggestions_}" + @match-focusin="${this.onMatchFocusin_}" + @keydown="${this.onDropdownKeydown_}"> </cr-composebox-dropdown> ${this.showSuggestionsActivityLink_ && this.showSuggestions_ ? html` <div id="suggestionActivity">
diff --git a/chrome/browser/resources/contextual_tasks/composebox.ts b/chrome/browser/resources/contextual_tasks/composebox.ts index 890eaa60..c5b3198 100644 --- a/chrome/browser/resources/contextual_tasks/composebox.ts +++ b/chrome/browser/resources/contextual_tasks/composebox.ts
@@ -16,7 +16,7 @@ import {assert} from '//resources/js/assert.js'; import {EventTracker} from '//resources/js/event_tracker.js'; import {loadTimeData} from '//resources/js/load_time_data.js'; -import type {AutocompleteMatch, AutocompleteResult, PageCallbackRouter as SearchboxPageCallbackRouter} from '//resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; +import type {AutocompleteMatch, AutocompleteResult, PageCallbackRouter as SearchboxPageCallbackRouter, PageHandlerRemote as SearchboxPageHandlerRemote} from '//resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; import {ToolMode} from '//resources/mojo/components/omnibox/composebox/composebox_query.mojom-webui.js'; import type {UnguessableToken} from '//resources/mojo/mojo/public/mojom/base/unguessable_token.mojom-webui.js'; import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js'; @@ -129,6 +129,7 @@ type: Boolean, reflect: true, }, + selectedMatchIndex_: {type: Number}, }; } @@ -161,6 +162,8 @@ loadTimeData.getBoolean('showOnboardingTooltip'); protected accessor activeToolMode_: ToolMode = ToolMode.kUnspecified; protected accessor showSuggestionsActivityLink_: boolean = false; + protected accessor selectedMatchIndex_: number = -1; + protected searchboxHandler_: SearchboxPageHandlerRemote; private eventTracker_: EventTracker = new EventTracker(); private pageHandler_: PageHandlerRemote; private searchboxCallbackRouter_: SearchboxPageCallbackRouter; @@ -184,6 +187,7 @@ this.pageHandler_ = ComposeboxProxyImpl.getInstance().handler; this.searchboxCallbackRouter_ = ComposeboxProxyImpl.getInstance().searchboxCallbackRouter; + this.searchboxHandler_ = ComposeboxProxyImpl.getInstance().searchboxHandler; } override connectedCallback() { @@ -380,6 +384,41 @@ this.zeroStateSuggestions_ = e.detail; } + // Handle keyboard events on the suggestions dropdown. + protected onDropdownKeydown_(e: KeyboardEvent) { + if (e.key === 'Enter') { + this.navigateToMatch_(this.selectedMatchIndex_); + e.preventDefault(); + e.stopPropagation(); + } + } + + private updateSelection_(index: number) { + this.selectedMatchIndex_ = index; + } + + protected onMatchFocusin_(e: CustomEvent<{index: number}>) { + this.updateSelection_(e.detail.index); + } + + private navigateToMatch_(index: number) { + const match = this.zeroStateSuggestions_?.matches[index]; + + if (match) { + this.searchboxHandler_.openAutocompleteMatch( + /*line=*/ index, + /*url=*/ match.destinationUrl, + /*areMatchesShowing=*/ true, + /*mouseButton=*/ 0, + /*altKey=*/ false, + /*ctrlKey=*/ false, + /*metaKey=*/ false, + /*shiftKey=*/ false); + } + this.clearInputAndFocus(/* querySubmitted= */ true); + this.selectedMatchIndex_ = -1; + } + private clearTooltipImpressionTimer_() { if (this.tooltipImpressionTimer_) { clearTimeout(this.tooltipImpressionTimer_);
diff --git a/chrome/browser/resources/lens/overlay/overlay_border_glow.ts b/chrome/browser/resources/lens/overlay/overlay_border_glow.ts index 61e67c95..5ad0cff 100644 --- a/chrome/browser/resources/lens/overlay/overlay_border_glow.ts +++ b/chrome/browser/resources/lens/overlay/overlay_border_glow.ts
@@ -48,10 +48,14 @@ protected getGradientColorStyles(): string { const styles: string[] = [ - `--gradient-blue: ${GLIF_HEX_COLORS.blue}`, - `--gradient-red: ${GLIF_HEX_COLORS.red}`, - `--gradient-yellow: ${GLIF_HEX_COLORS.yellow}`, - `--gradient-green: ${GLIF_HEX_COLORS.green}`, + `--gradient-blue: var(--overlay-border-glow-color-1, ${ + GLIF_HEX_COLORS.blue})`, + `--gradient-red: var(--overlay-border-glow-color-2, ${ + GLIF_HEX_COLORS.red})`, + `--gradient-yellow: var(--overlay-border-glow-color-3, ${ + GLIF_HEX_COLORS.yellow})`, + `--gradient-green: var(--overlay-border-glow-color-4, ${ + GLIF_HEX_COLORS.green})`, ]; return styles.join(';'); }
diff --git a/chrome/browser/resources/lens/overlay/post_selection_renderer.html b/chrome/browser/resources/lens/overlay/post_selection_renderer.html index cdc952d..9d917a3 100644 --- a/chrome/browser/resources/lens/overlay/post_selection_renderer.html +++ b/chrome/browser/resources/lens/overlay/post_selection_renderer.html
@@ -62,7 +62,8 @@ transition: none; } - :host([region-selected-glow-enabled]) #postSelection:before { + :host([region-selected-glow-enabled]:not([background-gradient-hidden])) + #postSelection:before { animation: postSelectionGlowFadeIn 120ms cubic-bezier(0, 0, 0, 1) forwards; background: conic-gradient( from 90deg at center,
diff --git a/chrome/browser/resources/lens/overlay/post_selection_renderer.ts b/chrome/browser/resources/lens/overlay/post_selection_renderer.ts index de208df..57bb45c 100644 --- a/chrome/browser/resources/lens/overlay/post_selection_renderer.ts +++ b/chrome/browser/resources/lens/overlay/post_selection_renderer.ts
@@ -135,6 +135,11 @@ value: () => loadTimeData.getBoolean('cornerSlidersEnabled'), reflectToAttribute: true, }, + backgroundGradientHidden: { + type: Boolean, + reflectToAttribute: true, + value: false, + }, }; } @@ -157,6 +162,8 @@ // Whether the region selected glow is enabled via feature flag. declare private regionSelectedGlowEnabled: boolean; declare private selectionOverlayRect: DOMRect; + // Whether the background gradient should be hidden. + declare private backgroundGradientHidden: boolean; private context: CanvasRenderingContext2D; // Listener IDs for events tracked from the browser.
diff --git a/chrome/browser/resources/lens/overlay/region_selection.ts b/chrome/browser/resources/lens/overlay/region_selection.ts index 78505bb..fe3181ba 100644 --- a/chrome/browser/resources/lens/overlay/region_selection.ts +++ b/chrome/browser/resources/lens/overlay/region_selection.ts
@@ -109,6 +109,26 @@ value: getFallbackTheme, }, selectionOverlayRect: Object, + regionStrokeColor1: { + type: String, + value: GLIF_HEX_COLORS.blue, + }, + regionStrokeColor2: { + type: String, + value: GLIF_HEX_COLORS.blue, + }, + regionStrokeColor3: { + type: String, + value: GLIF_HEX_COLORS.red, + }, + regionStrokeColor4: { + type: String, + value: GLIF_HEX_COLORS.yellow, + }, + regionStrokeColor5: { + type: String, + value: GLIF_HEX_COLORS.green, + }, }; } @@ -136,6 +156,13 @@ // Whether keyboard selection should be displayed. declare private displayKeyboardSelection: boolean; + // The colors used for the gradient stroke of the region selection. + declare private regionStrokeColor1: string; + declare private regionStrokeColor2: string; + declare private regionStrokeColor3: string; + declare private regionStrokeColor4: string; + declare private regionStrokeColor5: string; + private baseHandler: SelectionOverlayBaseHandler = SelectionOverlayBaseHandler.getInstance(); @@ -311,11 +338,11 @@ if (this.gradientRegionStrokeEnabled) { // Use AIM style GLIF color gradient. gradient = this.context.createConicGradient(0, centerX, centerY); - gradient.addColorStop(0, GLIF_HEX_COLORS.blue); - gradient.addColorStop(0.45, GLIF_HEX_COLORS.blue); - gradient.addColorStop(0.6, GLIF_HEX_COLORS.red); - gradient.addColorStop(0.76, GLIF_HEX_COLORS.yellow); - gradient.addColorStop(0.92, GLIF_HEX_COLORS.green); + gradient.addColorStop(0, this.regionStrokeColor1); + gradient.addColorStop(0.45, this.regionStrokeColor2); + gradient.addColorStop(0.6, this.regionStrokeColor3); + gradient.addColorStop(0.76, this.regionStrokeColor4); + gradient.addColorStop(0.92, this.regionStrokeColor5); } else if (this.whiteRegionStrokeEnabled) { // Use white gradient. gradient = this.context.createLinearGradient(
diff --git a/chrome/browser/resources/new_tab_page/action_chips/action_chips.html.ts b/chrome/browser/resources/new_tab_page/action_chips/action_chips.html.ts index 762f1d8a..a577afa 100644 --- a/chrome/browser/resources/new_tab_page/action_chips/action_chips.html.ts +++ b/chrome/browser/resources/new_tab_page/action_chips/action_chips.html.ts
@@ -25,7 +25,7 @@ this.isDeepDiveChip_(chip) ? 'deep-dive-chip' : ''}" data-index="${index}" title="${this.getChipTitle_(chip)}" - @click="${this.handleClick_}"> + @click="${this.onClick_}"> <div class="action-chip-icon-container ${ this.getAdditionalIconClasses_(chip)}"> ${ @@ -50,7 +50,7 @@ ${this.showDismissalUI_ ? html` <cr-icon-button class="chip-remove-button" data-index="${index}" - @click="${this.removeChip_}"> + @click="${this.onRemoveClick_}"> </cr-icon-button> ` : nothing} </button>
diff --git a/chrome/browser/resources/new_tab_page/action_chips/action_chips.ts b/chrome/browser/resources/new_tab_page/action_chips/action_chips.ts index cffdfa0e..74096d9 100644 --- a/chrome/browser/resources/new_tab_page/action_chips/action_chips.ts +++ b/chrome/browser/resources/new_tab_page/action_chips/action_chips.ts
@@ -168,7 +168,7 @@ } } - protected handleClick_(e: Event): void { + protected onClick_(e: Event): void { const index = Number((e.currentTarget as HTMLElement).dataset['index']); const chip = this.actionChips_[index]!; switch (chip.suggestTemplateInfo.typeIcon) { @@ -193,7 +193,7 @@ } } - protected removeChip_(e: MouseEvent) { + protected onRemoveClick_(e: MouseEvent) { e.preventDefault(); e.stopPropagation(); const index = Number((e.currentTarget as HTMLElement).dataset['index']);
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html index 7c9046d..b387311 100644 --- a/chrome/browser/resources/new_tab_page/app.html +++ b/chrome/browser/resources/new_tab_page/app.html
@@ -41,12 +41,12 @@ ?hidden="${!this.showScrim_}"></div> ` : ''} <div id="searchboxContainer" - @focusin="${this.onSearchboxContainerFocusIn_}" - @focusout="${this.onSearchboxContainerFocusOut_}"> + @focusin="${this.onSearchboxContainerFocusin_}" + @focusout="${this.onSearchboxContainerFocusout_}"> <cr-searchbox id="searchbox" ?is-dark="${this.isThemeDark_()}" placeholder-text="$i18n{searchBoxPlaceholder}" ?color-source-is-baseline="${this.colorSourceIsBaseline}" - @open-composebox="${this.openComposebox_}" + @open-composebox="${this.onOpenComposebox_}" @open-lens-search="${this.onOpenLensSearch_}" @open-voice-search="${this.onOpenVoiceSearch_}" ?shown="${this.realboxShown_}" ?had-secondary-side="${this.realboxHadSecondarySide}" @@ -69,7 +69,7 @@ ?searchbox-next-enabled="${this.ntpRealboxNextEnabled_}" ?disable-caret-color-animation="${!this.caretAnimationsEnabled_}" @composebox-initialized="${this.onComposeboxInitialized_}" - @close-composebox="${this.closeComposebox_}" + @close-composebox="${this.onCloseComposebox_}" @context-menu-entrypoint-click="${this.onContextMenuEntrypointClick_}" @voice-search-action="${this.onComposeVoiceSearchAction_}" searchbox-layout-mode="${this.realboxLayoutMode_}"> @@ -91,7 +91,7 @@ <ntp-action-chips ?show-background="${this.showActionChipsBackground_()}" ?reduced-motion-preferred="${this.reducedMotionPreferred_}" - @action-chip-click="${this.openComposebox_}" + @action-chip-click="${this.onActionChipClick_}" @action-chips-retrieval-state-changed="${this.onActionChipsRetrievalStateChanged_}"> </ntp-action-chips> ` : ''} @@ -109,12 +109,12 @@ single-row reflow-on-overflow ?expandable-tiles-enabled="${this.ntpNextFeaturesEnabled_}" max-tiles-before-show-more="${this.maxTilesBeforeShowMore_}" - @most-visited-auto-removed="${this.showAutoRemovedToast_}"> + @most-visited-auto-removed="${this.onMostVisitedAutoRemoved_}"> </cr-most-visited> ` : ''} ${this.middleSlotPromoEnabled_ ? html` <ntp-middle-slot-promo - @ntp-middle-slot-promo-loaded="${this.onMiddleSlotPromoLoaded_}" + @ntp-middle-slot-promo-loaded="${this.onNtpMiddleSlotPromoLoaded_}" ?hidden="${!this.promoAndModulesLoaded_}"> </ntp-middle-slot-promo> ` : ''} @@ -125,7 +125,7 @@ @modules-shown-to-user-changed="${this.onModulesShownToUserChanged_}" @customize-module="${this.onCustomizeModule_}" @modules-loaded="${this.onModulesLoaded_}" - @modules-auto-removed="${this.showAutoRemovedToast_}" + @modules-auto-removed="${this.onModulesAutoRemoved_}" ?hidden="${!this.promoAndModulesLoaded_}"> </ntp-modules> `}
diff --git a/chrome/browser/resources/new_tab_page/app.ts b/chrome/browser/resources/new_tab_page/app.ts index b60f55e..42135d6d 100644 --- a/chrome/browser/resources/new_tab_page/app.ts +++ b/chrome/browser/resources/new_tab_page/app.ts
@@ -20,7 +20,7 @@ import type {ComposeboxElement} from 'chrome://resources/cr_components/composebox/composebox.js'; import {VoiceSearchAction as ComposeVoiceSearchAction} from 'chrome://resources/cr_components/composebox/composebox.js'; import {HelpBubbleMixinLit} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin_lit.js'; -import type {SearchboxElement} from 'chrome://resources/cr_components/searchbox/searchbox.js'; +import type {OpenComposeboxEventDetail, SearchboxElement} from 'chrome://resources/cr_components/searchbox/searchbox.js'; import type {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js'; import {assert, assertNotReached} from 'chrome://resources/js/assert.js'; import type {ClickInfo} from 'chrome://resources/js/browser_command.mojom-webui.js'; @@ -886,13 +886,11 @@ this.pendingComposeboxInputState_ = null; } - protected openComposebox_(e: CustomEvent<{ - searchboxText: string, - contextFiles: ContextualUpload[], - mode: ToolMode, - model: ModelMode, - inputState: InputState|null, - }>) { + protected onActionChipClick_(e: CustomEvent<OpenComposeboxEventDetail>) { + this.onOpenComposebox_(e); + } + + protected onOpenComposebox_(e: CustomEvent<OpenComposeboxEventDetail>) { if (e.detail.searchboxText) { this.pendingComposeboxText_ = e.detail.searchboxText; } @@ -935,10 +933,10 @@ cancelable: true, }); - this.closeComposebox_(closeComposebox); + this.onCloseComposebox_(closeComposebox); } - protected closeComposebox_(e: CustomEvent) { + protected onCloseComposebox_(e: CustomEvent) { const composeboxDialog = this.shadowRoot.querySelector<HTMLDialogElement>('#composeboxDialog'); assert(composeboxDialog); @@ -1258,7 +1256,7 @@ } } - protected onMiddleSlotPromoLoaded_() { + protected onNtpMiddleSlotPromoLoaded_() { this.middleSlotPromoLoaded_ = true; } @@ -1442,13 +1440,13 @@ this.realboxHadSecondarySide = e.detail.value; } - protected onSearchboxContainerFocusIn_() { + protected onSearchboxContainerFocusin_() { if (this.ntpRealboxNextEnabled_) { this.containerFocused_ = true; } } - protected onSearchboxContainerFocusOut_() { + protected onSearchboxContainerFocusout_() { if (this.ntpRealboxNextEnabled_) { this.containerFocused_ = this.shadowRoot.getElementById('searchboxContainer')!.matches( @@ -1499,6 +1497,16 @@ * @param undoToastContext - An event that contains the undo toast message and * the undo callback function. */ + protected onMostVisitedAutoRemoved_( + undoToastContext: CustomEvent<{message: string, undo: () => void}>) { + this.showAutoRemovedToast_(undoToastContext); + } + + protected onModulesAutoRemoved_( + undoToastContext: CustomEvent<{message: string, undo: () => void}>) { + this.showAutoRemovedToast_(undoToastContext); + } + protected showAutoRemovedToast_( undoToastContext: CustomEvent<{message: string, undo: () => void}>) { this.pendingAutoRemovalToasts_.push(undoToastContext.detail);
diff --git a/chrome/browser/resources/new_tab_page/lens_form.html b/chrome/browser/resources/new_tab_page/lens_form.html index 72bd92c..d3fdcd6 100644 --- a/chrome/browser/resources/new_tab_page/lens_form.html +++ b/chrome/browser/resources/new_tab_page/lens_form.html
@@ -6,7 +6,7 @@ name="encoded_image" type="file" .accept="${this.supportedFileTypes_}" - @change="${this.handleFileInputChange_}"></input> + @change="${this.onFileInputChange_}"></input> </form> <form id="urlForm" .action="${this.uploadUrlAction_}"
diff --git a/chrome/browser/resources/new_tab_page/lens_form.ts b/chrome/browser/resources/new_tab_page/lens_form.ts index 41287379..db0bea1 100644 --- a/chrome/browser/resources/new_tab_page/lens_form.ts +++ b/chrome/browser/resources/new_tab_page/lens_form.ts
@@ -110,7 +110,7 @@ this.$.fileInput.click(); } - protected handleFileInputChange_() { + protected onFileInputChange_() { const fileList = this.$.fileInput.files; if (fileList) { this.submitFileList(fileList);
diff --git a/chrome/browser/resources/new_tab_page/lens_upload_dialog.html b/chrome/browser/resources/new_tab_page/lens_upload_dialog.html index 09f613f..b6a74f7 100644 --- a/chrome/browser/resources/new_tab_page/lens_upload_dialog.html +++ b/chrome/browser/resources/new_tab_page/lens_upload_dialog.html
@@ -1,8 +1,8 @@ <div id="dialog" ?hidden="${this.isHidden_}" tabindex="-1" lang="$i18n{language}" role="dialog" aria-modal="true" - @focusout="${this.onFocusOut_}"> - <ntp-lens-form id="lensForm" @loading="${this.handleFormLoading_}" - @error="${this.handleFormError_}"> + @focusout="${this.onFocusout_}"> + <ntp-lens-form id="lensForm" @loading="${this.onFormLoading_}" + @error="${this.onFormError_}"> </ntp-lens-form> <div id="container"> <cr-icon-button id="closeButton" class="icon-clear" @@ -11,8 +11,8 @@ @keydown="${this.onCloseButtonKeydown_}"> </cr-icon-button> <div id="title">$i18n{lensSearchUploadDialogTitle}</div> - <div id="dragDropArea" @dragenter="${this.onDragEnter_}" - @dragover="${this.onDragOver_}" @dragleave="${this.onDragLeave_}" + <div id="dragDropArea" @dragenter="${this.onDragenter_}" + @dragover="${this.onDragover_}" @dragleave="${this.onDragleave_}" @drop="${this.onDrop_}"> <!-- Error state --> ${this.isError_ ? html` @@ -28,8 +28,8 @@ <span id="dragText">$i18n{lensSearchUploadDialogDragTitle}</span> <span tabindex="0" role="button" id="uploadText" @click="${this.onUploadFileClick_}" - @keydown="${this.onUploadFileKeyDown_}" - @touchend="${this.onUploadFileTouchEnd_}"> + @keydown="${this.onUploadFileKeydown_}" + @touchend="${this.onUploadFileTouchend_}"> $i18n{lensSearchUploadDialogUploadFileTitle} </span> </div> @@ -45,10 +45,10 @@ placeholder="$i18n{lensSearchUploadDialogTextPlaceholder}" text="text" .value="${this.uploadUrl_}" @input="${this.onInputBoxInput_}" - @keydown="${this.onUrlKeyDown_}"> + @keydown="${this.onUrlKeydown_}"> <div id="inputSubmit" tabindex="0" role="button" - @click="${this.onSubmitUrl_}" - @keydown="${this.onInputSubmitKeyDown_}"> + @click="${this.onInputSubmitClick_}" + @keydown="${this.onInputSubmitKeydown_}"> $i18n{lensSearchUploadDialogSearchButtonLabel} </div> </div>
diff --git a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts index 8d1b92f..a86374094 100644 --- a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts +++ b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts
@@ -321,7 +321,7 @@ this.setOnlineState_(); } - protected onUploadFileKeyDown_(event: KeyboardEvent) { + protected onUploadFileKeydown_(event: KeyboardEvent) { if (event.key === EventKeys.ENTER || event.key === EventKeys.SPACE) { this.$.lensForm.openSystemFilePicker(); } @@ -334,11 +334,11 @@ // Remove this after the NTP is fully migrated off of Polymer. // This is to stop Polymer from running its touchend event listener that // keeps the event from making it to the file input. - protected onUploadFileTouchEnd_(e: Event) { + protected onUploadFileTouchend_(e: Event) { e.stopPropagation(); } - protected handleFormLoading_(event: CustomEvent<LensSubmitType>) { + protected onFormLoading_(event: CustomEvent<LensSubmitType>) { this.dialogState_ = DialogState.LOADING; switch (event.detail) { case LensSubmitType.FILE: @@ -352,7 +352,7 @@ } } - protected handleFormError_(event: CustomEvent<LensErrorType>) { + protected onFormError_(event: CustomEvent<LensErrorType>) { switch (event.detail) { case LensErrorType.MULTIPLE_FILES: this.dialogState_ = DialogState.ERROR; @@ -400,30 +400,34 @@ } } - protected onUrlKeyDown_(event: KeyboardEvent) { + protected onUrlKeydown_(event: KeyboardEvent) { if (event.key === EventKeys.ENTER) { event.preventDefault(); - this.onSubmitUrl_(); + this.submitUrl_(); } } - protected onInputSubmitKeyDown_(event: KeyboardEvent) { + protected onInputSubmitKeydown_(event: KeyboardEvent) { if (event.key === EventKeys.ENTER || event.key === EventKeys.SPACE) { - this.onSubmitUrl_(); + this.submitUrl_(); } else if (event.key === EventKeys.TAB && !event.shiftKey) { event.preventDefault(); this.$.closeButton.focus(); } } - protected onSubmitUrl_() { + protected onInputSubmitClick_() { + this.submitUrl_(); + } + + private submitUrl_() { const url = this.uploadUrl_.trim(); if (url.length > 0) { this.$.lensForm.submitUrl(url); } } - protected onDragEnter_(e: DragEvent) { + protected onDragenter_(e: DragEvent) { e.preventDefault(); this.dragCount += 1; @@ -432,11 +436,11 @@ } } - protected onDragOver_(e: DragEvent) { + protected onDragover_(e: DragEvent) { e.preventDefault(); } - protected onDragLeave_(e: DragEvent) { + protected onDragleave_(e: DragEvent) { e.preventDefault(); this.dragCount -= 1; @@ -455,7 +459,7 @@ } } - protected onFocusOut_(event: FocusEvent) { + protected onFocusout_(event: FocusEvent) { // If the focus event is occurring during a drag into the upload dialog, // do nothing. See b/284201957#6 for scenario in which this is necessary. if (this.dragCount === 1) {
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/calendar.html b/chrome/browser/resources/new_tab_page/modules/calendar/calendar.html index 844db5f9..3a9dbb34 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/calendar.html +++ b/chrome/browser/resources/new_tab_page/modules/calendar/calendar.html
@@ -12,7 +12,7 @@ ` : ''} `)} <div id="seeMore" class="row"> - <a href="${this.calendarLink}" @click="${this.recordSeeMoreClick_}" + <a href="${this.calendarLink}" @click="${this.onSeeMoreClick_}" aria-label="$i18n{modulesCalendarSeeMoreAcc}"> $i18n{modulesCalendarSeeMore} <div id="hoverContainer"></div>
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/calendar.ts b/chrome/browser/resources/new_tab_page/modules/calendar/calendar.ts index 7d911dc..5cdef1c 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/calendar.ts +++ b/chrome/browser/resources/new_tab_page/modules/calendar/calendar.ts
@@ -162,7 +162,7 @@ return index === this.expandedEventIndex_; } - protected recordSeeMoreClick_() { + protected onSeeMoreClick_() { this.dispatchEvent(new Event('usage', {composed: true, bubbles: true})); recordCalendarAction(CalendarAction.SEE_MORE_CLICKED, this.moduleName); }
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.html b/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.html index d5dfb759..afa526a9 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.html +++ b/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.html
@@ -1,4 +1,4 @@ -<a id="header" href="${this.event.url}" @click="${this.recordHeaderClick_}" +<a id="header" href="${this.event.url}" @click="${this.onHeaderClick_}" title="${this.event.title}"> <div id="hoverContainer"></div> <span id="startTime">${this.formattedStartTime_}</span> @@ -17,7 +17,7 @@ <div id="attachment-icon" class="cr-icon"></div> <div id="attachmentList" class="${this.attachmentListClass_}"> ${this.event.attachments.map((item, index) => html` - <cr-chip data-index="${index}" @click="${this.openAttachment_}" + <cr-chip data-index="${index}" @click="${this.onAttachmentClick_}" chip-role="link" class="attachment" title="${item.title}" ?disabled="${this.isAttachmentDisabled_(index)}"> <img is="cr-auto-img" auto-src="${item.iconUrl}" alt=""> @@ -28,7 +28,7 @@ </div> <div id="conference" ?hidden="${!this.showConferenceButton_()}"> <cr-button role="link" - @click="${this.openVideoConference_}" + @click="${this.onVideoConferenceClick_}" class="action-button" aria-label="${this.i18n('modulesCalendarJoinMeetingButtonAcc', this.event.title)}">
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.ts b/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.ts index 004598e7..2dd5cec 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.ts +++ b/chrome/browser/resources/new_tab_page/modules/calendar/calendar_event.ts
@@ -163,7 +163,7 @@ return !attachment.resourceUrl; } - protected openAttachment_(e: Event) { + protected onAttachmentClick_(e: Event) { this.dispatchEvent(new Event('usage', {composed: true, bubbles: true})); recordCalendarAction(CalendarAction.ATTACHMENT_CLICKED, this.moduleName); const currentTarget = e.currentTarget as HTMLElement; @@ -175,14 +175,14 @@ } } - protected openVideoConference_() { + protected onVideoConferenceClick_() { this.dispatchEvent(new Event('usage', {composed: true, bubbles: true})); recordCalendarAction( CalendarAction.CONFERENCE_CALL_CLICKED, this.moduleName); WindowProxy.getInstance().navigate(this.event.conferenceUrl!); } - protected recordHeaderClick_() { + protected onHeaderClick_() { this.dispatchEvent(new Event('usage', {composed: true, bubbles: true})); let action = CalendarAction.BASIC_EVENT_HEADER_CLICKED; if (this.expanded) {
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.html b/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.html index 7f28d66..f63dd7bb 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.html +++ b/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.html
@@ -8,7 +8,7 @@ @disable-button-click="${this.onDisableButtonClick_}" @dismiss-button-click="${this.onDismissButtonClick_}" @info-button-click="${this.onInfoButtonClick_}" - @signout-button-click="${this.onSignOutButtonClick_}"> + @signout-button-click="${this.onSignoutButtonClick_}"> </ntp-module-header> <ntp-calendar id="outlook-calendar" .events="${this.events_}"
diff --git a/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.ts b/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.ts index eddf968..750a99ec 100644 --- a/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.ts +++ b/chrome/browser/resources/new_tab_page/modules/calendar/outlook_calendar_module.ts
@@ -114,7 +114,7 @@ }); } - protected onSignOutButtonClick_() { + protected onSignoutButtonClick_() { ParentTrustedDocumentProxy.getInstance()?.getChildDocument().signOut(); } }
diff --git a/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.html b/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.html index 44a4dc52..b3eb215 100644 --- a/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.html +++ b/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.html
@@ -7,7 +7,7 @@ 'modulesMicrosoftFilesName')}" @disable-button-click="${this.onDisableButtonClick_}" @dismiss-button-click="${this.onDismissButtonClick_}" - @signout-button-click="${this.onSignOutButtonClick_}" + @signout-button-click="${this.onSignoutButtonClick_}" @info-button-click="${this.onInfoButtonClick_}"> </ntp-module-header> <ntp-file-suggestion module-name="MicrosoftFiles"
diff --git a/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.ts b/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.ts index fd39a8e..2fb0db2 100644 --- a/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.ts +++ b/chrome/browser/resources/new_tab_page/modules/file_suggestion/microsoft_files_module.ts
@@ -119,7 +119,7 @@ this.showInfoDialog_ = false; } - protected onSignOutButtonClick_() { + protected onSignoutButtonClick_() { ParentTrustedDocumentProxy.getInstance()?.getChildDocument().signOut(); }
diff --git a/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.html b/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.html index 5460157..95b6c39 100644 --- a/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.html +++ b/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.html
@@ -6,7 +6,7 @@ 'modulesMoreActions', 'modulesTabResumptionTitle')}" @disable-button-click="${this.onDisableButtonClick_}" - @dismiss-button-click="${this.onDismissAllButtonClick_}" + @dismiss-button-click="${this.onHeaderDismissButtonClick_}" @info-button-click="${this.onInfoButtonClick_}"> </ntp-module-header> <div id="urlVisits">
diff --git a/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.ts b/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.ts index 0226bbb..f77412a 100644 --- a/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.ts +++ b/chrome/browser/resources/new_tab_page/modules/most_relevant_tab_resumption/module.ts
@@ -117,7 +117,7 @@ }); } - protected onDismissAllButtonClick_() { + protected onHeaderDismissButtonClick_() { MostRelevantTabResumptionProxyImpl.getInstance().handler.dismissModule( this.urlVisits); this.fire('dismiss-module-instance', {
diff --git a/chrome/browser/resources/new_tab_page/modules/tab_groups/module.html b/chrome/browser/resources/new_tab_page/modules/tab_groups/module.html index 2f5c7dd..6615cf7 100644 --- a/chrome/browser/resources/new_tab_page/modules/tab_groups/module.html +++ b/chrome/browser/resources/new_tab_page/modules/tab_groups/module.html
@@ -27,7 +27,7 @@ </div> <div id="zeroTabGroupsCreateNewTabGroup"> <cr-button id="createNewTabGroupButton" class="create-new-tab-group" - @click="${this.onCreateNewTabGroupClickFromZeroState_}"> + @click="${this.onCreateNewTabGroupFromZeroStateClick_}"> <cr-icon id="createNewTabGroupButtonIcon" icon="tab_groups:create_new_tab_group" slot="prefix-icon"> </cr-icon> @@ -72,7 +72,7 @@ <div id="divider"></div> <cr-button id="createNewTabGroupFooterButton" class="create-new-tab-group" - @click="${this.onCreateNewTabGroupClickFromSteadyState_}"> + @click="${this.onCreateNewTabGroupFromSteadyStateClick_}"> <div class="row-content"> <cr-icon id="createNewTabGroupIcon" icon="tab_groups:create_new_tab_group" slot="icon">
diff --git a/chrome/browser/resources/new_tab_page/modules/tab_groups/module.ts b/chrome/browser/resources/new_tab_page/modules/tab_groups/module.ts index fff5a47..dd244a3 100644 --- a/chrome/browser/resources/new_tab_page/modules/tab_groups/module.ts +++ b/chrome/browser/resources/new_tab_page/modules/tab_groups/module.ts
@@ -176,11 +176,11 @@ this.showInfoDialog = false; } - protected onCreateNewTabGroupClickFromZeroState_() { + protected onCreateNewTabGroupFromZeroStateClick_() { this.onCreateNewTabGroupClick_(true); } - protected onCreateNewTabGroupClickFromSteadyState_() { + protected onCreateNewTabGroupFromSteadyStateClick_() { this.onCreateNewTabGroupClick_(false); }
diff --git a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.html.ts b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.html.ts index 2c65d9c..c41cd63 100644 --- a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.html.ts +++ b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.html.ts
@@ -35,7 +35,7 @@ @dismiss-button-click="${this.onDismissButtonClick_}" @info-button-click="${this.onInfoButtonClick_}"> </ntp-module-header> -<div id="promos" @ntp-promo-click="${this.onPromoClick_}"> +<div id="promos" @ntp-promo-click="${this.onNtpPromoClick_}"> ${this.eligiblePromos_.map(item => html` <setup-list-item body-icon-name="${item.iconName}"
diff --git a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.ts b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.ts index d18f7ba..6bf39ee 100644 --- a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.ts +++ b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list.ts
@@ -111,7 +111,7 @@ } } - protected onPromoClick_(e: CustomEvent) { + protected onNtpPromoClick_(e: CustomEvent) { const promoId = e.detail; assert(promoId, 'Entry should never have empty promo ID.'); this.handler_.onPromoClicked(promoId);
diff --git a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.html.ts b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.html.ts index 008dd93..9b2f042f 100644 --- a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.html.ts +++ b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.html.ts
@@ -12,8 +12,8 @@ <div id="container" ?hidden="${this.isModuleHidden_()}" @module-ready="${this.onModuleReady_}" - @disable-module="${this.onHideModule_}" - @dismiss-module-instance="${this.onHideModule_}"> + @disable-module="${this.onDisableModule_}" + @dismiss-module-instance="${this.onDismissModuleInstance_}"> <div id="moduleElement"> <setup-list id="setupList" maxPromos="${this.maxPromos}" maxCompletedPromos="${this.maxCompletedPromos}">
diff --git a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.ts b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.ts index 5af8579c..95b8d79 100644 --- a/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.ts +++ b/chrome/browser/resources/new_tab_page/ntp_promo/setup_list_module_wrapper.ts
@@ -84,7 +84,15 @@ this.eventTracker_.removeAll(); } - protected onHideModule_(e: UndoActionEvent) { + protected onDisableModule_(e: UndoActionEvent) { + this.onHideModule_(e); + } + + protected onDismissModuleInstance_(e: UndoActionEvent) { + this.onHideModule_(e); + } + + private onHideModule_(e: UndoActionEvent) { this.moduleHidden_ = true; const restoreCallback = e.detail.restoreCallback; this.undoData_ = {
diff --git a/chrome/browser/resources/omnibox_popup/aim_app.css b/chrome/browser/resources/omnibox_popup/aim_app.css index dd001d5..fa3739de 100644 --- a/chrome/browser/resources/omnibox_popup/aim_app.css +++ b/chrome/browser/resources/omnibox_popup/aim_app.css
@@ -90,8 +90,8 @@ top: 14px; } -cr-composebox::part(text-container) { - height: 44px; +cr-composebox::part(input) { + min-height: 44px; } cr-composebox[searchbox-layout-mode='TallBottomContext']::part(context-menu-and-tools) {
diff --git a/chrome/browser/resources/updater/event_list/filter_bar.css b/chrome/browser/resources/updater/event_list/filter_bar.css index 71e5b26..fb52a8f1 100644 --- a/chrome/browser/resources/updater/event_list/filter_bar.css +++ b/chrome/browser/resources/updater/event_list/filter_bar.css
@@ -9,35 +9,14 @@ * #css_wrapper_metadata_end */ :host { - display: block; - /* Ensure primary text color is inherited by default */ - color: var(--cr-primary-text-color); - - /* Accent background for primary chips */ - --chip-primary-background: var(--google-blue-50); - /* Accent border for primary chips */ - --chip-primary-border: var(--google-blue-100); - /* Neutral, subtle border for filter bar, input fields, and buttons */ + --chip-disabled-background-color: var(--google-grey-100); --filter-border-color: var(--google-grey-300); - /* Text color for primary chips */ - --chip-text-color: var(--cr-link-color); - /* Text color for strikethrough chips and secondary text elements */ - --chip-strikethrough-text: var(--cr-secondary-text-color); - /* Background for strikethrough chips */ - --chip-strikethrough-background: var(--google-grey-100); - /* Border for strikethrough chips */ - --chip-strikethrough-border: var(--google-grey-300); } @media (prefers-color-scheme: dark) { :host { - --chip-primary-background: var(--google-blue-300); - --chip-primary-border: var(--google-blue-300); + --chip-disabled-background-color: var(--google-grey-900); --filter-border-color: var(--google-grey-700); - --chip-text-color: buttontext; - --chip-strikethrough-background: var(--google-grey-900); - --chip-strikethrough-text: var(--cr-secondary-text-color); - --chip-strikethrough-border: var(--google-grey-800); } } @@ -51,7 +30,56 @@ gap: 4px; } -.chip cr-icon-button { +.chip { + align-items: center; + color: var(--cr-primary-text-color); + background-color: var(--cr-fallback-color-tonal-container); + border-radius: 8px; + border: none; + box-sizing: border-box; + display: flex; + height: 28px; + overflow: hidden; + position: relative; + padding-left: 10px; + padding-right: 32px; + font-family: inherit; + font-size: 12px; + cursor: pointer; +} + +.chip[disabled] { + opacity: var(--cr-disabled-opacity); + background-color: var(--chip-disabled-background-color); + color: var(--cr-secondary-text-color); + border: 1px solid var(--cr-fallback-color-on-primary-container); +} + +.chip:focus-visible { + outline: solid 2px var(--cr-focus-outline-color); + outline-offset: 2px; +} + +.chip .hover-layer { + display: none; +} + +.chip:hover .hover-layer { + background: var(--cr-hover-on-subtle-background-color); + display: block; + inset: 0; + pointer-events: none; + position: absolute; +} + +.chip cr-ripple { + color: var(--cr-active-neutral-on-subtle-background-color); +} + +.chip-wrapper cr-icon-button { + position: absolute; + right: 6px; + margin: 0; padding: 2px; --cr-icon-button-icon-size: 16px; --cr-icon-button-size: 20px; @@ -61,6 +89,8 @@ .chip-wrapper { position: relative; + display: flex; + align-items: center; } #add-filter-button {
diff --git a/chrome/browser/resources/updater/event_list/filter_bar.html.ts b/chrome/browser/resources/updater/event_list/filter_bar.html.ts index 64ffba5..b390620 100644 --- a/chrome/browser/resources/updater/event_list/filter_bar.html.ts +++ b/chrome/browser/resources/updater/event_list/filter_bar.html.ts
@@ -15,16 +15,18 @@ <cr-icon icon="updater:filter"></cr-icon> ${this.filterOrder.map(item => html` <div class="chip-wrapper"> - <cr-chip class="chip" ?disabled="${this.isEditingViaInput(item)}" - ?selected="${!this.isEditingViaInput(item)}" role="button" - aria-haspopup="dialog" data-filter-category="${item}" - @click="${this.onChipClick}" @keydown="${this.onChipKeydown}"> + <button class="chip" ?disabled="${this.isEditingViaInput(item)}" + data-filter-category="${item}" @click="${this.onChipClick}" + aria-haspopup="dialog"> + <div class="hover-layer"></div> ${this.getFilterLabel(item)} - <cr-icon-button iron-icon="cr:close" data-filter-category="${item}" - @click="${this.onRemoveFilterClick}" - aria-label="$i18n{removeFilter}"> - </cr-icon-button> - </cr-chip> + <cr-ripple></cr-ripple> + </button> + <cr-icon-button iron-icon="cr:close" data-filter-category="${item}" + ?disabled="${this.isEditingViaInput(item)}" + @click="${this.onRemoveFilterClick}" + aria-label="$i18n{removeFilter}"> + </cr-icon-button> </div> `)} @@ -46,14 +48,14 @@ <type-dialog .anchorElement="${this.getDialogAnchor()}" @type-selection-changed="${this.onTypeSelectionChanged}" @close="${this.onFilterDialogClose}"> - </filter-dialog-type> + </type-dialog> ` : ''} ${this.filterMenuState === FilterCategory.APP ? html` <app-dialog .anchorElement="${this.getDialogAnchor()}" .initialSelections="${this.filterSettings.apps}" @filter-change="${this.onAppFilterChange}" @close="${this.onFilterDialogClose}"> - </filter-dialog-app> + </app-dialog> ` : ''} ${this.filterMenuState === FilterCategory.EVENT ? html` <event-dialog .anchorElement="${this.getDialogAnchor()}" @@ -61,7 +63,7 @@ this.filterSettings.eventTypes}" @filter-change="${this.onEventTypeFilterChange}" @close="${this.onFilterDialogClose}"> - </filter-dialog-event> + </event-dialog> ` : ''} ${this.filterMenuState === FilterCategory.OUTCOME ? html` <outcome-dialog .anchorElement="${this.getDialogAnchor()}" @@ -69,14 +71,14 @@ this.filterSettings.updateOutcomes}" @filter-change="${this.onUpdateOutcomeFilterChange}" @close="${this.onFilterDialogClose}"> - </filter-dialog-outcome> + </outcome-dialog> ` : ''} ${this.filterMenuState === FilterCategory.SCOPE ? html` <scope-dialog .anchorElement="${this.getDialogAnchor()}" .initialSelections="${this.filterSettings.scopes}" @filter-change="${this.onScopeFilterChange}" @close="${this.onFilterDialogClose}"> - </filter-dialog-scope> + </scope-dialog> ` : ''} ${this.filterMenuState === FilterCategory.DATE ? html` <date-dialog .anchorElement="${this.getDialogAnchor()}" @@ -84,7 +86,7 @@ .initialEndDate="${this.filterSettings.endDate}" @filter-change="${this.onDateFilterChange}" @close="${this.onFilterDialogClose}"> - </filter-dialog-date> + </date-dialog> ` : ''} ` : ''} </div>
diff --git a/chrome/browser/resources/updater/event_list/filter_bar.ts b/chrome/browser/resources/updater/event_list/filter_bar.ts index 8e4c426..c4a8c02 100644 --- a/chrome/browser/resources/updater/event_list/filter_bar.ts +++ b/chrome/browser/resources/updater/event_list/filter_bar.ts
@@ -11,6 +11,7 @@ import './filter_dialog/type_dialog.js'; import '//resources/cr_elements/cr_chip/cr_chip.js'; import '//resources/cr_elements/cr_icon_button/cr_icon_button.js'; +import '//resources/cr_elements/cr_ripple/cr_ripple.js'; import '//resources/cr_elements/icons.html.js'; import '//resources/cr_elements/cr_button/cr_button.js'; import '//resources/cr_elements/cr_checkbox/cr_checkbox.js'; @@ -154,7 +155,7 @@ protected async onRemoveFilterClick(e: Event) { - const category = getFilterCategoryForTarget(e.target as HTMLElement); + const category = getFilterCategoryForTarget(e.currentTarget as HTMLElement); this.updateFilterOrder(category, false); switch (category) { case FilterCategory.APP: @@ -180,21 +181,9 @@ } protected onChipClick(e: MouseEvent) { - // Prevent opening the menu if the close button was clicked - const target = e.target as HTMLElement; - if (target.tagName !== 'CR-ICON-BUTTON') { - this.editFilter(getFilterCategoryForTarget(target)); - } + this.editFilter(getFilterCategoryForTarget(e.target as HTMLElement)); } - protected onChipKeydown(e: KeyboardEvent) { - const target = e.target as HTMLElement; - if ((e.key === 'Enter' || e.key === ' ') && target.tagName !== 'BUTTON') { - this.editFilter(getFilterCategoryForTarget(target)); - } - } - - protected getFilterLabel(category: FilterCategory): string { switch (category) { case FilterCategory.APP:
diff --git a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.css b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.css index a8ef5d70..cbbb5e9 100644 --- a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.css +++ b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.css
@@ -14,7 +14,7 @@ display: block; } -button.filter-menu-item { +cr-button.filter-menu-item { background: none; color: inherit; border: none; @@ -24,4 +24,5 @@ margin: 0; display: block; width: 100%; + border-radius: 0; }
diff --git a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.html.ts b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.html.ts index c0ac9ae..38fecb2 100644 --- a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.html.ts +++ b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.html.ts
@@ -12,11 +12,10 @@ <!--_html_template_start_--> <filter-dialog .anchorElement="${this.anchorElement}" @close="${this.onClose}"> ${this.filterMenuItems.map(item => html` - <button class="filter-menu-item" - data-filter-category="${item.filterCategory}" - @click="${this.onClick}"> + <cr-button class="filter-menu-item" + data-filter-category="${item.filterCategory}" @click="${this.onClick}"> ${item.label} - </button> + </cr-button> `)} </filter-dialog> <!--_html_template_end_-->`;
diff --git a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.ts b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.ts index bca1f2ce..b452744c 100644 --- a/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.ts +++ b/chrome/browser/resources/updater/event_list/filter_dialog/type_dialog.ts
@@ -3,6 +3,7 @@ // found in the LICENSE file. import './filter_dialog.js'; +import '//resources/cr_elements/cr_button/cr_button.js'; import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js'; import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
diff --git a/chrome/browser/resources/webui_toolbar/BUILD.gn b/chrome/browser/resources/webui_toolbar/BUILD.gn index 9a8bf99..c19ecdc0 100644 --- a/chrome/browser/resources/webui_toolbar/BUILD.gn +++ b/chrome/browser/resources/webui_toolbar/BUILD.gn
@@ -17,6 +17,8 @@ ts_files = [ "app.html.ts", "app.ts", + "back_forward_button.html.ts", + "back_forward_button.ts", "browser_proxy.ts", "metrics_recorder.ts", "reload_button.html.ts", @@ -28,6 +30,7 @@ ] css_files = [ "app.css", + "back_forward_button.css", "reload_button.css", "split_tabs_button.css", "toolbar_button.css",
diff --git a/chrome/browser/resources/webui_toolbar/app.html.ts b/chrome/browser/resources/webui_toolbar/app.html.ts index 0cc3b34..0ee38b2d 100644 --- a/chrome/browser/resources/webui_toolbar/app.html.ts +++ b/chrome/browser/resources/webui_toolbar/app.html.ts
@@ -11,6 +11,14 @@ return html`<!--_html_template_start_--> <link rel="stylesheet" href="layout_constants_v${this.navigationControlsState_.layoutConstantsVersion}.css"> +${this.isBackForwardButtonEnabled_ ? html` + <back-forward-button-app id="back" direction="back" + .state="${this.navigationControlsState_.backForwardControlState.backButtonState}" + .leadingMargin="${this.navigationControlsState_.backForwardControlState.backButtonLeadingMargin}"> + </back-forward-button-app> + <back-forward-button-app id="forward" direction="forward" + .state="${this.navigationControlsState_.backForwardControlState.forwardButtonState}"> + </back-forward-button-app>` : ''} ${this.isReloadButtonEnabled_ ? html`<reload-button-app id="reload" .state="${this.navigationControlsState_.reloadControlState}">
diff --git a/chrome/browser/resources/webui_toolbar/app.ts b/chrome/browser/resources/webui_toolbar/app.ts index 8aad382..656f64ae 100644 --- a/chrome/browser/resources/webui_toolbar/app.ts +++ b/chrome/browser/resources/webui_toolbar/app.ts
@@ -2,14 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import './back_forward_button.js'; import './reload_button.js'; import './split_tabs_button.js'; +import {loadTimeData} from '//resources/js/load_time_data.js'; import {TrackedElementManager} from '//resources/js/tracked_element/tracked_element_manager.js'; import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js'; import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js'; -import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import {getCss} from './app.css.js'; import {getHtml} from './app.html.js'; @@ -37,6 +38,7 @@ isSplitTabsButtonEnabled_: {type: Boolean}, isLocationBarEnabled_: {type: Boolean}, navigationControlsState_: {type: Object}, + isBackForwardButtonEnabled_: {type: Boolean}, }; } @@ -46,6 +48,8 @@ loadTimeData.getBoolean('enableSplitTabsButton'); protected accessor isLocationBarEnabled_: boolean = loadTimeData.getBoolean('enableLocationBar'); + protected accessor isBackForwardButtonEnabled_: boolean = + loadTimeData.getBoolean('enableBackForwardButtons'); protected accessor navigationControlsState_: NavigationControlsState = { reloadControlState: { canShowMenu: false, @@ -58,6 +62,11 @@ isPinned: false, isContextMenuVisible: false, }, + backForwardControlState: { + backButtonState: {enabled: false, visible: true}, + forwardButtonState: {enabled: false, visible: true}, + backButtonLeadingMargin: 0, + }, layoutConstantsVersion: 0, };
diff --git a/chrome/browser/resources/webui_toolbar/back_forward_button.css b/chrome/browser/resources/webui_toolbar/back_forward_button.css new file mode 100644 index 0000000..103a616 --- /dev/null +++ b/chrome/browser/resources/webui_toolbar/back_forward_button.css
@@ -0,0 +1,15 @@ +/* Copyright 2026 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * #import=//resources/cr_elements/cr_hidden_style_lit.css.js + * #import=./toolbar_button.css.js + * #scheme=relative + * #include=cr-hidden-style-lit toolbar-button + * #css_wrapper_metadata_end */ + +:host([direction='forward']) cr-icon-button { + transform: scaleX(-1); +}
diff --git a/chrome/browser/resources/webui_toolbar/back_forward_button.html.ts b/chrome/browser/resources/webui_toolbar/back_forward_button.html.ts new file mode 100644 index 0000000..19b9f2a --- /dev/null +++ b/chrome/browser/resources/webui_toolbar/back_forward_button.html.ts
@@ -0,0 +1,25 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {BackForwardButtonElement} from './back_forward_button.js'; + +export function getHtml(this: BackForwardButtonElement) { + return html`<!--_html_template_start_--> +<cr-icon-button + iron-icon="cr:arrow-back" + ?disabled="${!this.state.enabled}" + aria-label="${this.ariaLabel_}" + title="${this.tooltip_}" + style="margin-inline-start: ${this.leadingMargin}px" + @pointerdown="${this.onPointerdown_}" + @pointerup="${this.onPointerup_}" + @pointercancel="${this.onPointercancel_}" + @pointerenter="${this.onPointerenter_}" + @contextmenu="${this.onContextMenu_}"> + </cr-icon-button> + +<!--_html_template_end_-->`; +}
diff --git a/chrome/browser/resources/webui_toolbar/back_forward_button.ts b/chrome/browser/resources/webui_toolbar/back_forward_button.ts new file mode 100644 index 0000000..e58144a3 --- /dev/null +++ b/chrome/browser/resources/webui_toolbar/back_forward_button.ts
@@ -0,0 +1,152 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '//resources/cr_elements/cr_icon_button/cr_icon_button.js'; +import '//resources/cr_elements/icons.html.js'; + +import {loadTimeData} from '//resources/js/load_time_data.js'; +import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; +import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js'; +import {MenuSourceType} from '//resources/mojo/ui/base/mojom/menu_source_type.mojom-webui.js'; +import {isMac} from 'chrome://resources/js/platform.js'; + +import {getCss} from './back_forward_button.css.js'; +import {getHtml} from './back_forward_button.html.js'; +import {BrowserProxyImpl, ContextMenuType} from './browser_proxy.js'; +import type {BrowserProxy, ButtonState} from './browser_proxy.js'; +import {BUTTON_LEFT, BUTTON_RIGHT, getClickDispositionFlags, getContextMenuPosition} from './toolbar_button.js'; + +// This follows what is done in the views code. +// https://crsrc.org/c/chrome/browser/ui/views/toolbar/toolbar_button.cc;l=440 +const LONG_PRESS_TIMER_THRESHOLD_MS = 500; + +export class BackForwardButtonElement extends CrLitElement { + static get is() { + return 'back-forward-button-app'; + } + + static override get styles() { + return getCss(); + } + + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { + return { + direction: {type: String}, + state: {type: Object}, + leadingMargin: {type: Number}, + }; + } + + accessor direction: string = 'back'; + accessor state: ButtonState = {enabled: false, visible: true}; + accessor leadingMargin: number = 0; + + private isLongPressed_: boolean = false; + private longPressTimer_: number = 0; + private browserProxy_: BrowserProxy = BrowserProxyImpl.getInstance(); + + override willUpdate(changedProperties: PropertyValues<this>) { + super.willUpdate(changedProperties); + + if (changedProperties.has('state')) { + this.hidden = !this.state.visible; + } + } + + private get contextMenuType_(): ContextMenuType { + return this.direction === 'back' ? ContextMenuType.kBack : + ContextMenuType.kForward; + } + + private openMenu_(source: MenuSourceType) { + this.browserProxy_.toolbarUIHandler.showContextMenu( + this.contextMenuType_, getContextMenuPosition(this), source); + } + + protected get ariaLabel_(): string { + return this.direction === 'back' ? + loadTimeData.getString('backButtonAccName') : + loadTimeData.getString('forwardButtonAccName'); + } + + protected get tooltip_(): string { + return this.direction === 'back' ? + loadTimeData.getString('backButtonTooltip') : + loadTimeData.getString('forwardButtonTooltip'); + } + + protected onContextMenu_(e: MouseEvent) { + // TODO(crbug.com/470038385): Handle the event source type (such as touch + // event) like what is done for split tabs button. + this.browserProxy_.toolbarUIHandler.showContextMenu( + this.contextMenuType_, getContextMenuPosition(this), + MenuSourceType.kMouse); + e.preventDefault(); + } + + protected onPointerdown_(e: MouseEvent) { + // Mac Ctrl+click: Open menu immediately and BAIL. + if (isMac && e.button === BUTTON_LEFT && e.ctrlKey) { + this.openMenu_(MenuSourceType.kMouse); + return; + } + + if (e.button === BUTTON_RIGHT) { + return; + } + + this.isLongPressed_ = false; + clearTimeout(this.longPressTimer_); + + this.longPressTimer_ = setTimeout(() => { + this.isLongPressed_ = true; + this.openMenu_(MenuSourceType.kLongPress); + }, LONG_PRESS_TIMER_THRESHOLD_MS); + } + + protected onPointerup_(e: MouseEvent) { + const isMacCtrlClick = isMac && e.button === BUTTON_LEFT && e.ctrlKey; + + // Block navigation if: + // 1. It was a long press. + // 2. It's a Mac Ctrl+Click + if (this.isLongPressed_ || isMacCtrlClick) { + this.isLongPressed_ = false; + clearTimeout(this.longPressTimer_); + return; + } + + clearTimeout(this.longPressTimer_); + + const flags = getClickDispositionFlags(e); + + if (this.direction === 'back') { + this.browserProxy_.browserControlsHandler.back(flags); + } else { + this.browserProxy_.browserControlsHandler.forward(flags); + } + } + + protected onPointercancel_() { + clearTimeout(this.longPressTimer_); + } + + protected onPointerenter_() { + if (this.direction === 'back') { + this.browserProxy_.browserControlsHandler.backButtonHovered(); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'back-forward-button-app': BackForwardButtonElement; + } +} + +customElements.define(BackForwardButtonElement.is, BackForwardButtonElement);
diff --git a/chrome/browser/resources/webui_toolbar/browser_proxy.ts b/chrome/browser/resources/webui_toolbar/browser_proxy.ts index 32794288..5965a655 100644 --- a/chrome/browser/resources/webui_toolbar/browser_proxy.ts +++ b/chrome/browser/resources/webui_toolbar/browser_proxy.ts
@@ -15,16 +15,14 @@ import { ContextMenuType, } from './toolbar_ui_api_data_model.mojom-webui.js'; -import type { - NavigationControlsState, - ReloadControlState, -} from './toolbar_ui_api_data_model.mojom-webui.js'; +import type {ButtonState, NavigationControlsState, ReloadControlState} from './toolbar_ui_api_data_model.mojom-webui.js'; export { ClickDispositionFlag, ContextMenuType, }; export type { + ButtonState, NavigationControlsState, ReloadControlState, };
diff --git a/chrome/browser/resources/webui_toolbar/reload_button.ts b/chrome/browser/resources/webui_toolbar/reload_button.ts index 3ea2c00..01e4f17 100644 --- a/chrome/browser/resources/webui_toolbar/reload_button.ts +++ b/chrome/browser/resources/webui_toolbar/reload_button.ts
@@ -13,12 +13,12 @@ import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import {BrowserProxyImpl, ClickDispositionFlag, ContextMenuType} from './browser_proxy.js'; +import {BrowserProxyImpl, ContextMenuType} from './browser_proxy.js'; import type {BrowserProxy, ReloadControlState} from './browser_proxy.js'; import {MetricsRecorder} from './metrics_recorder.js'; import {getCss} from './reload_button.css.js'; import {getHtml} from './reload_button.html.js'; -import {getContextMenuPosition} from './toolbar_button.js'; +import {BUTTON_LEFT, BUTTON_RIGHT, getClickDispositionFlags, getContextMenuPosition} from './toolbar_button.js'; // go/keep-sorted start const RELOAD_BUTTON_ACC_NAME_RELOAD = 'reloadButtonAccNameReload'; @@ -28,10 +28,6 @@ const RELOAD_BUTTON_TOOLTIP_STOP = 'reloadButtonTooltipStop'; // go/keep-sorted end -const BUTTON_LEFT = 0; -const BUTTON_MIDDLE = 1; -const BUTTON_RIGHT = 2; - const LONG_PRESS_TIMER_THRESHOLD_MS = 500; export class ReloadButtonAppElement extends CrLitElement { @@ -150,23 +146,6 @@ } /** - * Generate the list of `ClickDispositionFlag`s based on the `MouseEvent`. - */ - private generateFlags(e: MouseEvent): ClickDispositionFlag[] { - const flags: ClickDispositionFlag[] = []; - if (e.button === BUTTON_MIDDLE) { - flags.push(ClickDispositionFlag.kMiddleMouseButton); - } - if (e.altKey) { - flags.push(ClickDispositionFlag.kAltKeyDown); - } - if (e.metaKey) { - flags.push(ClickDispositionFlag.kMetaKeyDown); - } - return flags; - } - - /** * Handles the mouse click event. * - If it's from the right mouse click, it's not handled from the Javascript. * - If it's a single click: @@ -211,7 +190,9 @@ // If the shift or ctrl key is pressed, we should reload with cache // bypassed. BrowserProxyImpl.getInstance().browserControlsHandler.reloadFromClick( - /*bypass_cache=*/ e.shiftKey || e.ctrlKey, this.generateFlags(e)); + /*bypass_cache=*/ e.shiftKey || e.ctrlKey, + getClickDispositionFlags( + e, {ignoreCtrlKey: true, ignoreShiftKey: true})); } if (e.button === BUTTON_LEFT && !e.metaKey) {
diff --git a/chrome/browser/resources/webui_toolbar/toolbar_button.ts b/chrome/browser/resources/webui_toolbar/toolbar_button.ts index 16495fa0..d7f35140 100644 --- a/chrome/browser/resources/webui_toolbar/toolbar_button.ts +++ b/chrome/browser/resources/webui_toolbar/toolbar_button.ts
@@ -4,6 +4,17 @@ import {MenuSourceType} from '//resources/mojo/ui/base/mojom/menu_source_type.mojom-webui.js'; +import {ClickDispositionFlag} from './browser_proxy.js'; + +export const BUTTON_LEFT = 0; +export const BUTTON_MIDDLE = 1; +export const BUTTON_RIGHT = 2; + +export interface GetClickDispositionFlagsOptions { + ignoreCtrlKey?: boolean; + ignoreShiftKey?: boolean; +} + export function getContextMenuPosition(element: HTMLElement) { const bounds = element.getBoundingClientRect(); const isRtl = document.dir === 'rtl'; @@ -47,3 +58,25 @@ } return MenuSourceType.kMouse; } + +export function getClickDispositionFlags( + e: MouseEvent, + options: GetClickDispositionFlagsOptions = {}): ClickDispositionFlag[] { + const flags: ClickDispositionFlag[] = []; + if (e.button === BUTTON_MIDDLE) { + flags.push(ClickDispositionFlag.kMiddleMouseButton); + } + if (e.altKey) { + flags.push(ClickDispositionFlag.kAltKeyDown); + } + if (e.ctrlKey && !options.ignoreCtrlKey) { + flags.push(ClickDispositionFlag.kControlKeyDown); + } + if (e.metaKey) { + flags.push(ClickDispositionFlag.kMetaKeyDown); + } + if (e.shiftKey && !options.ignoreShiftKey) { + flags.push(ClickDispositionFlag.kShiftKeyDown); + } + return flags; +}
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc index 68f6ba93..e780838 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -784,6 +784,83 @@ false, 1); } +TEST_F(ClientSideDetectionHostTest, UserReportSkipsAllowlist) { + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { + GTEST_SKIP(); + } + + GURL url("http://allowlisted.com/"); + + // Set the URL as allowlisted. + database_manager_->SetAllowlistLookupDetailsForUrl(url, true); + + // Common expectations for any classification. + EXPECT_CALL(*csd_service_, IsLocalResource(_)).WillRepeatedly(Return(false)); + EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*csd_service_, AtPhishingReportLimit()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*database_manager_.get(), CanCheckUrl(_)) + .WillRepeatedly(Return(true)); + + // For the initial navigation (TRIGGER_MODELS), it should check the allowlist + // and match. + EXPECT_CALL(*database_manager_.get(), CheckCsdAllowlistUrl(url, _)) + .WillOnce(Return(AsyncMatch::MATCH)); + + NavigateAndCommit(url); + base::RunLoop().RunUntilIdle(); + + // Verification: Phishing detection should NOT have started for + // TRIGGER_MODELS. + EXPECT_FALSE(fake_phishing_detector_.phishing_detection_started()); + + // Now trigger USER_REPORT. It should skip allowlist and start classification. + // CheckCsdAllowlistUrl should NOT be called again. + csd_host_->ReportUnsafeSite(std::nullopt, std::nullopt, std::nullopt); + + base::RunLoop().RunUntilIdle(); + + fake_phishing_detector_.CheckMessage(&url); +} + +TEST_F(ClientSideDetectionHostTest, UserReportSkipsReportLimit) { + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { + GTEST_SKIP(); + } + + GURL url("http://example.com/"); + + // Set that we are at the phishing report limit. + EXPECT_CALL(*csd_service_, AtPhishingReportLimit()) + .WillRepeatedly(Return(true)); + + // Common expectations. + EXPECT_CALL(*csd_service_, IsLocalResource(_)).WillRepeatedly(Return(false)); + EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*database_manager_.get(), CanCheckUrl(_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*database_manager_.get(), CheckCsdAllowlistUrl(url, _)) + .WillRepeatedly(Return(AsyncMatch::NO_MATCH)); + database_manager_->SetAllowlistLookupDetailsForUrl(url, false); + + NavigateAndCommit(url); + base::RunLoop().RunUntilIdle(); + + // Verification: Phishing detection should NOT have started for + // TRIGGER_MODELS. + EXPECT_FALSE(fake_phishing_detector_.phishing_detection_started()); + + // Now trigger USER_REPORT. It should skip report limit and start + // classification. + csd_host_->ReportUnsafeSite(std::nullopt, std::nullopt, std::nullopt); + + base::RunLoop().RunUntilIdle(); + + fake_phishing_detector_.CheckMessage(&url); +} + TEST_F(ClientSideDetectionHostTest, PhishingDetectionDoneMultiplePings) { if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { GTEST_SKIP();
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java index 9edf070..95876e3 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
@@ -163,9 +163,9 @@ mActivityScenario.getScenario().onActivity((activity) -> mActivity = activity); mActivityScenario.getScenario().moveToState(State.RESUMED); mWindow = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, - /* listenToActivityState= */ false, + false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc index 79f820a..4998ee33 100644 --- a/chrome/browser/ssl/ssl_browsertest.cc +++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -1522,8 +1522,7 @@ ssl_test_util::CheckAuthenticationBrokenState( browser()->tab_strip_model()->GetActiveWebContents(), - net::CERT_STATUS_INVALID | net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM, - AuthState::SHOWING_INTERSTITIAL); + net::CERT_STATUS_INVALID, AuthState::SHOWING_INTERSTITIAL); } // Visit a HTTP page which request WSS connection to a server providing invalid
diff --git a/chrome/browser/touch_to_fill/password_manager/BUILD.gn b/chrome/browser/touch_to_fill/password_manager/BUILD.gn index 1c38fd4..f250d1f 100644 --- a/chrome/browser/touch_to_fill/password_manager/BUILD.gn +++ b/chrome/browser/touch_to_fill/password_manager/BUILD.gn
@@ -30,9 +30,10 @@ deps = [ "//base", "//components/password_manager/core/browser", - "//url", ] + public_deps = [ "//url" ] + sources = [ "touch_to_fill_view.h" ] }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 7a8958e..f5bc274f 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -312,6 +312,7 @@ "//chrome/browser/gpu", "//chrome/browser/hang_monitor", "//chrome/browser/history", + "//chrome/browser/history_embeddings", "//chrome/browser/image_decoder", "//chrome/browser/image_fetcher", "//chrome/browser/lookalikes", @@ -3745,8 +3746,6 @@ "views/commerce/discounts_bubble_dialog_view.h", "views/commerce/discounts_coupon_code_label_view.cc", "views/commerce/discounts_coupon_code_label_view.h", - "views/commerce/discounts_icon_view.cc", - "views/commerce/discounts_icon_view.h", "views/commerce/discounts_page_action_view_controller.cc", "views/commerce/discounts_page_action_view_controller.h", "views/commerce/price_insights_page_action_view_controller.cc", @@ -4417,6 +4416,8 @@ "views/status_bubble_views.h", "views/storage/storage_pressure_bubble_view.cc", "views/storage/storage_pressure_bubble_view.h", + "views/sub_apps_permission_explanation.cc", + "views/sub_apps_permission_explanation.h", "views/tab_contents/chrome_web_contents_view_focus_helper.cc", "views/tab_contents/chrome_web_contents_view_focus_helper.h", "views/tab_dialogs_views.cc",
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java index 9172bac..513864fe 100644 --- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java +++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
@@ -126,9 +126,9 @@ public void setUp() { mActivity = Robolectric.buildActivity(Activity.class).get(); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, - /* listenToActivityState= */ false, + false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.cc b/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.cc index bdbb5dd6..2365a575 100644 --- a/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.cc +++ b/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.cc
@@ -159,9 +159,10 @@ } void ExtensionsMenuDelegateAndroid::OnActionUpdated( - const ToolbarActionsModel::ActionId& action_id) { + const ToolbarActionsModel::ActionId& action_id, + int index) { JNIEnv* env = base::android::AttachCurrentThread(); - Java_ExtensionsMenuBridge_onModelChanged(env, java_object_); + Java_ExtensionsMenuBridge_onActionUpdated(env, java_object_, index); } void ExtensionsMenuDelegateAndroid::OnActionIconUpdated(
diff --git a/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.h b/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.h index 376f2ff3..064f00d 100644 --- a/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.h +++ b/chrome/browser/ui/android/extensions/extensions_menu_delegate_android.h
@@ -52,7 +52,8 @@ int index) override; void OnActionRemoved(const ToolbarActionsModel::ActionId& action_id, int index) override; - void OnActionUpdated(const ToolbarActionsModel::ActionId& action_id) override; + void OnActionUpdated(const ToolbarActionsModel::ActionId& action_id, + int index) override; void OnActionsInitialized() override; void OnHostAccessRequestAdded(const extensions::ExtensionId& extension_id, int index) override;
diff --git a/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionsMenuBridge.java b/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionsMenuBridge.java index 6b313ec..1f136a6 100644 --- a/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionsMenuBridge.java +++ b/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionsMenuBridge.java
@@ -118,6 +118,17 @@ } /** + * Callback from native indicating that an extension has been updated. + * + * @param actionIndex The index of the menu entry in the menu corresponding to the action + * updated. + */ + @CalledByNative + public void onActionUpdated(int actionIndex) { + mObserver.onActionUpdated(actionIndex); + } + + /** * Callback from native indicating that the menu data is ready. This will not be called if the * menu data is ready at the menu bridge initialization. */ @@ -143,6 +154,9 @@ /** Called when an action has been removed from the menu. */ void onActionRemoved(int actionIndex); + /** Called when an extension has been updated on actionIndex. */ + void onActionUpdated(int actionIndex); + /** Called when the menu data is ready to be consumed. */ void onReady();
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java index ff086ff..201f22c7 100644 --- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java +++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java
@@ -66,9 +66,9 @@ doReturn(mDelegateSurveyClient).when(mFactory).createClient(any(), any(), any(), any()); mWindow = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, - /* listenToActivityState= */ false, + false, IntentRequestTracker.createFromActivity(mActivity), /* insetObserver= */ null, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java index 5e43f1c2..5de2ecb 100644 --- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java +++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java
@@ -61,9 +61,9 @@ mActivity = Robolectric.buildActivity(Activity.class).get(); mWindow = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( mActivity, - /* listenToActivityState= */ false, + false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionPopup.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionPopup.java index 2f0c328..3b19e79 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionPopup.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionActionPopup.java
@@ -25,18 +25,14 @@ import org.chromium.components.thinwebview.ThinWebViewConstraints; import org.chromium.components.thinwebview.ThinWebViewFactory; import org.chromium.content_public.browser.WebContents; -import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate; import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.ViewAndroidDelegate; import org.chromium.ui.base.ViewUtils; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.modaldialog.ModalDialogManager; -import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import org.chromium.ui.widget.AnchoredPopupWindow; import org.chromium.ui.widget.ViewRectProvider; -import java.lang.ref.WeakReference; - /** * Manages the display of an extension action's popup UI. * @@ -108,9 +104,6 @@ new ActivityWindowAndroid( activity, /* listenToActivityState= */ true, - new ActivityAndroidPermissionDelegate(new WeakReference(mActivity)), - new ActivityKeyboardVisibilityDelegate(new WeakReference(mActivity)), - /* activityTopResumedSupported= */ false, NullUtil.assumeNonNull(windowAndroid.getIntentRequestTracker()), /* insetObserver= */ null, /* trackOcclusion= */ true) {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuCoordinatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuCoordinatorTest.java index 6684076..f69022d 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuCoordinatorTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuCoordinatorTest.java
@@ -15,7 +15,6 @@ import android.app.Activity; import android.graphics.Color; -import android.view.View; import androidx.appcompat.app.AppCompatActivity; @@ -285,28 +284,6 @@ } @Test - public void testUpdateSiteSettingsToggle() { - mExtensionsMenuButton.performClick(); - MaterialSwitchWithText toggle = - mExtensionsMenuCoordinator - .getContentView() - .findViewById(R.id.extensions_menu_site_settings_toggle); - - mExtensionsMenuCoordinator.mMediator.updateSiteSettingsToggle( - createSiteSettingsState("label", true)); - assertEquals(View.VISIBLE, toggle.getVisibility()); - assertEquals(true, toggle.isChecked()); - assertEquals("label", toggle.getText()); - - mExtensionsMenuCoordinator.mMediator.updateSiteSettingsToggle( - createSiteSettingsState( - "label_2", false, ExtensionsMenuTypes.ControlState.Status.HIDDEN)); - assertEquals(View.GONE, toggle.getVisibility()); - assertEquals(false, toggle.isChecked()); - assertEquals("label_2", toggle.getText()); - } - - @Test public void testSiteSettingsToggle_ClickCallsBridge() { mCurrentTabSupplier.set(mTab); mExtensionsMenuButton.performClick();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuItemProperties.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuItemProperties.java index de76930..62fbe539 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuItemProperties.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuItemProperties.java
@@ -16,6 +16,9 @@ @NullMarked public class ExtensionsMenuItemProperties { + public static final WritableObjectPropertyKey<String> EXTENSION_ID = + new WritableObjectPropertyKey<>(); + public static final WritableObjectPropertyKey<@Nullable Bitmap> ICON = new WritableObjectPropertyKey<>(); @@ -28,5 +31,7 @@ CONTEXT_MENU_BUTTON_ON_CLICK = new WritableObjectPropertyKey<>(); public static final PropertyKey[] ALL_KEYS = - new PropertyKey[] {ICON, TITLE, CONTEXT_MENU_BUTTON_ICON, CONTEXT_MENU_BUTTON_ON_CLICK}; + new PropertyKey[] { + EXTENSION_ID, ICON, TITLE, CONTEXT_MENU_BUTTON_ICON, CONTEXT_MENU_BUTTON_ON_CLICK + }; }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediator.java index 91f1440..e847d2c 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediator.java
@@ -7,6 +7,8 @@ import android.content.Context; import android.graphics.Bitmap; +import androidx.annotation.VisibleForTesting; + import org.chromium.base.lifetime.Destroyable; import org.chromium.base.supplier.NullableObservableSupplier; import org.chromium.build.annotations.NullMarked; @@ -104,22 +106,6 @@ /* dismissRunnable= */ null); } - /** - * Updates the site settings toggle. - * - * @param siteSettingsState The site settings state to update to. - */ - void updateSiteSettingsToggle(ExtensionsMenuTypes.SiteSettingsState siteSettingsState) { - mMenuPropertyModel.set( - ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_VISIBLE, - siteSettingsState.toggle.status != ExtensionsMenuTypes.ControlState.Status.HIDDEN); - mMenuPropertyModel.set( - ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_CHECKED, - siteSettingsState.toggle.isOn); - mMenuPropertyModel.set( - ExtensionsMenuProperties.SITE_SETTINGS_LABEL, siteSettingsState.label); - } - /** Destroys the mediator. */ @Override public void destroy() { @@ -135,13 +121,9 @@ // TODO(crbug.com/473213114): Implement data pull for site permissions page. // This will need to consider the event source (e.g., page navigation vs. action update) // to fetch and update the UI correctly, as their effects differ on the site permissions - // page - // and they will need to have different JNI observers. + // page and they will need to have different JNI observers. if (isMainPageVisible()) { - ExtensionsMenuTypes.SiteSettingsState siteSettingsState = - mMenuBridge.getSiteSettingsState(); - updateSiteSettingsToggle(siteSettingsState); - + updateSiteSettingsToggle(); updateMenuEntries(); return; } @@ -174,6 +156,38 @@ updateZeroState(); } + @Override + public void onActionUpdated(int newIndex) { + ExtensionsMenuTypes.MenuEntryState entry = mMenuBridge.getMenuEntry(newIndex); + + // Find the old index for the model corresponding to the updated action. + int oldIndex = -1; + for (int i = 0; i < mActionModels.size(); i++) { + String modelId = + mActionModels.get(i).model.get(ExtensionsMenuItemProperties.EXTENSION_ID); + if (modelId.equals(entry.id)) { + oldIndex = i; + break; + } + } + assert oldIndex != -1 + : "Action model with ID " + entry.id + " should exist in mActionModels."; + + // Update the menu item model. + ListItem item = mActionModels.get(oldIndex); + item.model.set(ExtensionsMenuItemProperties.TITLE, entry.actionButton.text); + item.model.set(ExtensionsMenuItemProperties.ICON, entry.actionButton.icon); + + // Update position if the index changed. + if (oldIndex != newIndex) { + mActionModels.removeAt(oldIndex); + mActionModels.add(newIndex, item); + } + + // An action update can change the state of the site settings toggle. + updateSiteSettingsToggle(); + } + /** * Called when the native side is ready with the menu data, which can happen on mediator * construction or by an observer called originated from the native side. Populates the action @@ -194,8 +208,10 @@ private ListItem createMenuItem(ExtensionsMenuTypes.MenuEntryState entry) { boolean isActionPinned = entry.contextMenuButton.isOn; int contextMenuIcon = isActionPinned ? R.drawable.ic_keep_24dp : R.drawable.ic_more_vert; + PropertyModel model = new PropertyModel.Builder(ExtensionsMenuItemProperties.ALL_KEYS) + .with(ExtensionsMenuItemProperties.EXTENSION_ID, entry.id) .with(ExtensionsMenuItemProperties.TITLE, entry.actionButton.text) .with(ExtensionsMenuItemProperties.ICON, entry.actionButton.icon) .with( @@ -236,4 +252,19 @@ boolean isZeroState = mActionModels.size() == 0; mMenuPropertyModel.set(ExtensionsMenuProperties.IS_ZERO_STATE, isZeroState); } + + /** Updates the site settings toggle. */ + @VisibleForTesting + void updateSiteSettingsToggle() { + ExtensionsMenuTypes.SiteSettingsState siteSettingsState = + mMenuBridge.getSiteSettingsState(); + mMenuPropertyModel.set( + ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_VISIBLE, + siteSettingsState.toggle.status != ExtensionsMenuTypes.ControlState.Status.HIDDEN); + mMenuPropertyModel.set( + ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_CHECKED, + siteSettingsState.toggle.isOn); + mMenuPropertyModel.set( + ExtensionsMenuProperties.SITE_SETTINGS_LABEL, siteSettingsState.label); + } }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediatorTest.java index 00266c0..661416f 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediatorTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/extensions/ExtensionsMenuMediatorTest.java
@@ -124,7 +124,9 @@ ExtensionsMenuBridgeJni.setInstanceForTesting(mExtensionsMenuBridgeJniMock); // Mock site settings state. - mSiteSettingsState = createSiteSettingsState("label", true); + mSiteSettingsState = + createSiteSettingsState( + "label", ExtensionsMenuTypes.ControlState.Status.HIDDEN, /* isOn= */ false); when(mExtensionsMenuBridgeJniMock.getSiteSettings(anyLong())) .thenReturn(mSiteSettingsState); when(mExtensionsMenuBridgeJniMock.init(any(), anyLong())) @@ -372,6 +374,52 @@ } /** + * Tests that updating an extension action correctly updates its corresponding action model in + * the menu, updating its order if necessary. + */ + @Test + public void testOnActionUpdated() { + // Initialize the action models with two items. + List<ExtensionsMenuTypes.MenuEntryState> entries = new ArrayList<>(); + entries.add( + ExtensionTestUtils.createSimpleMenuEntry( + "id_a", "Extension A", ICON_RED, /* isPinned= */ false)); + entries.add( + ExtensionTestUtils.createSimpleMenuEntry( + "id_b", "Extension B", ICON_BLUE, /* isPinned= */ false)); + when(mExtensionsMenuBridgeJniMock.getMenuEntries(anyLong())).thenReturn(entries); + + // Open extensions menu by simulating the native callback triggering onReady. + mBridgeCaptor.getValue().onReady(); + + assertEquals(2, mActionModels.size()); + + // Simulate the native callback for Extension A updated, maintaining its current index. + ExtensionsMenuTypes.MenuEntryState updatedEntryA = + ExtensionTestUtils.createSimpleMenuEntry( + "id_a", "Extension A Updated", ICON_RED, /* isPinned= */ false); + when(mExtensionsMenuBridgeJniMock.getMenuEntry(anyLong(), eq(0))).thenReturn(updatedEntryA); + mBridgeCaptor.getValue().onActionUpdated(0); + + // Verify "Extension A" is updated at its current index. + assertEquals(2, mActionModels.size()); + assertItemAt(0, "Extension A Updated", ICON_RED, ICON_MORE); + assertItemAt(1, "Extension B", ICON_BLUE, ICON_MORE); + + // Simulate the native callback for Extension A updated, moving to a new index. + ExtensionsMenuTypes.MenuEntryState updatedEntryB = + ExtensionTestUtils.createSimpleMenuEntry( + "id_b", "Extension B Updated", ICON_BLUE, /* isPinned= */ false); + when(mExtensionsMenuBridgeJniMock.getMenuEntry(anyLong(), eq(0))).thenReturn(updatedEntryB); + mBridgeCaptor.getValue().onActionUpdated(0); + + // Verify "Extension B" moved to index 0 and "Extension A" moved to index 1. + assertEquals(2, mActionModels.size()); + assertItemAt(0, "Extension B Updated", ICON_BLUE, ICON_MORE); + assertItemAt(1, "Extension A Updated", ICON_RED, ICON_MORE); + } + + /** * Tests that clicking on the context menu button of an extension item opens the context menu. */ @Test @@ -420,16 +468,62 @@ return mockButton; } + /** + * Tests that site settings toggle is correctly updated based on the state provided by the + * native side. + */ @Test public void testUpdateSiteSettingsToggle() { - ExtensionsMenuTypes.SiteSettingsState siteSettingsState = - createSiteSettingsState("test_label", true); - - mMenuMediator.updateSiteSettingsToggle(siteSettingsState); + // Verify toggle properties when it should be visible and on. + ExtensionsMenuTypes.SiteSettingsState siteSettingsStateOn = + createSiteSettingsState( + "test_label", + ExtensionsMenuTypes.ControlState.Status.ENABLED, + /* isOn= */ true); + when(mExtensionsMenuBridgeJniMock.getSiteSettings(anyLong())) + .thenReturn(siteSettingsStateOn); + mMenuMediator.updateSiteSettingsToggle(); verify(mMenuPropertyModel).set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_VISIBLE, true); verify(mMenuPropertyModel).set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_CHECKED, true); verify(mMenuPropertyModel).set(ExtensionsMenuProperties.SITE_SETTINGS_LABEL, "test_label"); + + clearInvocations(mMenuPropertyModel); + + // Verify toggle properties when it should be visible and off. + ExtensionsMenuTypes.SiteSettingsState siteSettingsStateOff = + createSiteSettingsState( + "test_label_2", + ExtensionsMenuTypes.ControlState.Status.ENABLED, + /* isOn= */ false); + when(mExtensionsMenuBridgeJniMock.getSiteSettings(anyLong())) + .thenReturn(siteSettingsStateOff); + mMenuMediator.updateSiteSettingsToggle(); + + verify(mMenuPropertyModel).set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_VISIBLE, true); + verify(mMenuPropertyModel) + .set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_CHECKED, false); + verify(mMenuPropertyModel) + .set(ExtensionsMenuProperties.SITE_SETTINGS_LABEL, "test_label_2"); + + clearInvocations(mMenuPropertyModel); + + // Verify toggle properties when it should be hidden (doesn't matter if toggle is on/off). + ExtensionsMenuTypes.SiteSettingsState siteSettingsStateHidden = + createSiteSettingsState( + "test_label_3", + ExtensionsMenuTypes.ControlState.Status.HIDDEN, + /* isOn= */ false); + when(mExtensionsMenuBridgeJniMock.getSiteSettings(anyLong())) + .thenReturn(siteSettingsStateHidden); + mMenuMediator.updateSiteSettingsToggle(); + + verify(mMenuPropertyModel) + .set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_VISIBLE, false); + verify(mMenuPropertyModel) + .set(ExtensionsMenuProperties.SITE_SETTINGS_TOGGLE_CHECKED, false); + verify(mMenuPropertyModel) + .set(ExtensionsMenuProperties.SITE_SETTINGS_LABEL, "test_label_3"); } @Test @@ -463,10 +557,10 @@ } private ExtensionsMenuTypes.SiteSettingsState createSiteSettingsState( - String label, boolean isOn) { + String label, @ExtensionsMenuTypes.ControlState.Status int status, boolean isOn) { ExtensionsMenuTypes.ControlState toggleState = new ExtensionsMenuTypes.ControlState( - ExtensionsMenuTypes.ControlState.Status.ENABLED, + status, "toggle_text", "accessible_name", "tooltip",
diff --git a/chrome/browser/ui/autofill/BUILD.gn b/chrome/browser/ui/autofill/BUILD.gn index cf59d182..3066979 100644 --- a/chrome/browser/ui/autofill/BUILD.gn +++ b/chrome/browser/ui/autofill/BUILD.gn
@@ -163,6 +163,7 @@ "autofill_client_provider.cc", "autofill_client_provider_factory.cc", "autofill_popup_hide_helper.cc", + "autofill_popup_view.cc", "autofill_suggestion_controller.cc", "autofill_suggestion_controller_utils.cc", "chrome_autofill_client.cc",
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc index cf18bea..b27c5a5 100644 --- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc +++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -265,10 +265,20 @@ .no_results_message = l10n_util::GetStringUTF16( IDS_AUTOFILL_POPUP_SEARCH_BAR_PASSWORDS_NOT_FOUND)}) : std::nullopt; + auto tabbed_pane_config = + controller_common_.show_tabbed_popup + ? std::make_optional<AutofillPopupView::TabbedPaneConfig>( + std::vector<AutofillPopupView::TabbedPaneConfig::Tab>{ + {AutofillPopupView::TabbedPaneConfig::TabType::kPayNow, + l10n_util::GetStringUTF16(IDS_AUTOFILL_PAY_NOW)}, + {AutofillPopupView::TabbedPaneConfig::TabType::kPayLater, + l10n_util::GetStringUTF16(IDS_AUTOFILL_PAY_LATER)}}) + : std::nullopt; view_ = has_parent ? parent_controller_->get()->CreateSubPopupView(GetWeakPtr()) : AutofillPopupView::Create(GetWeakPtr(), - std::move(search_bar_config)); + std::move(search_bar_config), + std::move(tabbed_pane_config)); // It is possible to fail to create the popup, in this case // treat the popup as hiding right away.
diff --git a/chrome/browser/ui/autofill/autofill_popup_view.cc b/chrome/browser/ui/autofill/autofill_popup_view.cc new file mode 100644 index 0000000..7f610de --- /dev/null +++ b/chrome/browser/ui/autofill/autofill_popup_view.cc
@@ -0,0 +1,28 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/autofill/autofill_popup_view.h" + +namespace autofill { + +AutofillPopupView::TabbedPaneConfig::TabbedPaneConfig( + std::vector<TabbedPaneConfig::Tab> tabs) + : tabs(std::move(tabs)) {} + +AutofillPopupView::TabbedPaneConfig::TabbedPaneConfig(const TabbedPaneConfig&) = + default; + +AutofillPopupView::TabbedPaneConfig::TabbedPaneConfig(TabbedPaneConfig&&) = + default; + +AutofillPopupView::TabbedPaneConfig& +AutofillPopupView::TabbedPaneConfig::operator=(const TabbedPaneConfig&) = + default; + +AutofillPopupView::TabbedPaneConfig& +AutofillPopupView::TabbedPaneConfig::operator=(TabbedPaneConfig&&) = default; + +AutofillPopupView::TabbedPaneConfig::~TabbedPaneConfig() = default; + +} // namespace autofill
diff --git a/chrome/browser/ui/autofill/autofill_popup_view.h b/chrome/browser/ui/autofill/autofill_popup_view.h index a9dbfd6..475d4a1 100644 --- a/chrome/browser/ui/autofill/autofill_popup_view.h +++ b/chrome/browser/ui/autofill/autofill_popup_view.h
@@ -21,6 +21,10 @@ // The interface for creating and controlling a platform-dependent // AutofillPopupView. +// TODO(crbug.com/477689220): Check if this interface is still needed, and if +// not, deprecate it. This interface is only implemented by `PopupViewViews` and +// thus is no longer platform-dependent. The struct definitions can then be +// moved to `PopupViewViews`. class AutofillPopupView { public: struct SearchBarConfig { @@ -28,11 +32,39 @@ std::u16string no_results_message; }; - // Factory function for creating the view. Providing `std::nullopt` to - // the `search_bar_config` results in creating a popup without a search bar. + // Configuration for displaying a tabbed pane within the Autofill popup. + struct TabbedPaneConfig { + enum class TabType { + kPayNow = 0, + kPayLater = 1, + }; + + // Represents a single tab to be rendered in the tabbed pane. + struct Tab { + TabType type; + std::u16string title; + }; + + explicit TabbedPaneConfig(std::vector<TabbedPaneConfig::Tab> tabs); + TabbedPaneConfig(const TabbedPaneConfig&); + TabbedPaneConfig(TabbedPaneConfig&&); + TabbedPaneConfig& operator=(const TabbedPaneConfig&); + TabbedPaneConfig& operator=(TabbedPaneConfig&&); + ~TabbedPaneConfig(); + + // The ordered list of tabs that should be displayed in the tabbed pane. + std::vector<Tab> tabs; + }; + + // Factory function for creating the view. + // `search_bar_config` will be used to create a popup with a search bar, if + // present. + // `tabbed_pane_config` will be used to create a popup with a tabbed pane, + // if present. static base::WeakPtr<AutofillPopupView> Create( base::WeakPtr<AutofillSuggestionController> controller, - std::optional<const SearchBarConfig> search_bar_config = std::nullopt); + std::optional<const SearchBarConfig> search_bar_config = std::nullopt, + std::optional<const TabbedPaneConfig> tabbed_pane_config = std::nullopt); // Attempts to display the Autofill popup and fills it with data from the // controller. Returns whether the popup was shown.
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc index 52d55aa..66e96715 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client.cc +++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -1305,9 +1305,10 @@ // Deletes or reuses the old `suggestion_controller_`. suggestion_controller_ = AutofillSuggestionController::GetOrCreate( suggestion_controller_, delegate, web_contents(), - PopupControllerCommon( - element_bounds_in_screen_space, open_args.text_direction, - web_contents()->GetNativeView(), open_args.anchor_type), + PopupControllerCommon(element_bounds_in_screen_space, + open_args.text_direction, + web_contents()->GetNativeView(), + open_args.anchor_type, open_args.show_tabbed_popup), open_args.form_control_ax_id); suggestion_controller_->Show(
diff --git a/chrome/browser/ui/autofill/popup_controller_common.cc b/chrome/browser/ui/autofill/popup_controller_common.cc index 3691593..dc6776cb 100644 --- a/chrome/browser/ui/autofill/popup_controller_common.cc +++ b/chrome/browser/ui/autofill/popup_controller_common.cc
@@ -14,11 +14,13 @@ gfx::RectF element_bounds, base::i18n::TextDirection text_direction, gfx::NativeView container_view, - PopupAnchorType anchor_type) + PopupAnchorType anchor_type, + bool show_tabbed_popup) : element_bounds(std::move(element_bounds)), text_direction(text_direction), container_view(container_view), - anchor_type(anchor_type) {} + anchor_type(anchor_type), + show_tabbed_popup(show_tabbed_popup) {} PopupControllerCommon::PopupControllerCommon(const PopupControllerCommon&) = default;
diff --git a/chrome/browser/ui/autofill/popup_controller_common.h b/chrome/browser/ui/autofill/popup_controller_common.h index f631da2e..d3d3e4c2 100644 --- a/chrome/browser/ui/autofill/popup_controller_common.h +++ b/chrome/browser/ui/autofill/popup_controller_common.h
@@ -19,7 +19,8 @@ PopupControllerCommon(gfx::RectF element_bounds, base::i18n::TextDirection text_direction, gfx::NativeView container_view, - PopupAnchorType anchor_type = PopupAnchorType::kField); + PopupAnchorType anchor_type = PopupAnchorType::kField, + bool show_tabbed_popup = false); PopupControllerCommon(const PopupControllerCommon&); PopupControllerCommon(PopupControllerCommon&&); PopupControllerCommon& operator=(const PopupControllerCommon&); @@ -39,6 +40,9 @@ // The type of the element to anchor the popup on. PopupAnchorType anchor_type; + + // True if the popup should contain a tabbed pane. + bool show_tabbed_popup; }; } // namespace autofill
diff --git a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java index 1db4e1a..57132f8 100644 --- a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java +++ b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImpl.java
@@ -148,6 +148,7 @@ * different Profile. */ final Map<Profile, AndroidBrowserWindow> mAndroidBrowserWindows = new ArrayMap<>(); + @Nullable IncognitoTabModelObserver mIncognitoTabModelObserver; InternalActivityScopedObjects( ActivityScopedObjects activityScopedObjects, AndroidBrowserWindow browserWindow) { @@ -182,6 +183,30 @@ + " AndroidBrowserWindow"; mAndroidBrowserWindows.put(profile, browserWindow); } + + void addIncognitoTabModelObserver(ChromeAndroidTaskImpl chromeAndroidTaskImpl) { + assert mIncognitoTabModelObserver == null + : "mIncognitoTabModelObserver is already initialized."; + + var incognitoModel = + (IncognitoTabModel) + mActivityScopedObjects.mTabModelSelector.getModel( + /* incognito= */ true); + mIncognitoTabModelObserver = + new IncognitoTabModelObserverImpl(chromeAndroidTaskImpl, this); + incognitoModel.addIncognitoObserver(mIncognitoTabModelObserver); + } + + void removeIncognitoTabModelObserver() { + if (mIncognitoTabModelObserver != null) { + var incognitoModel = + (IncognitoTabModel) + mActivityScopedObjects.mTabModelSelector.getModel( + /* incognito= */ true); + incognitoModel.removeIncognitoObserver(mIncognitoTabModelObserver); + mIncognitoTabModelObserver = null; + } + } } /** @@ -339,43 +364,45 @@ private final Callback<TabModel> mOnTabModelSelectedCallback = this::onTabModelSelected; - // TODO(crbug.com/486858979): Attach this listener to all Activities. - // See the comments in cl/7560711 for more details. - private final IncognitoTabModelObserver mIncognitoTabModelObserver = - new IncognitoTabModelObserver() { - @Override - public void onIncognitoModelCreated() { - var internalActivityScopedObjects = mActivityScopedObjectsDeque.peekFirst(); - assert internalActivityScopedObjects != null - : "InternalActivityScopedObjects should not be null since the" - + " mIncognitoTabModelObserver is registered."; - var incognitoModel = - internalActivityScopedObjects.mActivityScopedObjects.mTabModelSelector - .getModel(/* incognito= */ true); + private static final class IncognitoTabModelObserverImpl implements IncognitoTabModelObserver { + private final ChromeAndroidTaskImpl mChromeAndroidTaskImpl; + private final InternalActivityScopedObjects mInternalActivityScopedObjects; - var incognitoProfile = incognitoModel.getProfile(); - assert incognitoProfile != null : "Incognito profile should not be null."; - assert internalActivityScopedObjects.mAndroidBrowserWindows.get( - incognitoProfile) - == null - : "Incognito TabModel created, but its Activity already has the" - + " incognito Profile"; + IncognitoTabModelObserverImpl( + ChromeAndroidTaskImpl chromeAndroidTaskImpl, + InternalActivityScopedObjects internalActivityScopedObjects) { + mChromeAndroidTaskImpl = chromeAndroidTaskImpl; + mInternalActivityScopedObjects = internalActivityScopedObjects; + } - var browserWindow = - new AndroidBrowserWindow( - ChromeAndroidTaskImpl.this, - incognitoProfile, - internalActivityScopedObjects - .mActivityScopedObjects - .mActivityWindowAndroid); - internalActivityScopedObjects.addBrowserWindow(browserWindow); - long ptr = browserWindow.getOrCreateNativePtr(); - incognitoModel.associateWithBrowserWindow(ptr); - for (var observer : mAndroidBrowserWindowObservers) { - observer.onBrowserWindowAdded(ptr); - } - } - }; + @Override + public void onIncognitoModelCreated() { + var incognitoModel = + mInternalActivityScopedObjects.mActivityScopedObjects.mTabModelSelector + .getModel(/* incognito= */ true); + + var incognitoProfile = incognitoModel.getProfile(); + assert incognitoProfile != null : "Incognito profile should not be null."; + assert mInternalActivityScopedObjects.mAndroidBrowserWindows.get(incognitoProfile) + == null + : "Incognito TabModel created, but its Activity already has the" + + " incognito Profile"; + + var browserWindow = + new AndroidBrowserWindow( + mChromeAndroidTaskImpl, + incognitoProfile, + mInternalActivityScopedObjects + .mActivityScopedObjects + .mActivityWindowAndroid); + mInternalActivityScopedObjects.addBrowserWindow(browserWindow); + long ptr = browserWindow.getOrCreateNativePtr(); + incognitoModel.associateWithBrowserWindow(ptr); + for (var observer : mChromeAndroidTaskImpl.mAndroidBrowserWindowObservers) { + observer.onBrowserWindowAdded(ptr); + } + } + } private @Nullable Integer mId; private long mLastActivatedTimeMillis; @@ -1316,6 +1343,10 @@ new InternalActivityScopedObjects(activityScopedObjects, newBrowserWindow); mActivityScopedObjectsDeque.addFirst(internalActivityScopedObjects); + if (activityScopedObjects.mSupportedProfileType == SupportedProfileType.MIXED) { + internalActivityScopedObjects.addIncognitoTabModelObserver(this); + } + // Notify observers of new window creation. long ptr = newBrowserWindow.getOrCreateNativePtr(); for (var observer : mAndroidBrowserWindowObservers) { @@ -1372,11 +1403,6 @@ } TabModelSelector tabModelSelector = topActivityScopedObjects.mTabModelSelector; - if (topActivityScopedObjects.mSupportedProfileType == SupportedProfileType.MIXED) { - var incognitoModel = - (IncognitoTabModel) tabModelSelector.getModel(/* incognito= */ true); - incognitoModel.addIncognitoObserver(mIncognitoTabModelObserver); - } tabModelSelector .getCurrentTabModelSupplier() @@ -1416,11 +1442,6 @@ var tabModelSelector = topActivityScopedObjects.mTabModelSelector; if (tabModelSelector != null) { - if (topActivityScopedObjects.mSupportedProfileType == SupportedProfileType.MIXED) { - var incognitoTabModel = - (IncognitoTabModel) tabModelSelector.getModel(/* incognito= */ true); - incognitoTabModel.removeIncognitoObserver(mIncognitoTabModelObserver); - } tabModelSelector .getCurrentTabModelSupplier() .removeObserver(mOnTabModelSelectedCallback); @@ -1516,6 +1537,8 @@ // Remove from Deque. mActivityScopedObjectsDeque.remove(activityScopedObjectsToRemove); + // Handle observers. + activityScopedObjectsToRemove.removeIncognitoTabModelObserver(); // Remove task features. removeAllFeaturesForActivityInternal(activityWindowAndroid); // Destroy associated windows. @@ -1545,6 +1568,7 @@ while (!mActivityScopedObjectsDeque.isEmpty()) { var internalActivityScopedObjects = mActivityScopedObjectsDeque.pollFirst(); + internalActivityScopedObjects.removeIncognitoTabModelObserver(); removeAllFeaturesForActivityInternal( internalActivityScopedObjects.mActivityScopedObjects.mActivityWindowAndroid); var windows =
diff --git a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImplUnitTest.java b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImplUnitTest.java index 6dffdfd..18b92fe 100644 --- a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImplUnitTest.java +++ b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskImplUnitTest.java
@@ -701,6 +701,7 @@ @Test public void removeActivityScopedObjects_mixedProfile_destroysBothRegularAndIncognitoWindows() { + assumeFalse(BuildConfig.IS_DESKTOP_ANDROID); // Arrange: Create Task with Mixed Profile support. var chromeAndroidTaskWithMockDeps = ChromeAndroidTaskUnitTestSupport.createChromeAndroidTaskWithMockDeps( @@ -807,6 +808,37 @@ } @Test + public void removeActivityScopedObjects_mixedProfile_removesIncognitoObserver() { + assumeFalse(BuildConfig.IS_DESKTOP_ANDROID); + // Arrange: Create Task with Mixed Profile support. + var chromeAndroidTaskWithMockDeps = + ChromeAndroidTaskUnitTestSupport.createChromeAndroidTaskWithMockDeps( + /* taskId= */ 1, + /* mockNatives= */ true, + /* isPendingTask= */ false, + /* isDesktopMode= */ true, + SupportedProfileType.MIXED); + var chromeAndroidTask = + (ChromeAndroidTaskImpl) chromeAndroidTaskWithMockDeps.mChromeAndroidTask; + var activityScopedObjects = chromeAndroidTaskWithMockDeps.mActivityScopedObjects; + var tabModelSelector = activityScopedObjects.mTabModelSelector; + var incognitoModel = (IncognitoTabModel) tabModelSelector.getModel(true); + + // Capture the observer that was registered during initialization. + ArgumentCaptor<IncognitoTabModelObserver> captor = + ArgumentCaptor.forClass(IncognitoTabModelObserver.class); + verify(incognitoModel).addIncognitoObserver(captor.capture()); + IncognitoTabModelObserver registeredObserver = captor.getValue(); + assertNotNull(registeredObserver); + + // Act: Remove the ActivityScopedObjects. + chromeAndroidTask.removeActivityScopedObjects(activityScopedObjects.mActivityWindowAndroid); + + // Assert: The exact observer that was added should be removed. + verify(incognitoModel, times(1)).removeIncognitoObserver(registeredObserver); + } + + @Test public void removeActivityScopedObjects_dissociatesTabModelBeforeDestroyingNativeWindow() { // Arrange var chromeAndroidTaskWithMockDeps = createChromeAndroidTaskWithMockDeps(1); @@ -1123,6 +1155,37 @@ } @Test + public void destroy_mixedProfile_removesIncognitoObserver() { + assumeFalse(BuildConfig.IS_DESKTOP_ANDROID); + // Arrange: Create Task with Mixed Profile support. + var chromeAndroidTaskWithMockDeps = + ChromeAndroidTaskUnitTestSupport.createChromeAndroidTaskWithMockDeps( + /* taskId= */ 1, + /* mockNatives= */ true, + /* isPendingTask= */ false, + /* isDesktopMode= */ true, + SupportedProfileType.MIXED); + var chromeAndroidTask = + (ChromeAndroidTaskImpl) chromeAndroidTaskWithMockDeps.mChromeAndroidTask; + var activityScopedObjects = chromeAndroidTaskWithMockDeps.mActivityScopedObjects; + var tabModelSelector = activityScopedObjects.mTabModelSelector; + var incognitoModel = (IncognitoTabModel) tabModelSelector.getModel(true); + + // Capture the observer that was registered during initialization. + ArgumentCaptor<IncognitoTabModelObserver> captor = + ArgumentCaptor.forClass(IncognitoTabModelObserver.class); + verify(incognitoModel).addIncognitoObserver(captor.capture()); + IncognitoTabModelObserver registeredObserver = captor.getValue(); + assertNotNull(registeredObserver); + + // Act: Destroy the entire task. + chromeAndroidTask.destroy(); + + // Assert: The observer should be removed during the teardown of ActivityScopedObjects. + verify(incognitoModel, times(1)).removeIncognitoObserver(registeredObserver); + } + + @Test public void destroy_destroysAndroidBrowserWindow() { // Arrange. var chromeAndroidTaskWithMockDeps = createChromeAndroidTaskWithMockDeps(/* taskId= */ 1); @@ -3227,9 +3290,7 @@ // TODO(crbug.com/479566813): Until clients are ported to properly destroy themselves on // profile // destruction, this test needs to be disabled. - if (BuildConfig.IS_DESKTOP_ANDROID) { - return; - } + assumeFalse(BuildConfig.IS_DESKTOP_ANDROID); // Arrange var chromeAndroidTaskWithMockDeps =
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc index f1f8633..793cfebb 100644 --- a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc +++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
@@ -506,14 +506,9 @@ } void CommerceUiTabHelper::UpdateDiscountsIconView() { - if (IsPageActionMigrated(PageActionIconType::kDiscounts)) { - DiscountsPageActionViewController::From(tab())->UpdatePageIcon( - ShouldShowDiscountsIconView(), - ShouldExpandPageActionIcon(PageActionIconType::kDiscounts)); - return; - } - - UpdatePageActionIconView(PageActionIconType::kDiscounts); + DiscountsPageActionViewController::From(tab())->UpdatePageIcon( + ShouldShowDiscountsIconView(), + ShouldExpandPageActionIcon(PageActionIconType::kDiscounts)); } const DiscountsBubbleCoordinator&
diff --git a/chrome/browser/ui/desktop_to_mobile_promos/BUILD.gn b/chrome/browser/ui/desktop_to_mobile_promos/BUILD.gn index 0da219c..7897dfa 100644 --- a/chrome/browser/ui/desktop_to_mobile_promos/BUILD.gn +++ b/chrome/browser/ui/desktop_to_mobile_promos/BUILD.gn
@@ -48,6 +48,7 @@ ":service", ":utils", "//chrome/browser:browser_process", + "//chrome/browser/desktop_to_mobile_promos:utils", "//chrome/browser/profiles:profile", "//chrome/browser/ui/browser_window", "//chrome/browser/ui/user_education", @@ -135,6 +136,7 @@ "//base", "//base/test:test_support", "//chrome/browser:browser_process", + "//chrome/browser/desktop_to_mobile_promos:utils", "//chrome/browser/feature_engagement", "//chrome/browser/profiles:profile", "//chrome/browser/sync",
diff --git a/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller.cc b/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller.cc index e600e74d4..9a9ed8f7 100644 --- a/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller.cc +++ b/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller.cc
@@ -6,6 +6,7 @@ #include "base/functional/bind.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/desktop_to_mobile_promos/promos_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" @@ -124,6 +125,13 @@ return false; } + // Verify that the user has not exceeded impression limits for + // desktop-to-mobile promos. + if (!promos_utils::IsIOSDesktopPromoAllowedByGlobalImpressions( + browser_->profile())) { + return false; + } + MobilePromoOnDesktopPromoType feature_type; switch (promo_type) { case PromoType::kPassword:
diff --git a/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller_browsertest.cc b/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller_browsertest.cc index a7c2d22..e59b2d8 100644 --- a/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller_browsertest.cc +++ b/chrome/browser/ui/desktop_to_mobile_promos/ios_promo_controller_browsertest.cc
@@ -7,6 +7,7 @@ #include "base/memory/raw_ptr.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" +#include "chrome/browser/desktop_to_mobile_promos/promos_pref_names.h" #include "chrome/browser/feature_engagement/tracker_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/device_info_sync_service_factory.h" @@ -23,6 +24,7 @@ #include "components/desktop_to_mobile_promos/promos_types.h" #include "components/feature_engagement/public/feature_constants.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/prefs/pref_service.h" #include "components/sync/test/test_sync_service.h" #include "components/sync_device_info/device_info.h" #include "components/sync_device_info/fake_device_info_sync_service.h" @@ -178,3 +180,54 @@ // Trigger the promo. promo_service()->NotifyPromoShouldBeShown(PromoType::kPassword); } + +// Verifies that the promo is not shown when the promo is in the cooldown +// period. +IN_PROC_BROWSER_TEST_F(IOSPromoControllerBrowserTest, + ShowPromo_BlockedByCooldown) { + // Add a device with receiving enabled. + device_info_tracker()->Add( + CreateDeviceInfo("guid1", syncer::DeviceInfo::OsType::kIOS, + syncer::DeviceInfo::FormFactor::kPhone, true)); + + // Set the last impression timestamp to now. + browser()->profile()->GetPrefs()->SetTime( + promos_prefs::kDesktopToiOSPasswordPromoLastImpressionTimestamp, + base::Time::Now()); + + views::Widget* widget = + BrowserView::GetBrowserViewForBrowser(browser())->GetWidget(); + views::test::WidgetTest::SimulateNativeActivate(widget); + views::test::WaitForWidgetActive(widget, true); + + EXPECT_CALL(*mock_user_education_interface(), MaybeShowFeaturePromo(_)) + .Times(0); + + // Trigger the promo. + promo_service()->NotifyPromoShouldBeShown(PromoType::kPassword); +} + +// Verifies that the promo is not shown when the user has seen too many +// impressions. +IN_PROC_BROWSER_TEST_F(IOSPromoControllerBrowserTest, + ShowPromo_BlockedByImpressionLimit) { + // Add a device with receiving enabled. + device_info_tracker()->Add( + CreateDeviceInfo("guid1", syncer::DeviceInfo::OsType::kIOS, + syncer::DeviceInfo::FormFactor::kPhone, true)); + + // Set the impression count to the maximum. + browser()->profile()->GetPrefs()->SetInteger( + promos_prefs::kDesktopToiOSPasswordPromoImpressionsCounter, 10); + + views::Widget* widget = + BrowserView::GetBrowserViewForBrowser(browser())->GetWidget(); + views::test::WidgetTest::SimulateNativeActivate(widget); + views::test::WaitForWidgetActive(widget, true); + + EXPECT_CALL(*mock_user_education_interface(), MaybeShowFeaturePromo(_)) + .Times(0); + + // Trigger the promo. + promo_service()->NotifyPromoShouldBeShown(PromoType::kPassword); +}
diff --git a/chrome/browser/ui/digital_credentials/digital_identity_safety_interstitial_bridge_android.cc b/chrome/browser/ui/digital_credentials/digital_identity_safety_interstitial_bridge_android.cc index 17600668..9f4c2e6 100644 --- a/chrome/browser/ui/digital_credentials/digital_identity_safety_interstitial_bridge_android.cc +++ b/chrome/browser/ui/digital_credentials/digital_identity_safety_interstitial_bridge_android.cc
@@ -39,9 +39,9 @@ callback) { callback_ = std::move(callback); JNIEnv* env = AttachCurrentThread(); - ScopedJavaLocalRef<jobject> j_origin = origin.ToJavaObject(env); + ScopedJavaLocalRef<url::JOrigin> j_origin = origin.ToJavaObject(env); - base::android::ScopedJavaLocalRef<jobject> j_window = nullptr; + base::android::ScopedJavaLocalRef<ui::JWindowAndroid> j_window = nullptr; if (web_contents.GetTopLevelNativeWindow()) { j_window = web_contents.GetTopLevelNativeWindow()->GetJavaObject(); }
diff --git a/chrome/browser/ui/extensions/extensions_menu_view_model.cc b/chrome/browser/ui/extensions/extensions_menu_view_model.cc index f02db60e..9daf31d 100644 --- a/chrome/browser/ui/extensions/extensions_menu_view_model.cc +++ b/chrome/browser/ui/extensions/extensions_menu_view_model.cc
@@ -1224,9 +1224,13 @@ // name is set on the manifest and shouldn't dynamically change. std::sort(action_models_.begin(), action_models_.end(), SortActionsByName); + // Find the new index of the action. + std::optional<int> index = GetActionIndex(action_id); + CHECK(index); + // Notify observers. for (Observer& observer : observers_) { - observer.OnActionUpdated(action_id); + observer.OnActionUpdated(action_id, *index); } }
diff --git a/chrome/browser/ui/extensions/extensions_menu_view_model.h b/chrome/browser/ui/extensions/extensions_menu_view_model.h index d3ea546..90f9dc6 100644 --- a/chrome/browser/ui/extensions/extensions_menu_view_model.h +++ b/chrome/browser/ui/extensions/extensions_menu_view_model.h
@@ -6,6 +6,7 @@ #define CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_MENU_VIEW_MODEL_H_ #include <optional> +#include <string> #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" @@ -94,9 +95,10 @@ // icon updates because (a) icons are loaded asynchronously and (b) they // only require updating the icon and no other fields (e.g an action update // can include a permissions change which affects other views apart from the - // action menu entry). - virtual void OnActionUpdated( - const ToolbarActionsModel::ActionId& action_id) = 0; + // action menu entry). An action update could affect the action order in the + // menu entries. Therefore, we pass the new `index`. + virtual void OnActionUpdated(const ToolbarActionsModel::ActionId& action_id, + int index) = 0; // Called when an action icon is updated. virtual void OnActionIconUpdated(
diff --git a/chrome/browser/ui/extensions/settings_overridden_dialog.cc b/chrome/browser/ui/extensions/settings_overridden_dialog.cc index 6f3f0095..a0f7ba7 100644 --- a/chrome/browser/ui/extensions/settings_overridden_dialog.cc +++ b/chrome/browser/ui/extensions/settings_overridden_dialog.cc
@@ -176,7 +176,7 @@ }; extensions::AddExplicitChoiceRadioButtons( - dialog_builder, show_params.previous_setting.value(), + dialog_builder, show_params.message, show_params.previous_setting.value(), kSettingsOverriddenDialogPreviousSettingButtonId, create_selection_callback(DialogResult::kChangeSettingsBack), show_params.new_setting.value(),
diff --git a/chrome/browser/ui/omnibox/BUILD.gn b/chrome/browser/ui/omnibox/BUILD.gn index 9e3bc50..1cd32971 100644 --- a/chrome/browser/ui/omnibox/BUILD.gn +++ b/chrome/browser/ui/omnibox/BUILD.gn
@@ -115,6 +115,7 @@ "//chrome/browser/extensions/api/omnibox", "//chrome/browser/favicon", "//chrome/browser/history", + "//chrome/browser/history_embeddings", "//chrome/browser/omnibox", "//chrome/browser/predictors", "//chrome/browser/preloading",
diff --git a/chrome/browser/ui/page_action/page_action_icon_type.cc b/chrome/browser/ui/page_action/page_action_icon_type.cc index bec103ff..753cd5cb 100644 --- a/chrome/browser/ui/page_action/page_action_icon_type.cc +++ b/chrome/browser/ui/page_action/page_action_icon_type.cc
@@ -18,8 +18,6 @@ return &features::kPageActionsMigrationZoom; case PageActionIconType::kFileSystemAccess: return &features::kPageActionsMigrationFileSystemAccess; - case PageActionIconType::kDiscounts: - return &features::kPageActionsMigrationDiscounts; case PageActionIconType::kManagePasswords: return &features::kPageActionsMigrationManagePasswords; case PageActionIconType::kCookieControls: @@ -76,6 +74,7 @@ case PageActionIconType::kIndigo: case PageActionIconType::kRecordReplay: case PageActionIconType::kPriceInsights: + case PageActionIconType::kDiscounts: return true; default: break;
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc index 50faa6f..eb8590d 100644 --- a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc +++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
@@ -256,7 +256,36 @@ const webapps::AppId* app_id = web_app::WebAppTabHelper::GetAppId(web_contents_); return app_id && provider->registrar_unsafe().AppMatches( - *app_id, web_app::WebAppFilter::IsIsolatedApp()); + *app_id, web_app::WebAppFilter::IsIsolatedApp() | + web_app::WebAppFilter::IsIsolatedSubApp()); +} + +bool ChromePageInfoDelegate::IsSubApp() { + CHECK(web_contents_); + web_app::WebAppProvider* provider = + web_app::WebAppProvider::GetForWebContents(web_contents_); + if (!provider) { + return false; + } + + const webapps::AppId* app_id = + web_app::WebAppTabHelper::GetAppId(web_contents_); + return app_id && provider->registrar_unsafe().AppMatches( + *app_id, web_app::WebAppFilter::IsIsolatedSubApp()); +} + +bool ChromePageInfoDelegate::HasSubApps() { + CHECK(web_contents_); + web_app::WebAppProvider* provider = + web_app::WebAppProvider::GetForWebContents(web_contents_); + if (!provider) { + return false; + } + + const webapps::AppId* app_id = + web_app::WebAppTabHelper::GetAppId(web_contents_); + return app_id && + !provider->registrar_unsafe().GetAllSubAppIds(*app_id).empty(); } void ChromePageInfoDelegate::ShowSiteSettings(const GURL& site_url) {
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_delegate.h index 68f7476..1d87251 100644 --- a/chrome/browser/ui/page_info/chrome_page_info_delegate.h +++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
@@ -60,6 +60,8 @@ std::unique_ptr<content_settings::CookieControlsController> CreateCookieControlsController() override; bool IsIsolatedWebApp() override; + bool IsSubApp() override; + bool HasSubApps() override; // In Chrome's case, this may show the site settings page or an app settings // page, depending on context. void ShowSiteSettings(const GURL& site_url) override;
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h index 9e45b6c..085f6f5b 100644 --- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h +++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
@@ -92,6 +92,8 @@ void RecordMerchantTrustButtonShown(); void RecordMerchantTrustSidePanelOpened(); + content::WebContents* GetWebContents() const { return web_contents_; } + private: Profile* GetProfile() const;
diff --git a/chrome/browser/ui/side_ui/internal/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinatorImpl.java b/chrome/browser/ui/side_ui/internal/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinatorImpl.java index 6a76fd7..7c44d89 100644 --- a/chrome/browser/ui/side_ui/internal/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinatorImpl.java +++ b/chrome/browser/ui/side_ui/internal/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinatorImpl.java
@@ -39,6 +39,8 @@ mEndAnchorContainer = (ViewGroup) endAnchorContainerStub.inflate(); } + // SideUiCoordinator Implementation + @Override public void registerSideUiContainer(SideUiContainer sideUiContainer) { assert mSideUiContainer == null : "Registering a SideUiContainer when already set."; @@ -52,20 +54,6 @@ } @Override - public void addObserver(SideUiObserver observer) { - if (mSideUiObservers.addObserver(observer)) { - observer.onSideUiSpecsChanged(getCurrentSideUiSpecs()); - } - } - - @Override - public void removeObserver(SideUiObserver observer) { - if (mSideUiObservers.removeObserver(observer)) { - observer.onSideUiSpecsChanged(SideUiSpecs.EMPTY_SIDE_UI_SPECS); - } - } - - @Override public void requestUpdateContainer(SideUiContainerProperties properties) { assert mSideUiContainer != null : "#requestUpdateContainer called with null SideUiContainer."; @@ -105,6 +93,33 @@ if (mSideUiContainer != null) unregisterSideUiContainer(mSideUiContainer); } + // SideUiStateProvider Implementation + + @Override + public void addObserver(SideUiObserver observer) { + if (mSideUiObservers.addObserver(observer)) { + observer.onSideUiSpecsChanged(getCurrentSideUiSpecs()); + } + } + + @Override + public void removeObserver(SideUiObserver observer) { + if (mSideUiObservers.removeObserver(observer)) { + observer.onSideUiSpecsChanged(SideUiSpecs.EMPTY_SIDE_UI_SPECS); + } + } + + @Override + public SideUiSpecs getCurrentSideUiSpecs() { + // Infers based on measuring the two parent containers. Should not be used in + // #requestUpdateContainer as that notifies Observers of the updated SideUiSpecs before any + // UI changes are actually made. + mStartAnchorContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + mEndAnchorContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + return new SideUiSpecs( + mStartAnchorContainer.getMeasuredWidth(), mEndAnchorContainer.getMeasuredWidth()); + } + /** * Attach the provided {@link SideUiContainer}'s {@link View} to its appropriate ViewGroup * determined by the {@link AnchorSide}. @@ -188,20 +203,6 @@ } } - /** - * Returns the current {@link SideUiSpecs}. Infers based on measuring the two parent containers. - * Should not be used in {@link #requestUpdateContainer} as that notifies Observers of the - * updated {@link SideUiSpecs} before any UI changes are actually made. - * - * @return The current {@link SideUiSpecs}. - */ - private SideUiSpecs getCurrentSideUiSpecs() { - mStartAnchorContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - mEndAnchorContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - return new SideUiSpecs( - mStartAnchorContainer.getMeasuredWidth(), mEndAnchorContainer.getMeasuredWidth()); - } - // Test Support @Nullable SideUiContainer getSideUiContainerForTesting() {
diff --git a/chrome/browser/ui/side_ui/public/BUILD.gn b/chrome/browser/ui/side_ui/public/BUILD.gn index b530571..1a773f55 100644 --- a/chrome/browser/ui/side_ui/public/BUILD.gn +++ b/chrome/browser/ui/side_ui/public/BUILD.gn
@@ -16,6 +16,7 @@ "android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiContainer.java", "android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinator.java", "android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiObserver.java", + "android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiStateProvider.java", ] deps = [ "//build/android:build_java",
diff --git a/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinator.java b/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinator.java index f7ae0b2..9ad7155 100644 --- a/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinator.java +++ b/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiCoordinator.java
@@ -17,7 +17,7 @@ * or right side of the main browser window. */ @NullMarked -public interface SideUiCoordinator { +public interface SideUiCoordinator extends SideUiStateProvider { /** * The side of the window ({@link #START} or {@link #END}) that a {@link SideUiContainer} will @@ -47,7 +47,8 @@ * POD-type that holds the info about the Side UI specs to be used by a {@link SideUiObserver}. * Specifically, this holds the widths (in px) for the two parent ViewGroups (one for * start-anchored UI and one for end-anchored UI) that hold a {@link SideUiContainer}'s View, - * based on the SideUiContainer's specified {@link AnchorSide}. + * based on the SideUiContainer's specified {@link AnchorSide}. TODO(crbug.com/489808658): Move + * to SideUiStateProvider. */ final class SideUiSpecs { /** A {@link SideUiSpecs} with a startX and endX of 0. */ @@ -95,37 +96,6 @@ void unregisterSideUiContainer(SideUiContainer sideUiContainer); /** - * Adds a {@link SideUiObserver}. The provided observer will be notified whenever a new {@link - * SideUiSpecs} is determined as a result of a change in a registered {@link SideUiContainer}. - * - * <p>Upon being added, the provided observer will also be notified of the current state of the - * side UI so that it may immediately position itself accordingly. e.g. This accounts for the - * case where an observer registers itself while some {@link SideUiContainer} is already showing - * and there is no incoming update request that would trigger a notification for observers. - * - * <p>This is no-op (including being notified of the current {@link SideUiSpecs}) if the - * provided observer was already registered. - * - * @param observer The {@link SideUiObserver} to add. - */ - void addObserver(SideUiObserver observer); - - /** - * Removes a {@link SideUiObserver}. - * - * <p>Upon removal, the provided observer will be notified as if there were no side UI present. - * i.e. it will be passed a {@link SideUiSpecs#EMPTY_SIDE_UI_SPECS}, which represents the specs - * when no side UI is currently showing. The intent of this is to attempt to return a given - * observer to its state prior to observing Side UI. - * - * <p>This is no-op (including not being notified of empty Side UI specs) if the provided - * observer was not actually present in the list of observers. - * - * @param observer The {@link SideUiObserver} to remove. - */ - void removeObserver(SideUiObserver observer); - - /** * Requests that the registered {@link SideUiContainer} change its width. * <strong>Important:</strong> this should only be called by the feature that owns the affected * {@link SideUiContainer}.
diff --git a/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiStateProvider.java b/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiStateProvider.java new file mode 100644 index 0000000..f524392 --- /dev/null +++ b/chrome/browser/ui/side_ui/public/android/java/src/org/chromium/chrome/browser/ui/side_ui/SideUiStateProvider.java
@@ -0,0 +1,53 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.ui.side_ui; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.ui.side_ui.SideUiCoordinator.SideUiSpecs; + +/** Provider for SideUi state. Primarily, the {@link SideUiSpecs} through observers and a getter. */ +@NullMarked +public interface SideUiStateProvider { + + /** + * Adds a {@link SideUiObserver}. The provided observer will be notified whenever a new {@link + * SideUiSpecs} is determined as a result of a change in a registered {@link SideUiContainer}. + * + * <p>Upon being added, the provided observer will also be notified of the current state of the + * side UI so that it may immediately position itself accordingly. e.g. This accounts for the + * case where an observer registers itself while some {@link SideUiContainer} is already showing + * and there is no incoming update request that would trigger a notification for observers. + * + * <p>This is no-op (including being notified of the current {@link SideUiSpecs}) if the + * provided observer was already registered. + * + * @param observer The {@link SideUiObserver} to add. + */ + void addObserver(SideUiObserver observer); + + /** + * Removes a {@link SideUiObserver}. + * + * <p>Upon removal, the provided observer will be notified as if there were no side UI present. + * i.e. it will be passed a {@link SideUiSpecs#EMPTY_SIDE_UI_SPECS}, which represents the specs + * when no side UI is currently showing. The intent of this is to attempt to return a given + * observer to its state prior to observing Side UI. + * + * <p>This is no-op (including not being notified of empty Side UI specs) if the provided + * observer was not actually present in the list of observers. + * + * @param observer The {@link SideUiObserver} to remove. + */ + void removeObserver(SideUiObserver observer); + + /** + * Returns the current {@link SideUiSpecs}. Most clients should instead register themselves as a + * {@link SideUiObserver}. This is primarily intended to be used by the CompositorViewHolder, + * which requires the current specs separate from when it's passed through the observer event. + * + * @return The current {@link SideUiSpecs}. + */ + SideUiSpecs getCurrentSideUiSpecs(); +}
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc index 7992438..81936dd 100644 --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc
@@ -449,8 +449,16 @@ HistoryClustersTabHelper::CreateForWebContents(web_contents, history_tab_helper); if (HistoryEmbeddingsServiceFactory::GetForProfile(profile)) { - HistoryEmbeddingsTabHelper::CreateForWebContents(web_contents, - history_tab_helper); + HistoryEmbeddingsTabHelper::CreateForWebContents(web_contents); + auto* history_embeddings_tab_helper = + HistoryEmbeddingsTabHelper::FromWebContents(web_contents); + if (history_tab_helper && history_embeddings_tab_helper) { + history_embeddings_tab_helper->SetHistoryTabHelperSubscription( + history_tab_helper->RegisterOnUpdatedHistoryForNavigationCallback( + base::BindRepeating( + &HistoryEmbeddingsTabHelper::OnUpdatedHistoryForNavigation, + history_embeddings_tab_helper->GetWeakPtr()))); + } } } HttpsOnlyModeTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/tabs/tab_features.cc b/chrome/browser/ui/tabs/tab_features.cc index 82010ea9..644e839c 100644 --- a/chrome/browser/ui/tabs/tab_features.cc +++ b/chrome/browser/ui/tabs/tab_features.cc
@@ -398,12 +398,10 @@ // This block instantiates the page action controllers that depends on the // `commerce_ui_tab_helper_` and not need to be created before. if (commerce_ui_tab_helper_) { - if (IsPageActionMigrated(PageActionIconType::kDiscounts)) { - commerce_discounts_page_action_view_controller_ = - GetUserDataFactory() - .CreateInstance<commerce::DiscountsPageActionViewController>( - tab, tab, *page_action_controller_, *commerce_ui_tab_helper_); - } + commerce_discounts_page_action_view_controller_ = + GetUserDataFactory() + .CreateInstance<commerce::DiscountsPageActionViewController>( + tab, tab, *page_action_controller_, *commerce_ui_tab_helper_); } if (base::FeatureList::IsEnabled(
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc index 9817fa3..a1d52ca5b 100644 --- a/chrome/browser/ui/ui_features.cc +++ b/chrome/browser/ui/ui_features.cc
@@ -245,22 +245,6 @@ BASE_FEATURE(kEnterpriseProfileBadgingForMenu, base::FEATURE_ENABLED_BY_DEFAULT); -// Enables enterprise badging for managed browsers on the new tab page footer. -// On managed browsers, a building icon and "Managed by <domain>" string will be -// shown in the footer, unless the icon and label are customized by the admin. -BASE_FEATURE(kEnterpriseBadgingForNtpFooter, base::FEATURE_ENABLED_BY_DEFAULT); - -// Enables enterprise badging for managed browsers with local management only on -// the new tab page footer. On managed browsers, a building icon and "Managed by -// your organization" string will be shown in the footer. -BASE_FEATURE(kEnterpriseBadgingForLocalManagemenetNtpFooter, - base::FEATURE_DISABLED_BY_DEFAULT); - -// Enables enterprise badging for managed browsers with local management only -// AND 3 or more policies on the new tab page footer. -BASE_FEATURE(kEnterpriseBadgingForNtpFooterWithOverThreePolicies, - base::FEATURE_DISABLED_BY_DEFAULT); - // Enables the management notice in the NTP footer if the custom policies are // set. This acts as a kill switch for "EnterpriseCustomLabelForBrowser" and // "EnterpriseLogoUrlForBrowser". @@ -353,12 +337,6 @@ true); BASE_FEATURE_PARAM(bool, - kPageActionsMigrationDiscounts, - &kPageActionsMigration, - "discounts", - true); - -BASE_FEATURE_PARAM(bool, kPageActionsMigrationManagePasswords, &kPageActionsMigration, "manage_passwords",
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h index 6f58f91..a07d8162 100644 --- a/chrome/browser/ui/ui_features.h +++ b/chrome/browser/ui/ui_features.h
@@ -261,9 +261,6 @@ // TODO(crbug.com/460764864): Cleanup all the enterprise badging feature flags. BASE_DECLARE_FEATURE(kEnterpriseProfileBadgingForMenu); -BASE_DECLARE_FEATURE(kEnterpriseBadgingForNtpFooter); -BASE_DECLARE_FEATURE(kEnterpriseBadgingForLocalManagemenetNtpFooter); -BASE_DECLARE_FEATURE(kEnterpriseBadgingForNtpFooterWithOverThreePolicies); BASE_DECLARE_FEATURE(kNTPFooterBadgingPolicies); BASE_DECLARE_FEATURE(kEnterpriseManagementDisclaimerUsesCustomLabel); @@ -312,7 +309,6 @@ BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationIntentPicker); BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationZoom); BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationFileSystemAccess); -BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationDiscounts); BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationManagePasswords); BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationCookieControls); BASE_DECLARE_FEATURE_PARAM(bool, kPageActionsMigrationAutofillAddress);
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views.cc b/chrome/browser/ui/views/autofill/popup/popup_view_views.cc index 3f1d4c2..af5e49b3 100644 --- a/chrome/browser/ui/views/autofill/popup/popup_view_views.cc +++ b/chrome/browser/ui/views/autofill/popup/popup_view_views.cc
@@ -89,6 +89,7 @@ #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_border_arrow_utils.h" #include "ui/views/controls/scroll_view.h" +#include "ui/views/controls/tabbed_pane/tabbed_pane.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout_view.h" #include "ui/views/layout/flex_layout.h" @@ -225,7 +226,8 @@ PopupViewViews::PopupViewViews( base::WeakPtr<AutofillPopupController> controller, - std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config) + std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config, + std::optional<const AutofillPopupView::TabbedPaneConfig> tabbed_pane_config) : PopupBaseView(controller, views::Widget::GetTopLevelWidgetForNativeView( controller->container_view()), @@ -233,7 +235,8 @@ ? views::Widget::InitParams::Activatable::kYes : views::Widget::InitParams::Activatable::kDefault), controller_(controller), - search_bar_config_(std::move(search_bar_config)) { + search_bar_config_(std::move(search_bar_config)), + tabbed_pane_config_(std::move(tabbed_pane_config)) { InitViews(); GetViewAccessibility().SetRole(ax::mojom::Role::kListBox); @@ -970,6 +973,10 @@ AddChildView(std::make_unique<PopupSeparatorView>(/*vertical_padding=*/0)); } + if (tabbed_pane_config_) { + CreateTabbedPaneView(); + } + suggestions_container_ = AddChildView(views::Builder<views::BoxLayoutView>() .SetOrientation(views::BoxLayout::Orientation::kVertical) @@ -1245,6 +1252,36 @@ return size; } +void PopupViewViews::CreateTabbedPaneView() { + auto tabbed_pane = std::make_unique<views::TabbedPane>(); + + auto create_empty_pane = []() { + auto pane = std::make_unique<views::View>(); + pane->SetBorder( + views::CreateEmptyBorder(ChromeLayoutProvider::Get()->GetInsetsMetric( + views::INSETS_DIALOG_SUBSECTION))); + return pane; + }; + + const int kCustomTabHeight = 52; + for (const auto& tab_config : tabbed_pane_config_->tabs) { + tabbed_pane->AddTab(tab_config.title, create_empty_pane()); + + views::TabbedPaneTab* tab_view = + tabbed_pane->GetTabAt(tabbed_pane->GetTabCount() - 1); + tab_view->SetHeight(kCustomTabHeight); + } + + // TODO(crbug.com/477689220): Investigate better approaches (e.g., layout + // management) for sizing the tabbed pane so it doesn't overlap the + // suggestions container. + // Calculate and set the preferred size for the tabbed pane based on its + // contents. + tabbed_pane->SetPreferredSize(tabbed_pane->GetPreferredSize()); + + tabbed_pane_ = AddChildView(std::move(tabbed_pane)); +} + bool PopupViewViews::DoUpdateBoundsAndRedrawPopup() { return DoUpdateBoundsAndRedrawPopup(/*prefer_prev_arrow_side=*/false); } @@ -1539,7 +1576,9 @@ // static base::WeakPtr<AutofillPopupView> AutofillPopupView::Create( base::WeakPtr<AutofillSuggestionController> controller, - std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config) { + std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config, + std::optional<const AutofillPopupView::TabbedPaneConfig> + tabbed_pane_config) { if (!controller || !CanShowRootPopup(*controller)) { return nullptr; } @@ -1547,7 +1586,7 @@ // On Desktop, all controllers are `AutofillPopupController`s. return (new PopupViewViews( static_cast<AutofillPopupController&>(*controller).GetWeakPtr(), - std::move(search_bar_config))) + std::move(search_bar_config), std::move(tabbed_pane_config))) ->GetWeakPtr(); }
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views.h b/chrome/browser/ui/views/autofill/popup/popup_view_views.h index e66be02..42b22b6c 100644 --- a/chrome/browser/ui/views/autofill/popup/popup_view_views.h +++ b/chrome/browser/ui/views/autofill/popup/popup_view_views.h
@@ -34,6 +34,7 @@ namespace views { class BoxLayoutView; class ScrollView; +class TabbedPane; } // namespace views namespace autofill { @@ -111,12 +112,17 @@ base::WeakPtr<ExpandablePopupParentView> parent, views::Widget* parent_widget); - // Constructor for creating root level popups. Providing `std::nullopt` to - // the `search_bar_config` results in creating a popup without a search bar. + // Constructor for creating root level popups. + // Providing `std::nullopt` to the `search_bar_config` results in creating a + // popup without a search bar. + // Providing `std::nullopt` to the `tabbed_pane_config` results in creating a + // popup without a tabbed pane. explicit PopupViewViews( base::WeakPtr<AutofillPopupController> controller, std::optional<const AutofillPopupView::SearchBarConfig> - search_bar_config = std::nullopt); + search_bar_config = std::nullopt, + std::optional<const AutofillPopupView::TabbedPaneConfig> + tabbed_pane_config = std::nullopt); PopupViewViews(const PopupViewViews&) = delete; PopupViewViews& operator=(const PopupViewViews&) = delete; ~PopupViewViews() override; @@ -207,6 +213,9 @@ // suggestions. void CreateSuggestionViews(); + // Creates a tabbed pane view based on the `tabbed_pane_config_`. + void CreateTabbedPaneView(); + // Selects the first row prior to the currently selected one that is // selectable (e.g. not a separator). If no row is selected or no row prior to // the current one is selectable, it tries to select the last row. If that one @@ -311,7 +320,10 @@ std::vector<RowPointer> rows_; const std::optional<const AutofillPopupView::SearchBarConfig> search_bar_config_; + const std::optional<const AutofillPopupView::TabbedPaneConfig> + tabbed_pane_config_; raw_ptr<PopupSearchBarView> search_bar_ = nullptr; + raw_ptr<views::TabbedPane> tabbed_pane_ = nullptr; raw_ptr<views::BoxLayoutView> suggestions_container_ = nullptr; raw_ptr<views::ScrollView> scroll_view_ = nullptr; raw_ptr<views::BoxLayoutView> body_container_ = nullptr;
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc index 99b95d2..aca05c6 100644 --- a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc +++ b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc
@@ -73,9 +73,11 @@ #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_border_arrow_utils.h" +#include "ui/views/controls/tabbed_pane/tabbed_pane.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/test/ax_event_counter.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_utils.h" @@ -238,6 +240,8 @@ void CreateView( std::optional<views::Widget::InitParams> widget_params = std::nullopt, std::optional<AutofillPopupView::SearchBarConfig> search_bar_config = + std::nullopt, + std::optional<AutofillPopupView::TabbedPaneConfig> tabbed_pane_config = std::nullopt) { view_ = nullptr; generator_.reset(); @@ -256,14 +260,18 @@ generator_ = std::make_unique<ui::test::EventGenerator>( GetRootWindow(widget_.get())); view_ = new TestPopupViewViews(controller().GetWeakPtr(), - std::move(search_bar_config)); + std::move(search_bar_config), + std::move(tabbed_pane_config)); } void CreateAndShowView( std::optional<views::Widget::InitParams> widget_params = std::nullopt, std::optional<AutofillPopupView::SearchBarConfig> search_bar_config = + std::nullopt, + std::optional<AutofillPopupView::TabbedPaneConfig> tabbed_pane_config = std::nullopt) { - CreateView(std::move(widget_params), std::move(search_bar_config)); + CreateView(std::move(widget_params), std::move(search_bar_config), + std::move(tabbed_pane_config)); ShowView(view_, *widget_); } @@ -271,9 +279,12 @@ const std::vector<SuggestionType>& ids, std::optional<views::Widget::InitParams> widget_params = std::nullopt, std::optional<AutofillPopupView::SearchBarConfig> search_bar_config = + std::nullopt, + std::optional<AutofillPopupView::TabbedPaneConfig> tabbed_pane_config = std::nullopt) { controller().set_suggestions(ids); - CreateAndShowView(std::move(widget_params), std::move(search_bar_config)); + CreateAndShowView(std::move(widget_params), std::move(search_bar_config), + std::move(tabbed_pane_config)); } void UpdateSuggestions(const std::vector<SuggestionType>& ids, @@ -2448,6 +2459,31 @@ generator().PressAndReleaseKey(ui::VKEY_DOWN); } +TEST_F(PopupViewViewsTest, TabbedPane_ConfigPassedThroughAndRendered) { + AutofillPopupView::TabbedPaneConfig tabbed_pane_config( + {{AutofillPopupView::TabbedPaneConfig::TabType::kPayNow, u"Pay Now Test"}, + {AutofillPopupView::TabbedPaneConfig::TabType::kPayLater, + u"Pay Later Test"}}); + + CreateAndShowView({SuggestionType::kCreditCardEntry}, + /*widget_params=*/std::nullopt, + /*search_bar_config=*/std::nullopt, + std::move(tabbed_pane_config)); + + views::TabbedPane* tabbed_pane = nullptr; + for (views::View* child : view().children()) { + if (views::IsViewClass<views::TabbedPane>(child)) { + tabbed_pane = views::AsViewClass<views::TabbedPane>(child); + break; + } + } + + ASSERT_NE(tabbed_pane, nullptr); + ASSERT_EQ(tabbed_pane->GetTabCount(), 2u); + EXPECT_EQ(tabbed_pane->GetTabAt(0)->GetTitleText(), u"Pay Now Test"); + EXPECT_EQ(tabbed_pane->GetTabAt(1)->GetTitleText(), u"Pay Later Test"); +} + TEST_F(PopupViewViewsTest, WarningOnShowA11yFocus) { views::test::AXEventCounter counter(views::AXUpdateNotifier::Get()); CreateAndShowView({SuggestionType::kMixedFormMessage});
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc index e72e0e6..6b91efe 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc +++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
@@ -549,9 +549,7 @@ dialog_model_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT); bookmark_bubble_ = bubble.get(); - if (highlighted_button) { - bubble->SetHighlightedButton(highlighted_button); - } + bubble->SetHighlightedElement(kBookmarkStarViewElementId); if (ShouldShowShoppingCollectionFootnote(profile, bookmark_model, bookmark_node)) {
diff --git a/chrome/browser/ui/views/commerce/discounts_icon_view.cc b/chrome/browser/ui/views/commerce/discounts_icon_view.cc deleted file mode 100644 index 98bbdf5..0000000 --- a/chrome/browser/ui/views/commerce/discounts_icon_view.cc +++ /dev/null
@@ -1,240 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/ui/views/commerce/discounts_icon_view.h" - -#include "base/time/default_clock.h" -#include "base/time/time.h" -#include "base/timer/timer.h" -#include "chrome/browser/ui/browser_element_identifiers.h" -#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" -#include "chrome/browser/ui/call_to_action/call_to_action_lock.h" -#include "chrome/browser/ui/commerce/commerce_ui_tab_helper.h" -#include "chrome/browser/ui/tabs/public/tab_features.h" -#include "chrome/browser/ui/views/commerce/discounts_bubble_dialog_view.h" -#include "components/commerce/core/commerce_feature_list.h" -#include "components/commerce/core/metrics/discounts_metric_collector.h" -#include "components/strings/grit/components_strings.h" -#include "components/tabs/public/tab_interface.h" -#include "components/vector_icons/vector_icons.h" -#include "content/public/browser/web_contents.h" -#include "services/metrics/public/cpp/ukm_source_id.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/base/metadata/metadata_impl_macros.h" -#include "ui/views/accessibility/view_accessibility.h" -#include "ui/views/property_effects.h" -#include "ui/views/view_class_properties.h" - -DiscountsIconView::DiscountsIconView( - IconLabelBubbleView::Delegate* icon_label_bubble_delegate, - PageActionIconView::Delegate* page_action_icon_delegate) - : PageActionIconView(nullptr, - 0, - icon_label_bubble_delegate, - page_action_icon_delegate, - "Discounts") { - GetViewAccessibility().SetName( - l10n_util::GetStringUTF16(IDS_DISCOUNT_ICON_EXPANDED_TEXT)); - SetUpForInOutAnimation(); - SetProperty(views::kElementIdentifierKey, kDiscountsChipElementId); -} - -DiscountsIconView::~DiscountsIconView() = default; - -views::BubbleDialogDelegate* DiscountsIconView::GetBubble() const { - const commerce::CommerceUiTabHelper* tab_helper = GetTabHelper(); - - if (!tab_helper) { - return nullptr; - } - - return tab_helper->GetDiscountsBubbleCoordinator().GetBubble(); -} - -void DiscountsIconView::SetIsLabelExpanded(bool is_expanded) { - is_label_expanded_ = is_expanded; - OnPropertyChanged(&is_label_expanded_, views::PropertyEffects::kNone); -} - -bool DiscountsIconView::GetIsLabelExpanded() const { - return is_label_expanded_; -} - -base::CallbackListSubscription -DiscountsIconView::AddIsLabelExpandedChangedCallback( - views::PropertyChangedCallback callback) { - return AddPropertyChangedCallback(&is_label_expanded_, std::move(callback)); -} - -void DiscountsIconView::OnExecuting( - PageActionIconView::ExecuteSource execute_source) { - MaybeShowBubble(/*from_user=*/true); - - commerce::CommerceUiTabHelper* tab_helper = GetTabHelper(); - - if (!tab_helper) { - return; - } - - commerce::metrics::DiscountsMetricCollector:: - RecordDiscountsPageActionIconClicked( - tab_helper->IsPageActionIconExpanded(PageActionIconType::kDiscounts), - tab_helper->GetDiscounts()); -} - -const gfx::VectorIcon& DiscountsIconView::GetVectorIcon() const { - return vector_icons::kShoppingmodeIcon; -} - -void DiscountsIconView::UpdateImpl() { - bool should_show = ShouldShow(); - - if (should_show) { - MaybeShowPageActionLabel(); - } else { - scoped_call_to_action_lock_.reset(); - HidePageActionLabel(); - } - SetBackgroundVisibility(BackgroundVisibility::kWithLabel); - SetVisible(should_show); -} - -bool DiscountsIconView::ShouldShow() { - if (!GetWebContents() || delegate()->ShouldHidePageActionIcons()) { - return false; - } - - commerce::CommerceUiTabHelper* tab_helper = GetTabHelper(); - - return tab_helper && tab_helper->ShouldShowDiscountsIconView(); -} - -void DiscountsIconView::MaybeShowPageActionLabel() { - commerce::CommerceUiTabHelper* tab_helper = GetTabHelper(); - - if (!tab_helper || - !tab_helper->ShouldExpandPageActionIcon(PageActionIconType::kDiscounts)) { - return; - } - - auto* call_to_action = CallToActionLock::From( - tabs::TabInterface::GetFromContents(GetWebContents()) - ->GetBrowserWindowInterface()); - if (!call_to_action->CanAcquireLock()) { - return; - } - - scoped_call_to_action_lock_ = call_to_action->AcquireLock(); - - should_extend_label_shown_duration_ = true; - AnimateIn(IDS_DISCOUNT_ICON_EXPANDED_TEXT); -} - -void DiscountsIconView::HidePageActionLabel() { - UnpauseAnimation(); - ResetSlideAnimation(false); -} - -void DiscountsIconView::AnimationProgressed(const gfx::Animation* animation) { - PageActionIconView::AnimationProgressed(animation); - // When the label is fully revealed pause the animation for - // kLabelPersistDuration before resuming the animation and allowing the label - // to animate out. This is currently set to show for 12s including the in/out - // animation. - // TODO(crbug.com/40832707): This approach of inspecting the animation - // progress to extend the animation duration is quite hacky. This should be - // removed and the IconLabelBubbleView API expanded to support a finer level - // of control. - constexpr double kAnimationValueWhenLabelFullyShown = 0.5; - constexpr base::TimeDelta kLabelPersistDuration = base::Seconds(10.8); - if (should_extend_label_shown_duration_ && - GetAnimationValue() >= kAnimationValueWhenLabelFullyShown) { - should_extend_label_shown_duration_ = false; - PauseAnimation(); - animate_out_timer_.Start( - FROM_HERE, kLabelPersistDuration, - base::BindRepeating(&DiscountsIconView::UnpauseAnimation, - base::Unretained(this))); - SetIsLabelExpanded(true); - MaybeShowBubble(false); - } -} - -void DiscountsIconView::UnpauseAnimation() { - IconLabelBubbleView::UnpauseAnimation(); - // The label is collapsed during UnpauseAnimation, so sets the label is - // expanded to false here. See the comment in AnimationProgressed for more - // details. - SetIsLabelExpanded(false); -} - -commerce::CommerceUiTabHelper* DiscountsIconView::GetTabHelper() { - return const_cast<commerce::CommerceUiTabHelper*>( - std::as_const(*this).GetTabHelper()); -} - -const commerce::CommerceUiTabHelper* DiscountsIconView::GetTabHelper() const { - auto* web_contents = GetWebContents(); - if (!web_contents) { - return nullptr; - } - - return tabs::TabInterface::GetFromContents(web_contents) - ->GetTabFeatures() - ->commerce_ui_tab_helper(); -} - -void DiscountsIconView::MaybeShowBubble(bool from_user) { - commerce::CommerceUiTabHelper* tab_helper = GetTabHelper(); - - if (!tab_helper) { - return; - } - - const std::vector<commerce::DiscountInfo>& discount_infos = - tab_helper->GetDiscounts(); - - CHECK(!discount_infos.empty()); - - // Currently only uses the first discount info. - bool should_auto_show = tab_helper->ShouldAutoShowDiscountsBubble( - discount_infos[0].id, discount_infos[0].is_merchant_wide); - if (!from_user && !should_auto_show) { - return; - } - - if (!from_user && should_auto_show) { - // If commerce::kDiscountDialogAutoPopupCounterfactual is enabled, we - // purposely not show the bubble. - bool should_suppress = base::FeatureList::IsEnabled( - commerce::kDiscountDialogAutoPopupCounterfactual); - - commerce::metrics::DiscountsMetricCollector:: - RecordDiscountAutoPopupEligibleButSuppressed( - GetWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(), - should_suppress); - - if (should_suppress) { - return; - } - } - - if (animate_out_timer_.IsRunning()) { - animate_out_timer_.Stop(); - } - - tab_helper->ShowDiscountBubble( - discount_infos[0], base::BindOnce(&DiscountsIconView::UnpauseAnimation, - weak_ptr_factory_.GetWeakPtr())); - - tab_helper->DiscountsBubbleShown(discount_infos[0].id); - - commerce::metrics::DiscountsMetricCollector::RecordDiscountBubbleShown( - should_auto_show, - GetWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(), - tab_helper->GetDiscounts()); -} - -BEGIN_METADATA(DiscountsIconView) -END_METADATA
diff --git a/chrome/browser/ui/views/commerce/discounts_icon_view.h b/chrome/browser/ui/views/commerce/discounts_icon_view.h deleted file mode 100644 index 8d2a53b..0000000 --- a/chrome/browser/ui/views/commerce/discounts_icon_view.h +++ /dev/null
@@ -1,72 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_UI_VIEWS_COMMERCE_DISCOUNTS_ICON_VIEW_H_ -#define CHROME_BROWSER_UI_VIEWS_COMMERCE_DISCOUNTS_ICON_VIEW_H_ - -#include "base/timer/timer.h" -#include "chrome/browser/ui/views/page_action/page_action_icon_view.h" -#include "ui/base/metadata/metadata_header_macros.h" - -namespace commerce { -class CommerceUiTabHelper; -} // namespace commerce - -namespace gfx { -struct VectorIcon; -} // namespace gfx - -class ScopedCallToActionLock; - -class DiscountsIconView : public PageActionIconView { - METADATA_HEADER(DiscountsIconView, PageActionIconView) - - public: - DiscountsIconView(IconLabelBubbleView::Delegate* icon_label_bubble_delegate, - PageActionIconView::Delegate* page_action_icon_delegate); - ~DiscountsIconView() override; - - // PageActionIconView: - views::BubbleDialogDelegate* GetBubble() const override; - - void SetIsLabelExpanded(bool is_expanded); - // For testing only. - bool GetIsLabelExpanded() const; - [[nodiscard]] base::CallbackListSubscription - AddIsLabelExpandedChangedCallback(views::PropertyChangedCallback callback); - - protected: - // PageActionIconView: - void OnExecuting(PageActionIconView::ExecuteSource execute_source) override; - const gfx::VectorIcon& GetVectorIcon() const override; - void UpdateImpl() override; - - private: - // IconLabelBubbleView: - void AnimationProgressed(const gfx::Animation* animation) override; - - void UnpauseAnimation(); - bool ShouldShow(); - void HidePageActionLabel(); - void MaybeShowPageActionLabel(); - commerce::CommerceUiTabHelper* GetTabHelper(); - const commerce::CommerceUiTabHelper* GetTabHelper() const; - void MaybeShowBubble(bool from_user); - - // Boolean that tracks whether we should extend the duration for which the - // label is shown when it animates in. - bool should_extend_label_shown_duration_ = false; - // Animates out the discounts icon label after a fixed period of time. - // This keeps the label visible for long enough to give users an opportunity - // to read the label text. - base::OneShotTimer animate_out_timer_; - - bool is_label_expanded_; - - std::unique_ptr<ScopedCallToActionLock> scoped_call_to_action_lock_; - - base::WeakPtrFactory<DiscountsIconView> weak_ptr_factory_{this}; -}; - -#endif // CHROME_BROWSER_UI_VIEWS_COMMERCE_DISCOUNTS_ICON_VIEW_H_
diff --git a/chrome/browser/ui/views/commerce/discounts_icon_view_browsertest.cc b/chrome/browser/ui/views/commerce/discounts_icon_view_browsertest.cc index ff4cc7a..0a18cbfc 100644 --- a/chrome/browser/ui/views/commerce/discounts_icon_view_browsertest.cc +++ b/chrome/browser/ui/views/commerce/discounts_icon_view_browsertest.cc
@@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ui/views/commerce/discounts_icon_view.h" - #include "base/time/default_clock.h" #include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/commerce/mock_commerce_ui_tab_helper.h"
diff --git a/chrome/browser/ui/views/commerce/discounts_interactive_uitest.cc b/chrome/browser/ui/views/commerce/discounts_interactive_uitest.cc index 81117dc..8248fe3 100644 --- a/chrome/browser/ui/views/commerce/discounts_interactive_uitest.cc +++ b/chrome/browser/ui/views/commerce/discounts_interactive_uitest.cc
@@ -16,7 +16,6 @@ #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/commerce/discounts_bubble_dialog_view.h" #include "chrome/browser/ui/views/commerce/discounts_coupon_code_label_view.h" -#include "chrome/browser/ui/views/commerce/discounts_icon_view.h" #include "chrome/browser/ui/views/controls/subpage_view.h" #include "chrome/browser/ui/views/interaction/browser_elements_views.h" #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h" @@ -63,7 +62,6 @@ std::string name; commerce::DiscountClusterType type; std::optional<base::test::FeatureRefAndParams> enabled_feature; - bool page_action_migration_enabled; }; std::string GetTestParamName(const ::testing::TestParamInfo<TestData>& info) { @@ -81,11 +79,6 @@ {{commerce::kMerchantWideBehaviorParam, "2"}, {commerce::kNonMerchantWideBehaviorParam, "2"}}}}; - enabled_features.push_back( - {features::kPageActionsMigration, - {{features::kPageActionsMigrationDiscounts.name, - GetParam().page_action_migration_enabled ? "true" : "false"}}}); - if (GetParam().enabled_feature.has_value()) { enabled_features.emplace_back(GetParam().enabled_feature.value()); } @@ -137,12 +130,6 @@ using PageActionInteractiveTestMixin::WaitForPageActionButtonVisible; - auto WaitForPageActionButtonVisible() { - MultiStep steps; - steps += WaitForPageActionButtonVisible(kActionCommerceDiscounts); - return steps; - } - commerce::DiscountClusterType test_discount_cluster_type_; private: @@ -184,38 +171,19 @@ DiscountsIconViewInteractiveTest, testing::Values( TestData{ - .name = "OfferLevelDiscountsWithPageActionsMigrationDisabled", + .name = "OfferLevelDiscounts", .type = commerce::DiscountClusterType::kOfferLevel, .enabled_feature = std::make_optional<base::test::FeatureRefAndParams>( {commerce::kEnableDiscountInfoApi, {}}), - .page_action_migration_enabled = false, }, TestData{ - .name = "OfferLevelDiscountsWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kOfferLevel, - .enabled_feature = - std::make_optional<base::test::FeatureRefAndParams>( - {commerce::kEnableDiscountInfoApi, {}}), - .page_action_migration_enabled = true, - }, - TestData{ - .name = "PageLevelDiscountsWithPageActionsMigrationDisabled", + .name = "PageLevelDiscounts", .type = commerce::DiscountClusterType::kPageLevel, .enabled_feature = std::make_optional<base::test::FeatureRefAndParams>( {commerce::kEnableDiscountInfoApi, {{commerce::kDiscountOnShoppyPageParam, "true"}}}), - .page_action_migration_enabled = false, - }, - TestData{ - .name = "PageLevelDiscountsWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kPageLevel, - .enabled_feature = - std::make_optional<base::test::FeatureRefAndParams>( - {commerce::kEnableDiscountInfoApi, - {{commerce::kDiscountOnShoppyPageParam, "true"}}}), - .page_action_migration_enabled = true, }), GetTestParamName); @@ -225,7 +193,7 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), EnsureNotPresent(kDiscountsBubbleDialogId), PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId)); @@ -239,7 +207,7 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), EnsureNotPresent(kDiscountsBubbleDialogId), PressButton(kDiscountsChipElementId), Do([&]() { histogram_tester.ExpectBucketCount( @@ -270,7 +238,7 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), EnsureNotPresent(kDiscountsBubbleDialogId), PressButton(kDiscountsChipElementId), Do([&]() { entries = test_ukm_recorder.GetEntriesByName( @@ -306,7 +274,7 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), EnsureNotPresent(kDiscountsBubbleDialogId), PressButton(kDiscountsChipElementId), Check([&]() { return user_action_tester.GetActionCount( @@ -341,38 +309,19 @@ DiscountsBubbleDialogInteractiveTest, testing::Values( TestData{ - .name = "OfferLevelDiscountsWithPageActionsMigrationDisabled", + .name = "OfferLevelDiscounts", .type = commerce::DiscountClusterType::kOfferLevel, .enabled_feature = std::make_optional<base::test::FeatureRefAndParams>( {commerce::kEnableDiscountInfoApi, {}}), - .page_action_migration_enabled = false, }, TestData{ - .name = "OfferLevelDiscountsWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kOfferLevel, - .enabled_feature = - std::make_optional<base::test::FeatureRefAndParams>( - {commerce::kEnableDiscountInfoApi, {}}), - .page_action_migration_enabled = true, - }, - TestData{ - .name = "PageLevelDiscountsWithPageActionsMigrationDisabled", + .name = "PageLevelDiscounts", .type = commerce::DiscountClusterType::kPageLevel, .enabled_feature = std::make_optional<base::test::FeatureRefAndParams>( {commerce::kEnableDiscountInfoApi, {{commerce::kDiscountOnShoppyPageParam, "true"}}}), - .page_action_migration_enabled = false, - }, - TestData{ - .name = "PageLevelDiscountsWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kPageLevel, - .enabled_feature = - std::make_optional<base::test::FeatureRefAndParams>( - {commerce::kEnableDiscountInfoApi, - {{commerce::kDiscountOnShoppyPageParam, "true"}}}), - .page_action_migration_enabled = true, }), GetTestParamName); @@ -382,7 +331,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), InSameContext( PressButton(kDiscountsBubbleCopyButtonElementId), Check([&]() { @@ -400,7 +350,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), InSameContext( CheckViewProperty(kDiscountsBubbleCopyButtonElementId, @@ -428,7 +379,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), CheckView(kDiscountsBubbleCopyButtonElementId, base::BindOnce([&](views::MdTextButton* copy_button) { @@ -459,7 +411,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), Do([&]() { entries = test_ukm_recorder.GetEntriesByName( ukm::builders::Shopping_ShoppingAction::Shopping_ShoppingAction:: @@ -493,7 +446,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), InSameContext( HideDiscountBubbleDialog(), WaitForHide(kDiscountsBubbleDialogId), @@ -517,7 +471,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), InSameContext( PressButton(kDiscountsBubbleCopyButtonElementId), @@ -540,7 +495,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), EnsurePresent(kDiscountsBubbleTermsAndConditionLabelId), EnsureNotPresent(kDiscountsBubbleTermsAndConditionPageId), @@ -558,7 +514,8 @@ InstrumentTab(kShoppingTab), NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), - WaitForPageActionButtonVisible(), PressButton(kDiscountsChipElementId), + WaitForPageActionButtonVisible(kActionCommerceDiscounts), + PressButton(kDiscountsChipElementId), WaitForShow(kDiscountsBubbleDialogId), WithElement(kDiscountsBubbleTermsAndConditionLabelId, [](ui::TrackedElement* el) { @@ -579,11 +536,6 @@ {{commerce::kMerchantWideBehaviorParam, "2"}, {commerce::kNonMerchantWideBehaviorParam, "1"}}}}; - enabled_features.push_back( - {features::kPageActionsMigration, - {{"discounts", - GetParam().page_action_migration_enabled ? "true" : "false"}}}); - if (GetParam().enabled_feature.has_value()) { is_counterfactual_enabled = true; enabled_features.emplace_back(GetParam().enabled_feature.value()); @@ -603,30 +555,15 @@ DiscountDialogAutoPopupCounterfactual, testing::Values( TestData{ - .name = "CounterfactualDisabledWithPageActionsMigrationDisabled", + .name = "CounterfactualDisabled", .type = commerce::DiscountClusterType::kOfferLevel, - .page_action_migration_enabled = false, }, TestData{ - .name = "CounterfactualDisabledWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kOfferLevel, - .page_action_migration_enabled = true, - }, - TestData{ - .name = "CounterfactualEnabledWithPageActionsMigrationDisabled", + .name = "CounterfactualEnabled", .type = commerce::DiscountClusterType::kOfferLevel, .enabled_feature = std::make_optional<base::test::FeatureRefAndParams>( {commerce::kDiscountDialogAutoPopupCounterfactual, {}}), - .page_action_migration_enabled = false, - }, - TestData{ - .name = "CounterfactualEnabledWithPageActionsMigrationEnabled", - .type = commerce::DiscountClusterType::kOfferLevel, - .enabled_feature = - std::make_optional<base::test::FeatureRefAndParams>( - {commerce::kDiscountDialogAutoPopupCounterfactual, {}}), - .page_action_migration_enabled = true, }), GetTestParamName); @@ -642,11 +579,8 @@ NavigateWebContents(kShoppingTab, embedded_test_server()->GetURL(kShoppingURL)), WaitForPageActionChipVisible(kActionCommerceDiscounts), - If([&]() { return IsPageActionMigrated(PageActionIconType::kDiscounts); }, - Then(WaitForViewProperty(kDiscountsChipElementId, views::LabelButton, - Visible, true)), - Else(WaitForViewProperty(kDiscountsChipElementId, DiscountsIconView, - IsLabelExpanded, true))), + WaitForViewProperty(kDiscountsChipElementId, views::LabelButton, Visible, + true), If([&]() { return is_counterfactual_enabled; }, Then(EnsureNotPresent(kDiscountsBubbleDialogId)), Else(WaitForShow(kDiscountsBubbleDialogId))),
diff --git a/chrome/browser/ui/views/commerce/discounts_page_action_view_controller.cc b/chrome/browser/ui/views/commerce/discounts_page_action_view_controller.cc index 28f72a9..2801b259 100644 --- a/chrome/browser/ui/views/commerce/discounts_page_action_view_controller.cc +++ b/chrome/browser/ui/views/commerce/discounts_page_action_view_controller.cc
@@ -34,7 +34,6 @@ page_action_controller_(page_action_controller), commerce_ui_tab_helper_(commerce_ui_tab_helper), scoped_unowned_user_data_(tab_interface.GetUnownedUserDataHost(), *this) { - CHECK(IsPageActionMigrated(PageActionIconType::kDiscounts)); RegisterAsPageActionObserver(*page_action_controller_); }
diff --git a/chrome/browser/ui/views/content_setting_bubble_contents.cc b/chrome/browser/ui/views/content_setting_bubble_contents.cc index e1669cb5..41e787a 100644 --- a/chrome/browser/ui/views/content_setting_bubble_contents.cc +++ b/chrome/browser/ui/views/content_setting_bubble_contents.cc
@@ -20,6 +20,7 @@ #include "chrome/browser/ui/views/chrome_layout_provider.h" #include "chrome/browser/ui/views/content_setting_site_row_view.h" #include "chrome/browser/ui/views/controls/rich_hover_button.h" +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" #include "chrome/grit/generated_resources.h" #include "components/strings/grit/components_strings.h" #include "components/vector_icons/vector_icons.h" @@ -471,6 +472,21 @@ rows.push_back({std::move(manage_checkbox), LayoutRowType::DEFAULT}); } + if (std::optional<std::u16string> explanation = + GetSubAppsPermissionExplanation(web_contents())) { + auto custom_label = std::make_unique<views::Label>( + *explanation, views::style::CONTEXT_DIALOG_BODY_TEXT, + views::style::STYLE_BODY_4); + custom_label->SetMultiLine(true); + custom_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + custom_label->SetProperty( + views::kMarginsKey, + gfx::Insets::VH(provider->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL), + 0)); + rows.push_back({std::move(custom_label), LayoutRowType::DEFAULT}); + } + if (bubble_content.manage_text_style == ManageTextStyle::kHoverButton) { SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone)); auto separator = std::make_unique<views::Separator>();
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.cc b/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.cc index f917fe1b..588b75933 100644 --- a/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.cc +++ b/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.cc
@@ -277,7 +277,8 @@ } void ExtensionsMenuDelegateDesktop::OnActionUpdated( - const ToolbarActionsModel::ActionId& action_id) { + const ToolbarActionsModel::ActionId& action_id, + int index) { CHECK(current_page_); // Update the main page if it is open since an action update can affect the
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.h b/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.h index e800393..d77cb08 100644 --- a/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.h +++ b/chrome/browser/ui/views/extensions/extensions_menu_delegate_desktop.h
@@ -62,7 +62,8 @@ int index) override; void OnActionRemoved(const ToolbarActionsModel::ActionId& action_id, int index) override; - void OnActionUpdated(const ToolbarActionsModel::ActionId& action_id) override; + void OnActionUpdated(const ToolbarActionsModel::ActionId& action_id, + int index) override; void OnActionIconUpdated(const ToolbarActionsModel::ActionId& action_id, int index) override; void OnActionsInitialized() override;
diff --git a/chrome/browser/ui/views/extensions/rich_radio_button.cc b/chrome/browser/ui/views/extensions/rich_radio_button.cc index 813e02e..49b0714 100644 --- a/chrome/browser/ui/views/extensions/rich_radio_button.cc +++ b/chrome/browser/ui/views/extensions/rich_radio_button.cc
@@ -27,6 +27,8 @@ const std::u16string& title, const std::u16string& description, int group_id, + int pos_in_set, + int set_size, base::RepeatingClosure on_selected_callback) : views::Button(base::BindRepeating( [](RichRadioButton* view) { view->radio_button_->SetChecked(true); }, @@ -34,7 +36,10 @@ SetFocusBehavior(FocusBehavior::NEVER); views::Builder<RichRadioButton>(this) .SetLayoutManager(std::make_unique<views::FlexLayout>()) - .SetAccessibleName(title) + // Act as a group with no accessible name. The underlying radio button + // will present the necessary information. + .SetAccessibleRole(ax::mojom::Role::kGroup) + .SetAccessibleName(u"", ax::mojom::NameFrom::kAttributeExplicitlyEmpty) .CustomConfigure(base::BindOnce([](RichRadioButton* view) { static_cast<views::FlexLayout*>(view->GetLayoutManager()) ->SetOrientation(views::LayoutOrientation::kHorizontal) @@ -68,10 +73,13 @@ .SetProperty(views::kFlexBehaviorKey, views::FlexSpecification().WithAlignment( views::LayoutAlignment::kEnd)) - .SetAccessibleName( - title, ax::mojom::NameFrom::kAttributeExplicitlyEmpty)) + .SetAccessibleName(title) + .SetAccessibleDescription(description)) .BuildChildren(); + radio_button_->GetViewAccessibility().SetPosInSet(pos_in_set); + radio_button_->GetViewAccessibility().SetSetSize(set_size); + subscription_ = radio_button_->AddCheckedChangedCallback(base::BindRepeating( [](views::RadioButton* button, base::RepeatingClosure closure) { if (button->GetChecked()) {
diff --git a/chrome/browser/ui/views/extensions/rich_radio_button.h b/chrome/browser/ui/views/extensions/rich_radio_button.h index 22b7e66..51280b6 100644 --- a/chrome/browser/ui/views/extensions/rich_radio_button.h +++ b/chrome/browser/ui/views/extensions/rich_radio_button.h
@@ -36,6 +36,8 @@ const std::u16string& title, const std::u16string& description, int group_id, + int pos_in_set, + int set_size, base::RepeatingClosure on_selected_callback); ~RichRadioButton() override;
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.cc b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.cc index 937710e..fa60a4f1 100644 --- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.cc +++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.cc
@@ -40,9 +40,10 @@ InitialFocusTrapView() { SetPreferredSize(gfx::Size(1, 1)); SetFocusBehavior(views::View::FocusBehavior::ALWAYS); - GetViewAccessibility().SetRole(ax::mojom::Role::kGroup); - GetViewAccessibility().SetName(u"Focus Trap", - ax::mojom::NameFrom::kAttribute); + // Make this object a Paragraph so it can be used to describe the + // options that follow. It would be more ideal if the actual Paragraph + // member of the dialog could read its text when the dialog appears. + GetViewAccessibility().SetRole(ax::mojom::Role::kParagraph); } ~InitialFocusTrapView() override = default; @@ -61,6 +62,7 @@ void AddExplicitChoiceRadioButtons( ui::DialogModel::Builder& builder, + std::u16string dialog_description, const SettingsOverriddenDialogController::SettingOption& option1, ui::ElementIdentifier id1, base::RepeatingClosure callback1, @@ -72,22 +74,28 @@ auto container = views::Builder<views::BoxLayoutView>() .SetOrientation(views::BoxLayout::Orientation::kVertical) - .AddChildren( - views::Builder<views::View>( - std::make_unique<InitialFocusTrapView>()), - views::Builder<views::Separator>(), - views::Builder<RichRadioButton>( - std::make_unique<RichRadioButton>( - option1.image, option1.text, option1.description, - kExplicitChoiceRadioGroupId, std::move(callback1))) - .SetProperty(views::kElementIdentifierKey, id1), - views::Builder<views::Separator>(), - views::Builder<RichRadioButton>( - std::make_unique<RichRadioButton>( - option2.image, option2.text, option2.description, - kExplicitChoiceRadioGroupId, std::move(callback2))) - .SetProperty(views::kElementIdentifierKey, id2), - views::Builder<views::Separator>()) + .AddChildren(views::Builder<views::View>( + std::make_unique<InitialFocusTrapView>()) + // Have the initially-focused element read the + // dialog description. Ideally, this would not be + // done here, and the paragraph on the dialog itself + // would provide this text. + .SetAccessibleName(dialog_description), + views::Builder<views::Separator>(), + views::Builder<RichRadioButton>( + std::make_unique<RichRadioButton>( + option1.image, option1.text, option1.description, + kExplicitChoiceRadioGroupId, /*pos_in_set=*/1, + /*set_size=*/2, std::move(callback1))) + .SetProperty(views::kElementIdentifierKey, id1), + views::Builder<views::Separator>(), + views::Builder<RichRadioButton>( + std::make_unique<RichRadioButton>( + option2.image, option2.text, option2.description, + kExplicitChoiceRadioGroupId, /*pos_in_set=*/2, + /*set_size=*/2, std::move(callback2))) + .SetProperty(views::kElementIdentifierKey, id2), + views::Builder<views::Separator>()) .Build(); // This groups the RadioButtons contained within the RichRadioButtons (needed
diff --git a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.h b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.h index 0399206..02935fa 100644 --- a/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.h +++ b/chrome/browser/ui/views/extensions/settings_overridden_dialog_view_utils.h
@@ -17,6 +17,7 @@ // customization. void AddExplicitChoiceRadioButtons( ui::DialogModel::Builder& builder, + std::u16string dialog_description, const SettingsOverriddenDialogController::SettingOption& option1, ui::ElementIdentifier id1, base::RepeatingClosure callback1,
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc index b6628f2..e47fdcb 100644 --- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc +++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -13,6 +13,7 @@ #include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h" #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h" #include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h" +#include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/color/chrome_color_id.h" @@ -1120,7 +1121,7 @@ views::BubbleDialogDelegateView* const bubble = PageInfoBubbleView::CreatePageInfoBubble(std::move(specification)); - bubble->SetHighlightedButton(location_icon_view_); + bubble->SetHighlightedElement(kLocationIconElementId); bubble->GetWidget()->Show(); PictureInPictureOcclusionTracker* tracker =
diff --git a/chrome/browser/ui/views/location_bar/BUILD.gn b/chrome/browser/ui/views/location_bar/BUILD.gn index 9517fb11..c524ea2 100644 --- a/chrome/browser/ui/views/location_bar/BUILD.gn +++ b/chrome/browser/ui/views/location_bar/BUILD.gn
@@ -108,6 +108,7 @@ "//chrome/browser/extensions", "//chrome/browser/extensions/api/omnibox", "//chrome/browser/glic", + "//chrome/browser/history_embeddings", "//chrome/browser/page_info", "//chrome/browser/search_engines", "//chrome/browser/sharing_hub",
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_coordinator.cc b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_coordinator.cc index 86a3209d..dc6d56b 100644 --- a/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_coordinator.cc +++ b/chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_coordinator.cc
@@ -6,6 +6,7 @@ #include "base/callback_list.h" #include "chrome/browser/ui/actions/chrome_action_id.h" +#include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/page_action/page_action_icon_type.h" #include "chrome/browser/ui/views/frame/toolbar_button_provider.h" #include "chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_bubble_view_controller.h" @@ -57,11 +58,7 @@ base::Unretained(this))); bubble_view_ = bubble_view.get(); bubble_view_->View::AddObserver(this); - - auto* icon_view = - toolbar_button_provider->GetPageActionView(kActionShowCookieControls); - CHECK(icon_view); - bubble_view_->SetHighlightedButton(icon_view); + bubble_view_->SetHighlightedElement(kCookieControlsIconElementId); view_controller_ = std::make_unique<CookieControlsBubbleViewController>( bubble_view_, controller, web_contents);
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc index ad5812a..680859e86c 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view.cc +++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -2194,7 +2194,7 @@ .Build(); views::BubbleDialogDelegateView* const bubble = PageInfoBubbleView::CreatePageInfoBubble(std::move(specification)); - bubble->SetHighlightedButton(location_icon_view_); + bubble->SetHighlightedElement(kLocationIconElementId); bubble->GetWidget()->Show(); return true; }
diff --git a/chrome/browser/ui/views/location_bar/merchant_trust_chip_button_controller.cc b/chrome/browser/ui/views/location_bar/merchant_trust_chip_button_controller.cc index c6bf6805..88f6663 100644 --- a/chrome/browser/ui/views/location_bar/merchant_trust_chip_button_controller.cc +++ b/chrome/browser/ui/views/location_bar/merchant_trust_chip_button_controller.cc
@@ -180,7 +180,7 @@ // and set chip_button_ as highlighted button. views::BubbleDialogDelegateView* const bubble = PageInfoBubbleView::CreatePageInfoBubble(std::move(specification)); - bubble->SetHighlightedButton(chip_button_); + bubble->SetHighlightedElement(kMerchantTrustChipElementId); bubble->GetWidget()->Show(); }
diff --git a/chrome/browser/ui/views/new_tab_footer/footer_controller_browsertest.cc b/chrome/browser/ui/views/new_tab_footer/footer_controller_browsertest.cc index 55ae9a69..cc0a4da2 100644 --- a/chrome/browser/ui/views/new_tab_footer/footer_controller_browsertest.cc +++ b/chrome/browser/ui/views/new_tab_footer/footer_controller_browsertest.cc
@@ -229,9 +229,7 @@ public testing::WithParamInterface<bool> { public: FooterControllerEnterpriseTest() { - feature_list_.InitWithFeatureStates( - {{ntp_features::kNtpFooter, true}, - {features::kEnterpriseBadgingForNtpFooter, true}}); + feature_list_.InitWithFeatureStates({{ntp_features::kNtpFooter, true}}); } ~FooterControllerEnterpriseTest() override = default;
diff --git a/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc b/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc index b79386f..b61de25 100644 --- a/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc +++ b/chrome/browser/ui/views/new_tab_footer/footer_interactive_uitest.cc
@@ -300,8 +300,7 @@ public: FooterEnterpriseInteractiveTest() { scoped_feature_list_.InitWithFeatureStates( - {{ntp_features::kNtpFooter, true}, - {features::kEnterpriseBadgingForNtpFooter, true}}); + {{ntp_features::kNtpFooter, true}}); } ~FooterEnterpriseInteractiveTest() override = default;
diff --git a/chrome/browser/ui/views/page_action/page_action_icon_controller.cc b/chrome/browser/ui/views/page_action/page_action_icon_controller.cc index 6b24c5b1..4bf996e 100644 --- a/chrome/browser/ui/views/page_action/page_action_icon_controller.cc +++ b/chrome/browser/ui/views/page_action/page_action_icon_controller.cc
@@ -23,7 +23,6 @@ #include "chrome/browser/ui/views/autofill/payments/mandatory_reauth_icon_view.h" #include "chrome/browser/ui/views/autofill/payments/save_payment_icon_view.h" #include "chrome/browser/ui/views/autofill/payments/virtual_card_enroll_icon_view.h" -#include "chrome/browser/ui/views/commerce/discounts_icon_view.h" #include "chrome/browser/ui/views/file_system_access/file_system_access_icon_view.h" #include "chrome/browser/ui/views/location_bar/ai_mode_page_action_icon_view.h" #include "chrome/browser/ui/views/location_bar/cookie_controls/cookie_controls_icon_view.h" @@ -126,11 +125,6 @@ params.browser, params.icon_label_bubble_delegate, params.page_action_icon_delegate)); break; - case PageActionIconType::kDiscounts: - add_page_action_icon(type, std::make_unique<DiscountsIconView>( - params.icon_label_bubble_delegate, - params.page_action_icon_delegate)); - break; case PageActionIconType::kFind: add_page_action_icon( type, std::make_unique<FindBarIcon>(
diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/browser/ui/views/page_info/page_info_main_view.cc index 976bde67..bd3c60f 100644 --- a/chrome/browser/ui/views/page_info/page_info_main_view.cc +++ b/chrome/browser/ui/views/page_info/page_info_main_view.cc
@@ -28,8 +28,10 @@ #include "chrome/browser/ui/views/page_info/page_info_view_factory.h" #include "chrome/browser/ui/views/page_info/permission_toggle_row_view.h" #include "chrome/browser/ui/views/page_info/star_rating_view.h" +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" #include "chrome/browser/vr/vr_tab_helper.h" #include "chrome/common/url_constants.h" +#include "chrome/grit/generated_resources.h" #include "components/page_info/core/about_this_site_service.h" #include "components/page_info/core/features.h" #include "components/page_info/page_info_ui_delegate.h" @@ -249,6 +251,9 @@ UpdateResetButton(permission_info_list); return; } + + ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get(); + const int separator_padding = GetSeparatorPadding(); permissions_view_->AddChildView( PageInfoViewFactory::CreateSeparator(GetSeparatorPadding())); @@ -266,6 +271,31 @@ content_view->SetProperty(views::kElementIdentifierKey, kPermissionsElementId); + const int controls_spacing = layout_provider->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL); + const int side_button_padding = + layout_provider + ->GetInsetsMetric(ChromeInsetsMetric::INSETS_PAGE_INFO_HOVER_BUTTON) + .left(); + + if (std::optional<std::u16string> explanation = + GetSubAppsPermissionExplanation(ui_delegate_->GetWebContents())) { + auto* label = content_view->AddChildView(std::make_unique<views::Label>( + *explanation, views::style::CONTEXT_DIALOG_BODY_TEXT, + views::style::STYLE_BODY_4)); + label->SetMultiLine(true); + label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + label->SetProperty( + views::kMarginsKey, + gfx::Insets::TLBR(controls_spacing, side_button_padding, + controls_spacing, side_button_padding)); + label->SetProperty(views::kCrossAxisAlignmentKey, + views::LayoutAlignment::kStretch); + label->SetMaximumWidth(layout_provider->GetDistanceMetric( + views::DISTANCE_BUBBLE_PREFERRED_WIDTH) - + side_button_padding * 2); + } + // If there is a permission that supports one time grants, offset all other // permissions to align toggles. bool should_show_spacer = false; @@ -298,8 +328,6 @@ content_view->AddChildView(std::move(object_view))); } - const int controls_spacing = ChromeLayoutProvider::Get()->GetDistanceMetric( - views::DISTANCE_RELATED_CONTROL_VERTICAL); reset_button_ = content_view->AddChildView( std::make_unique<views::MdTextButton>(base::BindRepeating( [=](PageInfoMainView* view) { @@ -317,16 +345,12 @@ base::Unretained(this)))); reset_button_->SetProperty(views::kCrossAxisAlignmentKey, views::LayoutAlignment::kStart); - ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get(); // Offset the reset button by left button padding, icon size and distance // between icon and label to match text in the row above. - const int side_offset = - layout_provider - ->GetInsetsMetric(ChromeInsetsMetric::INSETS_PAGE_INFO_HOVER_BUTTON) - .left() + - GetLayoutConstant(LayoutConstant::kPageInfoIconSize) + - layout_provider->GetDistanceMetric( - views::DISTANCE_RELATED_LABEL_HORIZONTAL); + const int side_offset = side_button_padding + + GetLayoutConstant(LayoutConstant::kPageInfoIconSize) + + layout_provider->GetDistanceMetric( + views::DISTANCE_RELATED_LABEL_HORIZONTAL); reset_button_->SetProperty( views::kMarginsKey, gfx::Insets::TLBR(controls_spacing, side_offset, controls_spacing, 0));
diff --git a/chrome/browser/ui/views/page_info/page_info_permission_content_view.cc b/chrome/browser/ui/views/page_info/page_info_permission_content_view.cc index 8c914d70..692c559 100644 --- a/chrome/browser/ui/views/page_info/page_info_permission_content_view.cc +++ b/chrome/browser/ui/views/page_info/page_info_permission_content_view.cc
@@ -26,6 +26,7 @@ #include "chrome/browser/ui/views/controls/rich_hover_button.h" #include "chrome/browser/ui/views/file_system_access/file_system_access_scroll_panel.h" #include "chrome/browser/ui/views/page_info/page_info_view_factory.h" +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" #include "components/content_settings/core/browser/permission_settings_registry.h" #include "components/page_info/page_info.h" #include "components/permissions/permission_util.h" @@ -172,6 +173,27 @@ MaybeAddMediaPreview(web_contents, *separator); + if (web_contents_.MaybeValid()) { + if (std::optional<std::u16string> explanation = + GetSubAppsPermissionExplanation(web_contents_.get())) { + auto* custom_label = AddChildView(std::make_unique<views::Label>( + *explanation, views::style::CONTEXT_DIALOG_BODY_TEXT, + views::style::STYLE_BODY_4)); + custom_label->SetMultiLine(true); + custom_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + custom_label->SetProperty(views::kCrossAxisAlignmentKey, + views::LayoutAlignment::kStretch); + custom_label->SetProperty( + views::kMarginsKey, + gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL), + 0)); + custom_label->SetMaximumWidth( + ChromeLayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_BUBBLE_PREFERRED_WIDTH)); + } + } + // TODO(crbug.com/40775890): Consider to use permission specific text. auto* subpage_manage_button = AddChildView(std::make_unique<RichHoverButton>( base::BindRepeating(
diff --git a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc index 9432475c..e984704d 100644 --- a/chrome/browser/ui/views/passwords/password_bubble_view_base.cc +++ b/chrome/browser/ui/views/passwords/password_bubble_view_base.cc
@@ -10,6 +10,7 @@ #include "chrome/browser/ui/actions/chrome_action_id.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_actions.h" +#include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/passwords/passwords_model_delegate.h" #include "chrome/browser/ui/views/accessibility/theme_tracking_non_accessible_image_view.h" @@ -86,8 +87,8 @@ // highlighted button by BubbleDialogDelegate. If not, we set the page action // icon as the highlighted button here. if (!bubble_anchor_util::IsHighlightable(anchor)) { - g_manage_passwords_bubble_->SetHighlightedButton( - button_provider->GetPageActionView(kActionShowPasswordsBubbleOrPage)); + g_manage_passwords_bubble_->SetHighlightedElement( + kPasswordsOmniboxKeyIconElementId); } views::BubbleDialogDelegateView::CreateBubble(g_manage_passwords_bubble_);
diff --git a/chrome/browser/ui/views/performance_controls/memory_saver_bubble_view.cc b/chrome/browser/ui/views/performance_controls/memory_saver_bubble_view.cc index beef9f8..eb11e72 100644 --- a/chrome/browser/ui/views/performance_controls/memory_saver_bubble_view.cc +++ b/chrome/browser/ui/views/performance_controls/memory_saver_bubble_view.cc
@@ -145,11 +145,7 @@ auto bubble_unique = std::make_unique<views::BubbleDialogModelHost>( std::move(dialog_model), anchor, views::BubbleBorder::TOP_RIGHT); auto* bubble = bubble_unique.get(); - auto* const toolbar_button_provider = - BrowserView::GetBrowserViewForBrowser(browser)->toolbar_button_provider(); - views::Button* highlighted_button = - toolbar_button_provider->GetPageActionView(kActionShowMemorySaverChip); - bubble->SetHighlightedButton(highlighted_button); + bubble->SetHighlightedElement(kMemorySaverChipElementId); views::Widget* const widget = views::BubbleDialogDelegate::CreateBubble(std::move(bubble_unique));
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc index 3d61f5d..9534b03 100644 --- a/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc +++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc
@@ -422,8 +422,8 @@ ->GetContentSettingBubbleModelDelegate(), web_contents), web_contents, anchor, views::BubbleBorder::TOP_LEFT); - bubble_view_->SetHighlightedButton( - permission_dashboard_view_->GetIndicatorChip()); + bubble_view_->SetHighlightedElement( + PermissionChipView::kIndicatorChipElementId); views::Widget* bubble_widget = views::BubbleDialogDelegateView::CreateBubble(bubble_view_); bubble_widget->Show();
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc index f20df0c..4e336959 100644 --- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc +++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
@@ -12,6 +12,7 @@ #include "chrome/browser/ui/views/chrome_layout_provider.h" #include "chrome/browser/ui/views/chrome_widget_sublevel.h" #include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" #include "components/permissions/features.h" #include "components/permissions/permission_util.h" #include "components/vector_icons/vector_icons.h" @@ -293,6 +294,25 @@ AddRequestLine(request, index++); } + if (delegate_ && delegate_->GetPermissionPromptDelegate()) { + if (std::optional<std::u16string> explanation = + GetSubAppsPermissionExplanation( + delegate_->GetPermissionPromptDelegate() + ->GetAssociatedWebContents())) { + auto custom_label = std::make_unique<views::Label>( + *explanation, views::style::CONTEXT_DIALOG_BODY_TEXT, + views::style::STYLE_BODY_4); + custom_label->SetMultiLine(true); + custom_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + custom_label->SetProperty( + views::kMarginsKey, + gfx::Insets::VH(views::LayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL), + 0)); + AddChildView(std::move(custom_label)); + } + } + SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone)); auto buttons_container = std::make_unique<views::View>();
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc index 907ffdd..a8dcb129 100644 --- a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc +++ b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc
@@ -17,11 +17,14 @@ #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/views/bubble_anchor_util_views.h" #include "chrome/browser/ui/views/title_origin_label.h" +#include "chrome/browser/web_applications/web_app_helpers.h" #include "chrome/grit/generated_resources.h" #include "components/permissions/features.h" #include "components/permissions/permission_request.h" #include "components/permissions/permission_util.h" #include "components/strings/grit/components_strings.h" +#include "components/webapps/isolated_web_apps/scheme.h" +#include "content/public/browser/web_contents.h" #include "ui/base/interaction/element_tracker.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/metadata/metadata_impl_macros.h" @@ -194,11 +197,16 @@ Browser* browser, permissions::PermissionPrompt::Delegate& delegate) { DCHECK(!delegate.Requests().empty()); - GURL origin_url = delegate.GetRequestingOrigin(); - UrlIdentity url_identity = - UrlIdentity::CreateFromUrl(browser ? browser->profile() : nullptr, - origin_url, allowed_types, options); + GURL origin_url = delegate.GetRequestingOrigin(); + // Use the full URL for Isolated Web Apps to match it with the app scope. + GURL url = origin_url.SchemeIs(webapps::kIsolatedAppScheme) && + delegate.GetAssociatedWebContents() + ? delegate.GetAssociatedWebContents()->GetLastCommittedURL() + : origin_url; + + UrlIdentity url_identity = UrlIdentity::CreateFromUrl( + browser ? browser->profile() : nullptr, url, allowed_types, options); if (url_identity.type == UrlIdentity::Type::kFile) { // File URLs will show the same constant. @@ -208,7 +216,6 @@ return url_identity; } - std::u16string PermissionPromptBaseView::GetAllowAlwaysText( const std::vector<std::unique_ptr<permissions::PermissionRequest>>& visible_requests) {
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc index e0667cf..c7cd873 100644 --- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc +++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc
@@ -18,6 +18,7 @@ #include "chrome/browser/ui/views/chrome_widget_sublevel.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/location_bar/location_bar_view.h" +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" #include "chrome/browser/ui/web_applications/app_browser_controller.h" #include "components/content_settings/core/common/content_settings_types.h" #include "components/permissions/permission_request.h" @@ -90,6 +91,24 @@ void PermissionPromptBubbleBaseView::CreatePermissionButtons( const std::u16string& allow_always_text, const std::u16string& block_text) { + if (delegate_) { + if (std::optional<std::u16string> explanation = + GetSubAppsPermissionExplanation( + delegate_->GetAssociatedWebContents())) { + auto custom_label = std::make_unique<views::Label>( + *explanation, views::style::CONTEXT_DIALOG_BODY_TEXT, + views::style::STYLE_BODY_4); + custom_label->SetMultiLine(true); + custom_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + custom_label->SetProperty( + views::kMarginsKey, + gfx::Insets::VH(views::LayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_RELATED_CONTROL_VERTICAL), + 0)); + AddChildView(std::move(custom_label)); + } + } + if (is_one_time_permission_) { SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
diff --git a/chrome/browser/ui/views/sub_apps_permission_explanation.cc b/chrome/browser/ui/views/sub_apps_permission_explanation.cc new file mode 100644 index 0000000..cd1c3d6f --- /dev/null +++ b/chrome/browser/ui/views/sub_apps_permission_explanation.cc
@@ -0,0 +1,66 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/sub_apps_permission_explanation.h" + +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/web_applications/web_app.h" +#include "chrome/browser/web_applications/web_app_filter.h" +#include "chrome/browser/web_applications/web_app_provider.h" +#include "chrome/browser/web_applications/web_app_registrar.h" +#include "chrome/browser/web_applications/web_app_tab_helper.h" +#include "chrome/grit/generated_resources.h" +#include "components/webapps/common/web_app_id.h" +#include "content/public/browser/web_contents.h" +#include "ui/base/l10n/l10n_util.h" + +std::optional<std::u16string> GetSubAppsPermissionExplanation( + content::WebContents* web_contents) { + if (!web_contents) { + return std::nullopt; + } + + const webapps::AppId* app_id_ptr = + web_app::WebAppTabHelper::GetAppId(web_contents); + if (!app_id_ptr) { + return std::nullopt; + } + + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); + auto* provider = web_app::WebAppProvider::GetForWebApps(profile); + if (!provider) { + return std::nullopt; + } + + const web_app::WebAppRegistrar& registrar = provider->registrar_unsafe(); + + // Show if it's an isolated sub app. + if (registrar.AppMatches(*app_id_ptr, + web_app::WebAppFilter::IsIsolatedSubApp())) { + const web_app::WebApp* app = registrar.GetAppById(*app_id_ptr); + if (app && app->parent_app_id().has_value()) { + std::string app_name = registrar.GetAppShortName(*app_id_ptr); + std::string parent_app_name = + registrar.GetAppShortName(app->parent_app_id().value()); + return l10n_util::GetStringFUTF16( + IDS_APP_MANAGEMENT_IS_SUB_APP_PERMISSION_EXPLANATION, + base::UTF8ToUTF16(app_name), base::UTF8ToUTF16(parent_app_name)); + } + } + + // Show if it's an isolated web app that has sub apps. + if (registrar.AppMatches(*app_id_ptr, + web_app::WebAppFilter::IsIsolatedApp())) { + if (!registrar.GetAllSubAppIds(*app_id_ptr).empty()) { + std::string app_name = registrar.GetAppShortName(*app_id_ptr); + return l10n_util::GetStringFUTF16( + IDS_APP_MANAGEMENT_HAS_SUB_APPS_PERMISSION_EXPLANATION, + base::UTF8ToUTF16(app_name)); + } + } + + return std::nullopt; +}
diff --git a/chrome/browser/ui/views/sub_apps_permission_explanation.h b/chrome/browser/ui/views/sub_apps_permission_explanation.h new file mode 100644 index 0000000..a9d783f --- /dev/null +++ b/chrome/browser/ui/views/sub_apps_permission_explanation.h
@@ -0,0 +1,21 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_SUB_APPS_PERMISSION_EXPLANATION_H_ +#define CHROME_BROWSER_UI_VIEWS_SUB_APPS_PERMISSION_EXPLANATION_H_ + +#include <optional> +#include <string> + +namespace content { +class WebContents; +} + +// When the app in `web_contents` is an IWA sub app or a parent IWA with sub +// apps, this returns a string explaining that sub apps share permissions with +// the parent IWA app. Otherwise returns `std::nullopt`. +std::optional<std::u16string> GetSubAppsPermissionExplanation( + content::WebContents* web_contents); + +#endif // CHROME_BROWSER_UI_VIEWS_SUB_APPS_PERMISSION_EXPLANATION_H_
diff --git a/chrome/browser/ui/views/toolbar/BUILD.gn b/chrome/browser/ui/views/toolbar/BUILD.gn index 47253bf8..740bce9 100644 --- a/chrome/browser/ui/views/toolbar/BUILD.gn +++ b/chrome/browser/ui/views/toolbar/BUILD.gn
@@ -41,6 +41,7 @@ "toolbar_icon_container_view.h", "toolbar_ink_drop_util.h", "toolbar_view.h", + "webui_back_forward_control.h", "webui_reload_control.h", "webui_split_tabs_control.h", "webui_toolbar_web_view.h", @@ -129,6 +130,7 @@ "toolbar_icon_container_view.cc", "toolbar_ink_drop_util.cc", "toolbar_view.cc", + "webui_back_forward_control.cc", "webui_reload_control.cc", "webui_split_tabs_control.cc", "webui_toolbar_web_view.cc",
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc index 546b33a9..dad17aa 100644 --- a/chrome/browser/ui/views/toolbar/app_menu.cc +++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -97,6 +97,7 @@ #include "ui/gfx/scoped_canvas.h" #include "ui/gfx/text_elider.h" #include "ui/gfx/text_utils.h" +#include "ui/gfx/vector_icon_types.h" #include "ui/menus/simple_menu_model.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/background.h" @@ -336,9 +337,8 @@ void Init(InMenuButtonBackground::ButtonType type, InMenuButtonBackground::ButtonShape shape, - const ui::ImageModel& image_model) { + const gfx::VectorIcon& vector_icon) { SetFocusBehavior(FocusBehavior::ALWAYS); - SetImageModel(views::Button::STATE_NORMAL, image_model); SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER); SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE); SetBackground(std::make_unique<InMenuButtonBackground>(type, shape)); @@ -346,6 +346,15 @@ gfx::Insets::TLBR(0, kHorizontalPadding, 0, kHorizontalPadding))); GetViewAccessibility().SetRole(ax::mojom::Role::kMenuItem); + SetImageModel(views::Button::STATE_NORMAL, + ui::ImageModel::FromVectorIcon(vector_icon, + ui::kColorMenuItemForeground)); + SetImageModel(ImageButton::STATE_HOVERED, + ui::ImageModel::FromVectorIcon( + vector_icon, ui::kColorMenuItemForegroundSelected)); + SetImageModel(ImageButton::STATE_PRESSED, + ui::ImageModel::FromVectorIcon( + vector_icon, ui::kColorMenuItemForegroundSelected)); } }; @@ -485,14 +494,14 @@ int accessible_name_id, bool add_accelerator_text, bool use_accessible_name_as_tooltip_text, - const ui::ImageModel image_model = ui::ImageModel()) { + const gfx::VectorIcon& vector_icon = gfx::VectorIcon::EmptyIcon()) { // Should only be invoked during construction when |menu_| is valid. DCHECK(menu_); std::unique_ptr<views::Button> menu_button; auto button = std::make_unique<InMenuImageButton>(std::move(callback)); button->Init(type, InMenuButtonBackground::ButtonShape::kCircular, - image_model); + vector_icon); menu_button = std::move(button); menu_button->GetViewAccessibility().SetName(GetAccessibleNameForAppMenuItem( menu_model_, index, accessible_name_id, add_accelerator_text)); @@ -694,15 +703,13 @@ const auto activate = [](ButtonMenuItemModel* menu_model, size_t index) { menu_model->ActivatedAt(index); }; - auto image_model = ui::ImageModel::FromVectorIcon( - kZoomMinusMenuRefreshIcon, ui::kColorMenuItemForeground); decrement_button_ = CreateButtonWithAccessibleName( base::BindRepeating(activate, menu_model, decrement_index), IDS_ZOOM_MINUS2, InMenuButtonBackground::ButtonType::kNoBorder, decrement_index, IDS_ACCNAME_ZOOM_MINUS2, /*add_accelerator_text=*/false, /*use_accessible_name_as_tooltip_text=*/true, - /*image_model=*/image_model); + /*vector_icon=*/kZoomMinusMenuRefreshIcon); auto zoom_label = std::make_unique<Label>(base::FormatPercent(100)); zoom_label->SetAutoColorReadabilityEnabled(false); @@ -731,14 +738,12 @@ zoom_label_ = AddChildView(std::move(zoom_label)); - image_model = ui::ImageModel::FromVectorIcon(kZoomPlusMenuRefreshIcon, - ui::kColorMenuItemForeground); increment_button_ = CreateButtonWithAccessibleName( base::BindRepeating(activate, menu_model, increment_index), IDS_ZOOM_PLUS2, InMenuButtonBackground::ButtonType::kNoBorder, increment_index, IDS_ACCNAME_ZOOM_PLUS2, /*add_accelerator_text=*/false, /*use_accessible_name_as_tooltip_text=*/true, - /*image_model=*/image_model); + /*vector_icon=*/kZoomPlusMenuRefreshIcon); auto fullscreen_button = std::make_unique<FullscreenButton>( base::BindRepeating( @@ -1319,10 +1324,10 @@ if (command_id == IDC_CREATE_NEW_TAB_GROUP_TOP_LEVEL) { // Same as 'Create new tab group' except the menu item is at the top level // of the app menu instead of in the tab groups submenu. - return browser_->browser_window_features() - ->accelerator_provider() - ->GetAcceleratorForCommandId(IDC_CREATE_NEW_TAB_GROUP, accelerator); - } + return browser_->browser_window_features() + ->accelerator_provider() + ->GetAcceleratorForCommandId(IDC_CREATE_NEW_TAB_GROUP, accelerator); + } if (IsTabGroupsCommand(command_id)) { return false;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc index 87d72cc..c63ab3b 100644 --- a/chrome/browser/ui/views/toolbar/toolbar_view.cc +++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -101,6 +101,7 @@ #include "chrome/browser/ui/views/toolbar/toolbar_glic_actor_task_icon.h" #include "chrome/browser/ui/views/toolbar/toolbar_glic_button.h" #include "chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h" +#include "chrome/browser/ui/views/toolbar/webui_back_forward_control.h" #include "chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h" #include "chrome/browser/ui/views/zoom/zoom_view_controller.h" #include "chrome/browser/ui/waap/initial_webui_window_metrics_manager.h" @@ -439,14 +440,14 @@ #endif // Always add children in order from left to right, for accessibility. - - back_ = AddChildView(std::make_unique<BackForwardButton>( - BackForwardButton::Direction::kBack, - base::BindRepeating(callback, browser_, IDC_BACK), browser_)); - - forward_ = AddChildView(std::make_unique<BackForwardButton>( - BackForwardButton::Direction::kForward, - base::BindRepeating(callback, browser_, IDC_FORWARD), browser_)); + if (!features::IsWebUIBackForwardButtonEnabled()) { + back_ = AddChildView(std::make_unique<BackForwardButton>( + BackForwardButton::Direction::kBack, + base::BindRepeating(callback, browser_, IDC_BACK), browser_)); + forward_ = AddChildView(std::make_unique<BackForwardButton>( + BackForwardButton::Direction::kForward, + base::BindRepeating(callback, browser_, IDC_FORWARD), browser_)); + } if (features::IsWebUIToolbarEnabled()) { toolbar_webview_ = AddChildView(std::make_unique<WebUIToolbarWebView>( @@ -632,7 +633,7 @@ base::BindRepeating(&ToolbarView::OnShowForwardButtonChanged, base::Unretained(this))); - forward_->SetVisible(show_forward_button_.GetValue()); + SetForwardButtonVisibility(show_forward_button_.GetValue()); show_home_button_.Init( prefs::kShowHomeButton, prefs, @@ -1028,6 +1029,13 @@ void ToolbarView::EnabledStateChangedForCommand(int id, bool enabled) { DCHECK(display_mode_ == DisplayMode::kNormal); + + if ((id == IDC_BACK || id == IDC_FORWARD) && + features::IsWebUIBackForwardButtonEnabled()) { + toolbar_webview_->SetBackForwardEnabled(id, enabled); + return; + } + const std::array<views::Button*, 5> kButtons{back_, forward_, reload_, home_, avatar_}; auto it = std::ranges::find_if( @@ -1069,7 +1077,7 @@ // on some installations. if (layout_manager_ && location_bar_->IsVisible()) { const int max_height = std::max(location_bar_->PreferredSize().height(), - back_->GetPreferredSize().height()) + + GetBackForwardButtonSize().height()) + layout_manager_->interior_margin().height(); size.SetToMin({size.width(), max_height}); } @@ -1096,9 +1104,10 @@ // TODO(crbug.com/40663413): Figure out why the height reports incorrectly // on some installations. if (layout_manager_ && location_bar_->IsVisible()) { - const int max_height = std::max(location_bar_->MinimumSize().height(), - back_->GetMinimumSize().height()) + - layout_manager_->interior_margin().height(); + const int max_height = + std::max(location_bar_->MinimumSize().height(), + GetBackForwardButtonSize(/*minimum_size=*/true).height()) + + layout_manager_->interior_margin().height(); size.SetToMin({size.width(), max_height}); } // Overflow button must be part of minimum size calculation. @@ -1316,7 +1325,12 @@ const bool extend_buttons_to_edge = browser_->window() && (browser_->window()->IsMaximized() || browser_->window()->IsFullscreen()); - back_->SetLeadingMargin(extend_buttons_to_edge ? interior_margin.left() : 0); + const int margin = extend_buttons_to_edge ? interior_margin.left() : 0; + if (features::IsWebUIBackForwardButtonEnabled()) { + toolbar_webview_->SetBackButtonLeadingMargin(margin); + } else { + back_->SetLeadingMargin(margin); + } app_menu_button_->SetTrailingMargin( extend_buttons_to_edge ? interior_margin.right() : 0); @@ -1541,7 +1555,7 @@ } void ToolbarView::OnShowForwardButtonChanged() { - forward_->SetVisible(show_forward_button_.GetValue()); + SetForwardButtonVisibility(show_forward_button_.GetValue()); InvalidateLayout(); } @@ -1572,6 +1586,22 @@ } } +void ToolbarView::SetForwardButtonVisibility(bool visible) { + if (features::IsWebUIBackForwardButtonEnabled()) { + toolbar_webview_->SetForwardVisible(visible); + } else { + forward_->SetVisible(visible); + } +} + +gfx::Size ToolbarView::GetBackForwardButtonSize(bool minimum_size) const { + if (back_) { + return minimum_size ? back_->GetMinimumSize() : back_->GetPreferredSize(); + } + const int size = GetLayoutConstant(LayoutConstant::kToolbarButtonHeight); + return gfx::Size(size, size); +} + BEGIN_METADATA(ToolbarView) ADD_READONLY_PROPERTY_METADATA(bool, AppMenuFocused) END_METADATA
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h index 847a138..047c384 100644 --- a/chrome/browser/ui/views/toolbar/toolbar_view.h +++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -291,6 +291,10 @@ void OnVerticalTabStripModeChanged( tabs::VerticalTabStripStateController* controller); + void SetForwardButtonVisibility(bool visible); + + gfx::Size GetBackForwardButtonSize(bool minimum_size = false) const; + std::unique_ptr<glic::ToolbarGlicButton> CreateGlicButton(); void OnGlicButtonClicked(); void OnGlicButtonDismissed();
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc index 6ce0cde5..eb33da0 100644 --- a/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc +++ b/chrome/browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc
@@ -335,6 +335,8 @@ RunToolbarCycleFocusTest(second_browser); } +// TODO(crbug.com/470038385): Include WebUI version back forward buttons in this +// test. IN_PROC_BROWSER_TEST_P(ToolbarViewTest, BackButtonUpdate) { ToolbarButtonProvider* toolbar_button_provider = BrowserView::GetBrowserViewForBrowser(browser())->toolbar(); @@ -355,6 +357,8 @@ EXPECT_FALSE(back_button->GetEnabled()); } +// TODO(crbug.com/470038385): Include WebUI version back forward buttons in this +// test. IN_PROC_BROWSER_TEST_P(ToolbarViewTest, BackButtonHoverThenClick) { ToolbarButtonProvider* toolbar_button_provider = BrowserView::GetBrowserViewForBrowser(browser())->toolbar(); @@ -377,6 +381,8 @@ EXPECT_FALSE(back_button->GetEnabled()); } +// TODO(crbug.com/470038385): Include WebUI version back forward buttons in this +// test. // TODO(crbug.com/40252318): The ui test utils do not seem to adequately // simulate mouse hovering on Mac. #if BUILDFLAG(IS_MAC) @@ -457,8 +463,11 @@ EXPECT_EQ(nullptr, extensions_container); } -// Verifies that the identifiers for the pop-up menus are properly assigned so -// that the menu can be located by tests when it is shown. +// TODO(crbug.com/470038385): Include WebUI version back forward buttons in this +// test. +// +// Verifies that the identifiers for the pop-up menus are properly +// assigned so that the menu can be located by tests when it is shown. // // The back button is just one example for which the menu identifier is defined. IN_PROC_BROWSER_TEST_P(ToolbarViewTest, BackButtonMenu) {
diff --git a/chrome/browser/ui/views/toolbar/webui_back_forward_control.cc b/chrome/browser/ui/views/toolbar/webui_back_forward_control.cc new file mode 100644 index 0000000..8d91a67 --- /dev/null +++ b/chrome/browser/ui/views/toolbar/webui_back_forward_control.cc
@@ -0,0 +1,63 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/toolbar/webui_back_forward_control.h" + +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" +#include "chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h" +#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h" +#include "ui/base/mojom/menu_source_type.mojom.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/view.h" + +WebUIBackForwardControl::WebUIBackForwardControl( + WebUIToolbarWebView* webui_toolbar_web_view, + BackForwardButton::Direction direction) + : webui_toolbar_web_view_(webui_toolbar_web_view), + direction_(direction), + menu_model_( + webui_toolbar_web_view->browser_->GetBrowserForMigrationOnly(), + direction == BackForwardButton::Direction::kBack + ? BackForwardMenuModel::ModelType::kBackward + : BackForwardMenuModel::ModelType::kForward) {} + +WebUIBackForwardControl::~WebUIBackForwardControl() = default; + +void WebUIBackForwardControl::HandleContextMenu( + views::Widget* widget, + gfx::Point screen_location, + ui::mojom::MenuSourceType source) { + menu_runner_ = std::make_unique<views::MenuRunner>( + &menu_model_, views::MenuRunner::HAS_MNEMONICS); + menu_runner_->RunMenuAt(webui_toolbar_web_view_->GetWidget(), nullptr, + gfx::Rect(screen_location, gfx::Size()), + views::MenuAnchorPosition::kTopLeft, source); +} + +void WebUIBackForwardControl::SetEnabled(bool enabled) { + enabled_ = enabled; + webui_toolbar_web_view_->OnBackForwardStateChanged(); +} + +void WebUIBackForwardControl::SetVisible(bool visible) { + visible_ = visible; + webui_toolbar_web_view_->OnBackForwardStateChanged(); +} + +bool WebUIBackForwardControl::GetVisible() const { + return visible_; +} + +void WebUIBackForwardControl::SetLeadingMargin(int margin) { + if (direction_ == BackForwardButton::Direction::kBack) { + webui_toolbar_web_view_->SetBackButtonLeadingMargin(margin); + } +} + +toolbar_ui_api::mojom::ButtonStatePtr WebUIBackForwardControl::GetButtonState() + const { + return toolbar_ui_api::mojom::ButtonState::New(/*enabled=*/enabled_, + /*visible=*/visible_); +}
diff --git a/chrome/browser/ui/views/toolbar/webui_back_forward_control.h b/chrome/browser/ui/views/toolbar/webui_back_forward_control.h new file mode 100644 index 0000000..60f9224 --- /dev/null +++ b/chrome/browser/ui/views/toolbar/webui_back_forward_control.h
@@ -0,0 +1,53 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_TOOLBAR_WEBUI_BACK_FORWARD_CONTROL_H_ +#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_WEBUI_BACK_FORWARD_CONTROL_H_ + +#include "base/memory/raw_ptr.h" +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" +#include "chrome/browser/ui/views/toolbar/back_forward_button.h" +#include "components/browser_apis/browser_controls/browser_controls_api_data_model.mojom.h" +#include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom-forward.h" +#include "ui/base/mojom/menu_source_type.mojom-forward.h" +#include "ui/gfx/geometry/point.h" +#include "ui/views/controls/menu/menu_runner.h" + +namespace views { +class Widget; +} // namespace views + +class WebUIToolbarWebView; + +// A WebUI-based implementation of the Back/Forward control. +// This class manages the communication with the WebUI via Mojo. +class WebUIBackForwardControl { + public: + WebUIBackForwardControl(WebUIToolbarWebView* webui_toolbar_web_view, + BackForwardButton::Direction direction); + WebUIBackForwardControl(const WebUIBackForwardControl&) = delete; + WebUIBackForwardControl& operator=(const WebUIBackForwardControl&) = delete; + ~WebUIBackForwardControl(); + + void HandleContextMenu(views::Widget* widget, + gfx::Point screen_location, + ui::mojom::MenuSourceType source); + + void SetEnabled(bool enabled); + void SetVisible(bool visible); + bool GetVisible() const; + void SetLeadingMargin(int margin); + + toolbar_ui_api::mojom::ButtonStatePtr GetButtonState() const; + + private: + const raw_ptr<WebUIToolbarWebView> webui_toolbar_web_view_; + const BackForwardButton::Direction direction_; + BackForwardMenuModel menu_model_; + std::unique_ptr<views::MenuRunner> menu_runner_; + bool enabled_ = true; + bool visible_ = true; +}; + +#endif // CHROME_BROWSER_UI_VIEWS_TOOLBAR_WEBUI_BACK_FORWARD_CONTROL_H_
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc index ccf3302..cfd827b 100644 --- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc +++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
@@ -16,6 +16,7 @@ #include "base/time/default_tick_clock.h" #include "base/time/time.h" #include "base/trace_event/named_trigger.h" +#include "chrome/app/chrome_command_ids.h" #include "chrome/browser/external_protocol/external_protocol_handler.h" #include "chrome/browser/lifetime/browser_shutdown.h" #include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h" @@ -130,6 +131,8 @@ reload_control_(this), split_tabs_control_(this), location_bar_(std::move(location_bar)), + back_control_(this, BackForwardButton::Direction::kBack), + forward_control_(this, BackForwardButton::Direction::kForward), clock_(base::DefaultTickClock::GetInstance()), touch_ui_subscription_(ui::TouchUiController::Get()->RegisterCallback( base::BindRepeating(&WebUIToolbarWebView::OnTouchUiChanged, @@ -140,6 +143,8 @@ last_queued_state_.reload_control_state = toolbar_ui_api::mojom::ReloadControlState::New(); last_queued_state_.layout_constants_version = 0; + last_queued_state_.back_forward_control_state = GetBackForwardState(); + if (auto* manager = InitialWebUIWindowMetricsManager::From(browser_)) { manager->OnReloadButtonCreated(); } @@ -202,6 +207,9 @@ button_count += features::IsWebUIReloadButtonEnabled(); button_count += features::IsWebUISplitTabsButtonEnabled() && split_tabs_control_.IsVisible(); + button_count += features::IsWebUIBackForwardButtonEnabled(); + button_count += features::IsWebUIBackForwardButtonEnabled() && + forward_control_.GetVisible(); const int size = GetLayoutConstant(LayoutConstant::kToolbarButtonHeight); int width = button_count * size; @@ -214,6 +222,11 @@ // TODO(http://crbug.com/470042732): Where is the 4px margin from? width += 4 + location_bar_->PreferredSize().width(); } + + if (features::IsWebUIBackForwardButtonEnabled()) { + width += back_button_leading_margin_; + } + return gfx::Size(width, size); } @@ -234,6 +247,12 @@ .OffsetFromOrigin(); switch (menu_type) { + case toolbar_ui_api::mojom::ContextMenuType::kBack: + back_control_.HandleContextMenu(GetWidget(), screen_location, source); + break; + case toolbar_ui_api::mojom::ContextMenuType::kForward: + forward_control_.HandleContextMenu(GetWidget(), screen_location, source); + break; case toolbar_ui_api::mojom::ContextMenuType::kReload: reload_control_.HandleContextMenu(GetWidget(), screen_location, source); break; @@ -305,6 +324,25 @@ ui->Init(this); } +void WebUIToolbarWebView::SetBackButtonLeadingMargin(int margin) { + back_button_leading_margin_ = margin; + OnBackForwardStateChanged(); + PreferredSizeChanged(); +} + +void WebUIToolbarWebView::SetBackForwardEnabled(int command_id, bool enabled) { + if (command_id == IDC_BACK) { + back_control_.SetEnabled(enabled); + } else { + forward_control_.SetEnabled(enabled); + } +} + +void WebUIToolbarWebView::SetForwardVisible(bool visible) { + forward_control_.SetVisible(visible); + PreferredSizeChanged(); +} + void WebUIToolbarWebView::DidFirstVisuallyNonEmptyPaint() { has_finished_first_non_empty_paint_ = true; if (did_first_non_empty_paint_callback_) { @@ -437,6 +475,14 @@ } } +void WebUIToolbarWebView::OnBackForwardStateChanged() { + auto state = GetBackForwardState(); + if (*state != *last_queued_state_.back_forward_control_state) { + last_queued_state_.back_forward_control_state = std::move(state); + PostPushNavigationState(); + } +} + void WebUIToolbarWebView::OnTouchUiChanged() { ++last_queued_state_.layout_constants_version; PostPushNavigationState(); @@ -457,5 +503,14 @@ } } +toolbar_ui_api::mojom::BackForwardControlStatePtr +WebUIToolbarWebView::GetBackForwardState() const { + auto state = toolbar_ui_api::mojom::BackForwardControlState::New(); + state->back_button_state = back_control_.GetButtonState(); + state->forward_button_state = forward_control_.GetButtonState(); + state->back_button_leading_margin = back_button_leading_margin_; + return state; +} + BEGIN_METADATA(WebUIToolbarWebView) END_METADATA
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h index 27eafa10..d7ba3001 100644 --- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h +++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.h
@@ -9,6 +9,7 @@ #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "chrome/browser/ui/browser_command_controller.h" +#include "chrome/browser/ui/views/toolbar/webui_back_forward_control.h" #include "chrome/browser/ui/views/toolbar/webui_reload_control.h" #include "chrome/browser/ui/views/toolbar/webui_split_tabs_control.h" #include "chrome/browser/ui/webui/webui_toolbar/adapters/navigation_controls_state_fetcher.h" @@ -53,6 +54,10 @@ ReloadControl* GetReloadControl(); + void SetBackButtonLeadingMargin(int margin); + void SetBackForwardEnabled(int command_id, bool enabled); + void SetForwardVisible(bool visible); + // May be nullptr. WebUILocationBar* GetLocationBar() { return location_bar_.get(); } @@ -101,6 +106,7 @@ CheckSplitTabsButtonSourceType); friend WebUIReloadControl; friend WebUISplitTabsControl; + friend WebUIBackForwardControl; toolbar_ui_api::mojom::NavigationControlsStatePtr GetNavigationControlsState(); @@ -130,11 +136,13 @@ toolbar_ui_api::mojom::ReloadControlStatePtr state); void OnSplitTabsControlStateChanged( toolbar_ui_api::mojom::SplitTabsControlStatePtr state); + void OnBackForwardStateChanged(); void OnTouchUiChanged(); void PostPushNavigationState(); void PushNavigationState(uint64_t state_generation); toolbar_ui_api::mojom::NavigationControlsState last_queued_state_; + toolbar_ui_api::mojom::BackForwardControlStatePtr GetBackForwardState() const; uint64_t current_state_generation_ = 0; InitializationState initialization_state_ = @@ -146,12 +154,15 @@ WebUIReloadControl reload_control_; WebUISplitTabsControl split_tabs_control_; std::unique_ptr<WebUILocationBar> location_bar_; + WebUIBackForwardControl back_control_; + WebUIBackForwardControl forward_control_; raw_ptr<const base::TickClock> clock_; base::OnceClosure did_first_non_empty_paint_callback_; bool has_finished_first_non_empty_paint_ = false; uint32_t crash_count_ = 0; base::TimeTicks last_crash_time_; base::CallbackListSubscription touch_ui_subscription_; + int back_button_leading_margin_ = 0; base::WeakPtrFactory<WebUIToolbarWebView> weak_ptr_factory_{this}; };
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc index fed8324..8c8763b 100644 --- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc +++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
@@ -40,6 +40,7 @@ #include "chrome/test/base/ui_test_utils.h" #include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom.h" #include "components/prefs/pref_service.h" +#include "components/strings/grit/components_strings.h" #include "components/viz/common/frame_sinks/copy_output_result.h" #include "content/public/browser/javascript_dialog_manager.h" #include "content/public/browser/navigation_handle.h" @@ -78,6 +79,8 @@ constexpr char kSplitTabsSelector[] = "split-tabs-button-app"; constexpr char kReloadButtonSelector[] = "reload-button-app"; +constexpr char kBackSelector[] = "#back"; +constexpr char kForwardSelector[] = "#forward"; std::string GetButtonAppJS(const std::string& selector) { return base::StringPrintf( @@ -130,6 +133,21 @@ content::WaitForCopyableViewInWebContents(web_view->GetWebContents()); } +void PinForwardButton(Browser* browser, views::WebView* web_view) { + browser->profile()->GetPrefs()->SetBoolean(prefs::kShowForwardButton, true); + content::WaitForCopyableViewInWebContents(web_view->GetWebContents()); +} + +bool WaitForButtonEnabled(content::WebContents* web_contents, + const std::string& selector) { + return base::test::RunUntil([&]() { + return content::EvalJs(web_contents, + base::StrCat({GetButtonIconJS(selector), + "?.disabled === false"})) + .ExtractBool(); + }); +} + // Dispatches an event to the Split Tabs Button. // `event_class`: The JS event class (e.g. 'MouseEvent', 'PointerEvent'). // `type`: The event type string (e.g. 'click', 'contextmenu'). @@ -144,6 +162,24 @@ type.c_str(), options.c_str()); } +// Simulates a full physical click cycle (press + release) using PointerEvents. +// Required for back/forward buttons because they listen for 'pointerup' +// to trigger navigation, which a standard 'element.click()' does not fire. +std::string DispatchPointerClick(const std::string& selector, + const std::string& opts = "button: 0") { + const std::string el = GetButtonIconJS(selector); + return base::StringPrintf( + "%s.dispatchEvent(new PointerEvent('pointerdown', {%s}));" + "%s.dispatchEvent(new PointerEvent('pointerup', {%s}));", + el.c_str(), opts.c_str(), el.c_str(), opts.c_str()); +} + +bool ClickButton(content::WebContents* web_contents, + const std::string& selector) { + return content::ExecJs( + web_contents, base::StrCat({GetButtonIconJS(selector), "?.click();"})); +} + class NavigationCounter : public content::WebContentsObserver { public: explicit NavigationCounter(content::WebContents* web_contents) @@ -168,7 +204,7 @@ // All features for Webium Production should be included here. feature_list_.InitWithFeatures( {features::kInitialWebUI, features::kWebUIReloadButton, - features::kWebUISplitTabsButton, + features::kWebUISplitTabsButton, features::kWebUIBackForwardButton, features::kSkipIPCChannelPausingForNonGuests, features::kWebUIInProcessResourceLoadingV2, features::kInitialWebUISyncNavStartToCommit}, @@ -332,12 +368,41 @@ reload_node->GetData().GetStringAttribute( ax::mojom::StringAttribute::kDescription)); + // Verify appropriate accessibility properties for back button. + content::WaitForAccessibilityTreeToContainNodeWithName( + web_view->GetWebContents(), "Back"); + find_criteria.name = "Back"; + ui::AXPlatformNodeDelegate* back_node = + content::FindAccessibilityNode(web_view->GetWebContents(), find_criteria); + ASSERT_TRUE(back_node); + const ui::AXNodeData& back = back_node->GetData(); + EXPECT_EQ(ax::mojom::Role::kButton, back.role); + EXPECT_EQ("Back", back.GetStringAttribute(ax::mojom::StringAttribute::kName)); + EXPECT_EQ("Click to go back, hold to see history", + back.GetStringAttribute(ax::mojom::StringAttribute::kDescription)); + + // Verify appropriate accessibility properties for forward button. + content::WaitForAccessibilityTreeToContainNodeWithName( + web_view->GetWebContents(), "Forward"); + find_criteria.name = "Forward"; + ui::AXPlatformNodeDelegate* forward_node = + content::FindAccessibilityNode(web_view->GetWebContents(), find_criteria); + ASSERT_TRUE(forward_node); + const ui::AXNodeData& forward = forward_node->GetData(); + EXPECT_EQ(ax::mojom::Role::kButton, forward.role); + EXPECT_EQ("Forward", + forward.GetStringAttribute(ax::mojom::StringAttribute::kName)); + EXPECT_EQ( + "Click to go forward, hold to see history", + forward.GetStringAttribute(ax::mojom::StringAttribute::kDescription)); + // Verify that setting mode to kStop is reflected in HasPopup attribute. webui_toolbar_view->GetReloadControl()->ChangeMode(ReloadControl::Mode::kStop, true); content::WaitForAccessibilityTreeToChange(web_view->GetWebContents()); content::WaitForAccessibilityTreeToContainNodeWithName( web_view->GetWebContents(), "Reload"); + find_criteria.name = "Reload"; reload_node = content::FindAccessibilityNode(web_view->GetWebContents(), find_criteria); ASSERT_TRUE(reload_node); @@ -353,6 +418,7 @@ content::WaitForAccessibilityTreeToChange(web_view->GetWebContents()); content::WaitForAccessibilityTreeToContainNodeWithName( web_view->GetWebContents(), "Reload"); + find_criteria.name = "Reload"; reload_node = content::FindAccessibilityNode(web_view->GetWebContents(), find_criteria); ASSERT_TRUE(reload_node); @@ -462,6 +528,211 @@ })); } +IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewPixelBrowserTest, + BackForwardButtonsVisibility) { + content::ScopedAccessibilityModeOverride mode_override(ui::kAXModeComplete); + ui::TrackedElement* element = nullptr; + WebUIToolbarWebView* webui_toolbar_view = nullptr; + views::WebView* web_view = nullptr; + ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element, + &webui_toolbar_view, &web_view, + browser())); + + // Check initial state: Buttons should be visible but disabled. + EXPECT_TRUE(WaitForButtonVisible(web_view->GetWebContents(), kBackSelector)); + EXPECT_EQ("true", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kBackSelector), + "?.disabled ? 'true' : 'false'"}))); + + EXPECT_TRUE( + WaitForButtonVisible(web_view->GetWebContents(), kForwardSelector)); + EXPECT_EQ("true", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kForwardSelector), + "?.disabled ? 'true' : 'false'"}))); + + // Navigate to enable back button. + ASSERT_TRUE( + ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"))); + + content::WaitForAccessibilityTreeToContainNodeWithName( + web_view->GetWebContents(), "Back"); + + EXPECT_TRUE(WaitForButtonVisible(web_view->GetWebContents(), kBackSelector)); + EXPECT_EQ("false", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kBackSelector), + "?.disabled ? 'true' : 'false'"}))); + + // Forward button should be visible but disabled initially (no forward + // history). + EXPECT_TRUE( + WaitForButtonVisible(web_view->GetWebContents(), kForwardSelector)); + EXPECT_EQ("true", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kForwardSelector), + "?.disabled ? 'true' : 'false'"}))); + + // Go back to enable forward button. + chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB); + + content::WaitForAccessibilityTreeToContainNodeWithName( + web_view->GetWebContents(), "Forward"); + + // Check that forward is enabled and back is disabled. + EXPECT_TRUE( + WaitForButtonVisible(web_view->GetWebContents(), kForwardSelector)); + EXPECT_EQ("false", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kForwardSelector), + "?.disabled ? 'true' : 'false'"}))); + + EXPECT_TRUE(WaitForButtonVisible(web_view->GetWebContents(), kBackSelector)); + EXPECT_EQ("true", + content::EvalJs(web_view->GetWebContents(), + base::StrCat({GetButtonIconJS(kBackSelector), + "?.disabled ? 'true' : 'false'"}))); +} + +IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewPixelBrowserTest, + BackForwardButtonsNavigation) { + ui::TrackedElement* element = nullptr; + WebUIToolbarWebView* webui_toolbar_view = nullptr; + views::WebView* web_view = nullptr; + ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element, + &webui_toolbar_view, &web_view, + browser())); + PinForwardButton(browser(), web_view); + + // Create navigation history. + GURL url1("chrome://version/"); + GURL url2("chrome://flags/"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2)); + + // Wait for the back button to be enabled. + ASSERT_TRUE(WaitForButtonEnabled(web_view->GetWebContents(), kBackSelector)); + + // Click Back. + { + content::TestNavigationObserver nav_observer( + browser()->tab_strip_model()->GetActiveWebContents()); + EXPECT_TRUE(content::ExecJs(web_view->GetWebContents(), + DispatchPointerClick(kBackSelector))); + nav_observer.Wait(); + EXPECT_EQ(url1, browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetLastCommittedURL()); + } + + // Wait for the forward button to be enabled. + ASSERT_TRUE( + WaitForButtonEnabled(web_view->GetWebContents(), kForwardSelector)); + + // Click Forward. + { + content::TestNavigationObserver nav_observer( + browser()->tab_strip_model()->GetActiveWebContents()); + EXPECT_TRUE(content::ExecJs(web_view->GetWebContents(), + DispatchPointerClick(kForwardSelector))); + nav_observer.Wait(); + EXPECT_EQ(url2, browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetLastCommittedURL()); + } +} + +IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewPixelBrowserTest, + BackForwardButtonsModifierClick) { + ui::TrackedElement* element = nullptr; + WebUIToolbarWebView* webui_toolbar_view = nullptr; + views::WebView* web_view = nullptr; + ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element, + &webui_toolbar_view, &web_view, + browser())); + PinForwardButton(browser(), web_view); + + // Create navigation history. + GURL url1("chrome://version/"); + GURL url2("chrome://flags/"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1)); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2)); + + // Wait for the back button to be enabled. + ASSERT_TRUE(WaitForButtonEnabled(web_view->GetWebContents(), kBackSelector)); + + int initial_tab_count = browser()->tab_strip_model()->count(); + +#if BUILDFLAG(IS_MAC) + // Ctrl+Click Back button. + // On Mac, Ctrl+Click opens a context menu and does not navigate. + EXPECT_TRUE(content::ExecJs( + web_view->GetWebContents(), + DispatchPointerClick(kBackSelector, "button: 0, ctrlKey: true"))); + + // Verify no new tab was opened. + EXPECT_EQ(initial_tab_count, browser()->tab_strip_model()->count()); + // Verify we didn't navigate away. + EXPECT_EQ(url2, browser() + ->tab_strip_model() + ->GetActiveWebContents() + ->GetLastCommittedURL()); +#else + // Ctrl+Click Back button (New Tab). + content::TestNavigationObserver nav_observer(nullptr); + nav_observer.StartWatchingNewWebContents(); + + EXPECT_TRUE(content::ExecJs( + web_view->GetWebContents(), + DispatchPointerClick(kBackSelector, "button: 0, ctrlKey: true"))); + + nav_observer.Wait(); + + // Verify new tab was opened. + EXPECT_EQ(initial_tab_count + 1, browser()->tab_strip_model()->count()); + EXPECT_EQ(url1, nav_observer.last_navigation_url()); + + // Switch back to the first tab. + browser()->tab_strip_model()->ActivateTabAt(0); +#endif // BUILDFLAG(IS_MAC) + + // Navigate back to enable the forward button. + { + content::TestNavigationObserver back_nav_observer( + browser()->tab_strip_model()->GetActiveWebContents()); + chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB); + back_nav_observer.Wait(); + } + + // Wait for the forward button to be enabled. + ASSERT_TRUE( + WaitForButtonEnabled(web_view->GetWebContents(), kForwardSelector)); + + // Shift+Click Forward button (New Window). + ui_test_utils::BrowserCreatedObserver new_browser_observer; + + EXPECT_TRUE(content::ExecJs( + web_view->GetWebContents(), + DispatchPointerClick(kForwardSelector, "button: 0, shiftKey: true"))); + + Browser* new_browser = new_browser_observer.Wait(); + ASSERT_TRUE(new_browser); + + // Wait for the navigation in the new browser's active tab. + content::WebContents* new_tab = + new_browser->tab_strip_model()->GetActiveWebContents(); + content::TestNavigationObserver observer(new_tab); + if (new_tab->GetLastCommittedURL() != url2) { + observer.WaitForNavigationFinished(); + } + + // Verify navigation happened in a new window/web contents. + EXPECT_EQ(url2, new_tab->GetLastCommittedURL()); +} + class WebUIToolbarWebViewStabilityTest : public InProcessBrowserTest { public: WebUIToolbarWebViewStabilityTest() { @@ -1072,9 +1343,7 @@ auto* tab_strip_model = browser()->tab_strip_model(); EXPECT_FALSE(tab_strip_model->GetActiveTab()->IsSplit()); - EXPECT_TRUE( - content::ExecJs(web_view->GetWebContents(), - DispatchEventScript("MouseEvent", "click", "detail: 1"))); + EXPECT_TRUE(ClickButton(web_view->GetWebContents(), kSplitTabsSelector)); // Verify entered split view. This might take a moment, so need to wait. ASSERT_TRUE(base::test::RunUntil( @@ -1141,9 +1410,7 @@ [&]() { return tab_strip_model->GetActiveTab()->IsSplit(); })); // Click the button while in split mode. - EXPECT_TRUE( - content::ExecJs(web_view->GetWebContents(), - DispatchEventScript("MouseEvent", "click", "detail: 1"))); + EXPECT_TRUE(ClickButton(web_view->GetWebContents(), kSplitTabsSelector)); // Verify no crash. }
diff --git a/chrome/browser/ui/views/user_education/ios_promo_bubble_view.cc b/chrome/browser/ui/views/user_education/ios_promo_bubble_view.cc index bcc22b3..979f3a2 100644 --- a/chrome/browser/ui/views/user_education/ios_promo_bubble_view.cc +++ b/chrome/browser/ui/views/user_education/ios_promo_bubble_view.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ui/views/user_education/ios_promo_bubble_view.h" #include "base/functional/bind.h" +#include "chrome/browser/desktop_to_mobile_promos/promos_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/desktop_to_mobile_promos/ios_promo_trigger_service.h" @@ -208,6 +209,8 @@ set_highlight_button_when_shown(ShouldHighlightAnchorButton()); LogDesktopPromoBubbleCreated(promo_type_, promo_bubble_type_); + + promos_utils::IOSDesktopPromoShown(profile_, promo_type_); } IOSPromoBubbleView::~IOSPromoBubbleView() = default;
diff --git a/chrome/browser/ui/waap/initial_webui_browsertest.cc b/chrome/browser/ui/waap/initial_webui_browsertest.cc index 9c870ed..4361a32 100644 --- a/chrome/browser/ui/waap/initial_webui_browsertest.cc +++ b/chrome/browser/ui/waap/initial_webui_browsertest.cc
@@ -67,9 +67,16 @@ GetNavigationControlsStateFetcher() override { return std::make_unique<toolbar_ui_api::NavigationControlsStateFetcherImpl>( base::BindLambdaForTesting([]() { + auto back_forward_state = + toolbar_ui_api::mojom::BackForwardControlState::New(); + back_forward_state->back_button_state = + toolbar_ui_api::mojom::ButtonState::New(); + back_forward_state->forward_button_state = + toolbar_ui_api::mojom::ButtonState::New(); return toolbar_ui_api::mojom::NavigationControlsState::New( toolbar_ui_api::mojom::ReloadControlState::New(), toolbar_ui_api::mojom::SplitTabsControlState::New(), + std::move(back_forward_state), /*layout_constants_version=*/0); })); }
diff --git a/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc b/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc index 4ebd6a6..960f216 100644 --- a/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc +++ b/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc
@@ -1086,4 +1086,6 @@ EXPECT_TRUE(prompt_factory->is_visible()); Accept(manager); + + EXPECT_FALSE(prompt_factory->is_visible()); }
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler.cc index a63076f5..79f5314 100644 --- a/chrome/browser/ui/webui/app_home/app_home_page_handler.cc +++ b/chrome/browser/ui/webui/app_home/app_home_page_handler.cc
@@ -386,7 +386,9 @@ app_info->store_page_url = std::nullopt; app_info->may_uninstall = registrar.CanUserUninstallWebApp(app_id); app_info->app_type = - registrar.AppMatches(app_id, web_app::WebAppFilter::IsIsolatedApp()) + registrar.AppMatches(app_id, + web_app::WebAppFilter::IsIsolatedApp() | + web_app::WebAppFilter::IsIsolatedSubApp()) ? app_home::mojom::AppType::kIsolatedWebApp : app_home::mojom::AppType::kWebApp; return app_info;
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc index fc289f1..978a773 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc +++ b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc
@@ -149,11 +149,10 @@ for (const auto& tab : tabs) { title_counts[tab->title]++; } - int duplicate_count = - std::count_if(title_counts.begin(), title_counts.end(), - [](const std::pair<const std::string, int>& pair) { - return pair.second > 1; - }); + int duplicate_count = std::ranges::count_if( + title_counts, [](const std::pair<const std::string, int>& pair) { + return pair.second > 1; + }); // Sort the tabs by last active time, and truncate to the maximum number of // tabs to return. @@ -336,22 +335,21 @@ contextual_search::ContextualSearchSessionHandle* ContextualSearchboxHandler::GetContextualSessionHandle() { if (!get_session_callback_) { - context_controller_observation_.Reset(); return nullptr; } auto* session_handle = get_session_callback_.Run(); auto* context_controller = session_handle ? session_handle->GetController() : nullptr; - - if (context_controller) { - if (!context_controller_observation_.IsObservingSource( - context_controller)) { - context_controller_observation_.Reset(); - context_controller_observation_.Observe(context_controller); - } - } else { - context_controller_observation_.Reset(); + // Remove the old context controller if it's different from the new one. + if (context_controller_ && context_controller_.get() != context_controller) { + context_controller_->RemoveObserver(this); + context_controller_ = nullptr; + } + // Reset to the new context controller if it is different. + if (context_controller && !context_controller_) { + context_controller->AddObserver(this); + context_controller_ = context_controller->AsWeakPtr(); } return session_handle; } @@ -362,6 +360,9 @@ if (browser_window_interface) { browser_window_interface->GetTabStripModel()->RemoveObserver(this); } + if (context_controller_) { + context_controller_->RemoveObserver(this); + } } void ContextualSearchboxHandler::ResetInputStateModel() {
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h index 3f1c4c8..a5bcdc6 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h +++ b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h
@@ -184,13 +184,6 @@ OnInputStateChanged(state); } - base::ScopedObservation<contextual_search::ContextualSearchContextController, - contextual_search::ContextualSearchContextController:: - FileUploadStatusObserver>& - context_controller_observation_for_testing() { - return context_controller_observation_; - } - protected: // SearchboxHandler: omnibox::InputState GetInputState() const override; @@ -281,10 +274,8 @@ // The context controller this searchbox is listening to for file upload // status updates. - base::ScopedObservation<contextual_search::ContextualSearchContextController, - contextual_search::ContextualSearchContextController:: - FileUploadStatusObserver> - context_controller_observation_{this}; + base::WeakPtr<contextual_search::ContextualSearchContextController> + context_controller_; std::optional<lens::ContextualInputData> context_input_data_;
diff --git a/chrome/browser/ui/webui/new_tab_footer/footer_context_menu_browsertest.cc b/chrome/browser/ui/webui/new_tab_footer/footer_context_menu_browsertest.cc index a7936ec..5e36e0d 100644 --- a/chrome/browser/ui/webui/new_tab_footer/footer_context_menu_browsertest.cc +++ b/chrome/browser/ui/webui/new_tab_footer/footer_context_menu_browsertest.cc
@@ -107,12 +107,6 @@ #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) class FooterContextMenuEnterpriseTest : public FooterContextMenuBrowserTest { public: - void SetUp() override { - feature_list_.InitAndEnableFeature( - features::kEnterpriseBadgingForNtpFooter); - FooterContextMenuBrowserTest::SetUp(); - } - void SetUpOnMainThread() override { // Simulate browser management. scoped_browser_management_ = @@ -130,7 +124,6 @@ private: std::unique_ptr<policy::ScopedManagementServiceOverrideForTesting> scoped_browser_management_; - base::test::ScopedFeatureList feature_list_; }; IN_PROC_BROWSER_TEST_F(FooterContextMenuEnterpriseTest,
diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_unittest.cc b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_unittest.cc index fb0c6ca..4917e6c5 100644 --- a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_unittest.cc +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_unittest.cc
@@ -287,8 +287,6 @@ class NewTabFooterHandlerEnterpriseTest : public testing::Test { public: void SetUp() override { - feature_list_.InitAndEnableFeature( - features::kEnterpriseBadgingForNtpFooter); profile_manager_ = std::make_unique<TestingProfileManager>( TestingBrowserProcess::GetGlobal()); ASSERT_TRUE(profile_manager_->SetUp());
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_browsertest.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_browsertest.cc index cf7c7640..c236823 100644 --- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_browsertest.cc +++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_browsertest.cc
@@ -422,8 +422,7 @@ public: NewTabPageHandlerManagedTest() { feature_list_.InitWithFeatures( - /*enabled_features=*/{ntp_features::kNtpFooter, - features::kEnterpriseBadgingForNtpFooter}, + /*enabled_features=*/{ntp_features::kNtpFooter}, /*disabled_features=*/{}); }
diff --git a/chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.cc b/chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.cc index 225536d..2170826 100644 --- a/chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.cc +++ b/chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.cc
@@ -296,10 +296,6 @@ omnibox::CreateQueryControllerConfigParams(), contextual_search::ContextualSearchSource::kOmnibox, lens::LensOverlayInvocationSource::kOmniboxContextualQuery); - // TODO(crbug.com/469875271): Determine what to do with the return value - // of this call, or move this call to a different location. - shared_session_handle_->CheckSearchContentSharingSettings( - profile_->GetPrefs()); } } return shared_session_handle_.get();
diff --git a/chrome/browser/ui/webui/searchbox/contextual_searchbox_handler_unittest.cc b/chrome/browser/ui/webui/searchbox/contextual_searchbox_handler_unittest.cc index fe5c1c80..3bab9a6f 100644 --- a/chrome/browser/ui/webui/searchbox/contextual_searchbox_handler_unittest.cc +++ b/chrome/browser/ui/webui/searchbox/contextual_searchbox_handler_unittest.cc
@@ -110,11 +110,6 @@ bool shift_key) override {} void OnThumbnailRemoved() override {} - contextual_search::ContextualSearchSessionHandle* - GetContextualSessionHandle() { - return ContextualSearchboxHandler::GetContextualSessionHandle(); - } - contextual_search::ContextualSearchMetricsRecorder* GetMetricsRecorder() { return ContextualSearchboxHandler::GetMetricsRecorder(); } @@ -221,21 +216,6 @@ ContextualSearchboxHandlerTestHarness::TearDown(); } - void SetSessionHandle( - std::unique_ptr<contextual_search::ContextualSearchSessionHandle> - session_handle) { - contextual_session_handle_ = std::move(session_handle); - } - - std::unique_ptr<contextual_search::ContextualSearchSessionHandle> - TakeSessionHandle() { - return std::move(contextual_session_handle_); - } - - void ClearQueryController() { query_controller_ = nullptr; } - - contextual_search::ContextualSearchService* service() { return service_; } - protected: testing::NiceMock<MockSearchboxPage> mock_searchbox_page_; std::unique_ptr<FakeContextualSearchboxHandler> handler_; @@ -716,55 +696,6 @@ "ContextualSearch.Models.NewTabPage", composebox_query::mojom::ModelMode::kGeminiRegular, 1); } - -// Regression test for crbug.com/487773783. -TEST_F(ContextualSearchboxHandlerTest, ManageObservationOfContextController) { - EXPECT_TRUE( - handler().context_controller_observation_for_testing().IsObserving()); - EXPECT_TRUE( - handler().context_controller_observation_for_testing().IsObservingSource( - &query_controller())); - - auto old_session_handle = TakeSessionHandle(); - auto* old_query_controller = - static_cast<MockQueryController*>(old_session_handle->GetController()); - - ClearQueryController(); - - auto query_controller_config_params = std::make_unique< - contextual_search::ContextualSearchContextController::ConfigParams>(); - query_controller_config_params->send_lns_surface = false; - query_controller_config_params->enable_viewport_images = true; - auto new_query_controller_ptr = std::make_unique<MockQueryController>( - /*identity_manager=*/nullptr, url_loader_factory(), - version_info::Channel::UNKNOWN, "en-US", template_url_service(), - fake_variations_client(), std::move(query_controller_config_params)); - auto* new_query_controller = new_query_controller_ptr.get(); - auto new_metrics_recorder_ptr = - std::make_unique<MockContextualSearchMetricsRecorder>(); - - SetSessionHandle( - service()->CreateSessionForTesting(std::move(new_query_controller_ptr), - std::move(new_metrics_recorder_ptr))); - - handler().GetContextualSessionHandle(); - - // Verify observation is moved to the new controller. - EXPECT_TRUE( - handler().context_controller_observation_for_testing().IsObservingSource( - new_query_controller)); - EXPECT_FALSE( - handler().context_controller_observation_for_testing().IsObservingSource( - old_query_controller)); - - auto last_session_handle = TakeSessionHandle(); - handler().GetContextualSessionHandle(); - - // Verify observation is stopped. - EXPECT_FALSE( - handler().context_controller_observation_for_testing().IsObserving()); -} - TEST_F(ContextualSearchboxHandlerTest, SubmitQueryWithAdditionalParams) { // Ensure udm param is always set as an additional param. SubmitQueryAndWaitForNavigation();
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc index 596fbaf..203a27bf 100644 --- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc +++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc
@@ -289,9 +289,7 @@ void SetUp() override { scoped_feature_list_.InitWithFeatures( - /*enabled_features=*/{ntp_features::kNtpFooter, - features::kEnterpriseBadgingForNtpFooter}, - {}); + /*enabled_features=*/{ntp_features::kNtpFooter}, {}); InteractiveBrowserTest::SetUp(); }
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn b/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn index 181b942f..47cda91f9 100644 --- a/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn +++ b/chrome/browser/ui/webui/webui_toolbar/adapters/BUILD.gn
@@ -27,6 +27,7 @@ ":adapters", "//chrome/app:command_ids", "//chrome/browser:primitives", + "//chrome/browser/preloading", "//chrome/browser/ui/interaction", "//chrome/browser/ui/tabs", "//chrome/browser/ui/webui/webui_toolbar/utils",
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h index dd99820a..4211ca2 100644 --- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h +++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter.h
@@ -20,6 +20,9 @@ virtual void Reload(bool bypass_cache, WindowOpenDisposition disposition) = 0; virtual void Stop() = 0; + virtual void Back(WindowOpenDisposition disposition) = 0; + virtual void Forward(WindowOpenDisposition disposition) = 0; + virtual void BackButtonHovered() = 0; virtual void CreateNewSplitTab() = 0; // These should probably be pulled to their own adapter. virtual webui_toolbar::TabSplitStatus ComputeSplitTabStatus() = 0;
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc index bbc611a..18858ea8 100644 --- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc +++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.cc
@@ -7,10 +7,13 @@ #include "base/check_deref.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/command_updater.h" +#include "chrome/browser/preloading/chrome_preloading.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/tabs/split_tab_metrics.h" #include "chrome/browser/ui/webui/webui_toolbar/utils/split_tabs_utils.h" +#include "components/tabs/public/tab_interface.h" +#include "content/public/browser/web_contents.h" namespace browser_controls_api { @@ -33,6 +36,20 @@ IDC_STOP, WindowOpenDisposition::CURRENT_TAB); } +void BrowserControlsAdapterImpl::Back(WindowOpenDisposition disposition) { + command_updater_->ExecuteCommandWithDisposition(IDC_BACK, disposition); +} + +void BrowserControlsAdapterImpl::Forward(WindowOpenDisposition disposition) { + command_updater_->ExecuteCommandWithDisposition(IDC_FORWARD, disposition); +} + +void BrowserControlsAdapterImpl::BackButtonHovered() { + browser_.get().GetActiveTabInterface()->GetContents()->BackNavigationLikely( + chrome_preloading_predictor::kBackButtonHover, + WindowOpenDisposition::CURRENT_TAB); +} + void BrowserControlsAdapterImpl::CreateNewSplitTab() { chrome::NewSplitTab(&browser_.get(), split_tabs::SplitTabCreatedSource::kToolbarButton);
diff --git a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h index 6e52975..9709c70 100644 --- a/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h +++ b/chrome/browser/ui/webui/webui_toolbar/adapters/browser_controls_adapter_impl.h
@@ -26,6 +26,9 @@ // BrowserControlsAdapter: void Reload(bool bypass_cache, WindowOpenDisposition disposition) override; void Stop() override; + void Back(WindowOpenDisposition disposition) override; + void Forward(WindowOpenDisposition disposition) override; + void BackButtonHovered() override; void CreateNewSplitTab() override; webui_toolbar::TabSplitStatus ComputeSplitTabStatus() override; bool IsButtonPinned(toolbar_ui_api::mojom::ToolbarButtonType type) override;
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc index c955014..aaf4493 100644 --- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc +++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.cc
@@ -46,6 +46,14 @@ event_flags |= ui::EF_COMMAND_DOWN; break; } + case ClickDispositionFlag::kShiftKeyDown: { + event_flags |= ui::EF_SHIFT_DOWN; + break; + } + case ClickDispositionFlag::kControlKeyDown: { + event_flags |= ui::EF_CONTROL_DOWN; + break; + } case ClickDispositionFlag::kUnspecified: NOTREACHED() << "Unexpected ClickDispositionFlag::kUnspecified."; } @@ -116,6 +124,23 @@ // TODO(crbug.com/448794588): Handle KeyPress events. } +void BrowserControlsService::Back( + const std::vector<browser_controls_api::mojom::ClickDispositionFlag>& + flags) { + browser_adapter_->Back(ui::DispositionFromEventFlags(ToUIEventFlags(flags))); +} + +void BrowserControlsService::Forward( + const std::vector<browser_controls_api::mojom::ClickDispositionFlag>& + flags) { + browser_adapter_->Forward( + ui::DispositionFromEventFlags(ToUIEventFlags(flags))); +} + +void BrowserControlsService::BackButtonHovered() { + browser_adapter_->BackButtonHovered(); +} + void BrowserControlsService::SplitActiveTab() { // We only reach here if the frontend decided we need to CREATE a split. // We don't need to check IsActiveTabInSplit() or handle the menu here.
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h index 380c1b14..f3c14ac 100644 --- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h +++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service.h
@@ -45,6 +45,9 @@ bool bypass_cache, const std::vector<mojom::ClickDispositionFlag>& click_flags) override; void StopLoad() override; + void Back(const std::vector<mojom::ClickDispositionFlag>& flags) override; + void Forward(const std::vector<mojom::ClickDispositionFlag>& flags) override; + void BackButtonHovered() override; void SplitActiveTab() override; private:
diff --git a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc index d0e668e..57c9ef112 100644 --- a/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc +++ b/chrome/browser/ui/webui/webui_toolbar/browser_controls_service_unittest.cc
@@ -162,6 +162,101 @@ duration, 1); } +// Tests that calling Back() with CURRENT_TAB executes the IDC_BACK command +// with CURRENT_TAB. +TEST_F(BrowserControlsServiceTest, Back_CurrentTab) { + service().Back({}); + EXPECT_EQ(IDC_BACK, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::CURRENT_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Back() with kMiddleMouseButton executes the IDC_BACK +// command with NEW_BACKGROUND_TAB. +TEST_F(BrowserControlsServiceTest, Back_MiddleClick) { + service().Back( + {browser_controls_api::mojom::ClickDispositionFlag::kMiddleMouseButton}); + EXPECT_EQ(IDC_BACK, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_BACKGROUND_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Back() with the platform's background tab modifier +// executes the IDC_BACK command with NEW_BACKGROUND_TAB. On macOS, Ctrl+Click +// opens a context menu, so we test Meta+Click instead. +TEST_F(BrowserControlsServiceTest, Back_MetaOrCtrlClick) { + service().Back( +#if BUILDFLAG(IS_MAC) + {browser_controls_api::mojom::ClickDispositionFlag::kMetaKeyDown}); +#else + {browser_controls_api::mojom::ClickDispositionFlag::kControlKeyDown}); +#endif // BUILDFLAG(IS_MAC) + EXPECT_EQ(IDC_BACK, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_BACKGROUND_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Back() with kShiftKeyDown executes the IDC_BACK command +// with NEW_WINDOW. +TEST_F(BrowserControlsServiceTest, Back_ShiftClick) { + service().Back( + {browser_controls_api::mojom::ClickDispositionFlag::kShiftKeyDown}); + EXPECT_EQ(IDC_BACK, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_WINDOW, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Forward() by default executes the IDC_FORWARD +// command with CURRENT_TAB. +TEST_F(BrowserControlsServiceTest, Forward_CurrentTab) { + service().Forward({}); + EXPECT_EQ(IDC_FORWARD, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::CURRENT_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Forward() with kMiddleMouseButton executes the IDC_FORWARD +// command with NEW_BACKGROUND_TAB. +TEST_F(BrowserControlsServiceTest, Forward_MiddleClick) { + service().Forward( + {browser_controls_api::mojom::ClickDispositionFlag::kMiddleMouseButton}); + EXPECT_EQ(IDC_FORWARD, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_BACKGROUND_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Forward() with the platform's background tab modifier +// executes the IDC_FORWARD command with NEW_BACKGROUND_TAB. On macOS, +// Ctrl+Click opens a context menu, so we test Meta+Click instead. +TEST_F(BrowserControlsServiceTest, Forward_MetaOrCtrlClick) { + service().Forward( +#if BUILDFLAG(IS_MAC) + {browser_controls_api::mojom::ClickDispositionFlag::kMetaKeyDown}); +#else + {browser_controls_api::mojom::ClickDispositionFlag::kControlKeyDown}); +#endif // BUILDFLAG(IS_MAC) + EXPECT_EQ(IDC_FORWARD, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_BACKGROUND_TAB, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling Forward() with kShiftKeyDown executes the IDC_FORWARD +// command with NEW_WINDOW. +TEST_F(BrowserControlsServiceTest, Forward_ShiftClick) { + service().Forward( + {browser_controls_api::mojom::ClickDispositionFlag::kShiftKeyDown}); + EXPECT_EQ(IDC_FORWARD, toy_browser().received_commands().back().command_id); + EXPECT_EQ(WindowOpenDisposition::NEW_WINDOW, + toy_browser().received_commands().back().disposition); +} + +// Tests that calling BackButtonHovered() +TEST_F(BrowserControlsServiceTest, BackButtonHovered) { + EXPECT_FALSE(toy_browser().is_back_button_hovered()); + service().BackButtonHovered(); + EXPECT_TRUE(toy_browser().is_back_button_hovered()); +} + } // namespace } // namespace browser_controls_api
diff --git a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc index d590672..b0b0c28 100644 --- a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc +++ b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.cc
@@ -27,6 +27,24 @@ .disposition = WindowOpenDisposition::CURRENT_TAB}); } + void Back(WindowOpenDisposition disposition) override { + toy_browser_->received_commands_.push_back({ + .command_id = IDC_BACK, + .disposition = disposition, + }); + } + + void Forward(WindowOpenDisposition disposition) override { + toy_browser_->received_commands_.push_back({ + .command_id = IDC_FORWARD, + .disposition = disposition, + }); + } + + void BackButtonHovered() override { + toy_browser_->back_button_hovered_ = true; + } + void CreateNewSplitTab() override { toy_browser_->is_split_tab_ = true; } webui_toolbar::TabSplitStatus ComputeSplitTabStatus() override {
diff --git a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h index 7b7c5eb..4663582 100644 --- a/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h +++ b/chrome/browser/ui/webui/webui_toolbar/testing/toy_browser.h
@@ -48,6 +48,7 @@ bool IsButtonPinned(toolbar_ui_api::mojom::ToolbarButtonType type) const; bool is_split_tab() const { return is_split_tab_; } + bool is_back_button_hovered() const { return back_button_hovered_; } private: friend class ToyBrowserControlsAdapter; @@ -56,6 +57,7 @@ // True when split tab is created. This state currently sticks, with no way // to unset it. bool is_split_tab_ = false; + bool back_button_hovered_ = false; }; } // namespace testing
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc index bed7c48b..56afe47 100644 --- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc +++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_test_utils.cc
@@ -31,9 +31,16 @@ toolbar_ui_api::mojom::NavigationControlsStatePtr CreateValidNavigationControlsState() { + auto back_forward_state = + toolbar_ui_api::mojom::BackForwardControlState::New(); + back_forward_state->back_button_state = + toolbar_ui_api::mojom::ButtonState::New(); + back_forward_state->forward_button_state = + toolbar_ui_api::mojom::ButtonState::New(); return toolbar_ui_api::mojom::NavigationControlsState::New( toolbar_ui_api::mojom::ReloadControlState::New(), toolbar_ui_api::mojom::SplitTabsControlState::New(), + std::move(back_forward_state), /*layout_constants_version=*/0); }
diff --git a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc index b42a443..9146ccc7 100644 --- a/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc +++ b/chrome/browser/ui/webui/webui_toolbar/webui_toolbar_ui.cc
@@ -38,6 +38,7 @@ #include "chrome/grit/webui_toolbar_resources_map.h" #include "components/browser_apis/browser_controls/browser_controls_api.mojom.h" #include "components/browser_apis/ui_controllers/toolbar/toolbar_ui_api.mojom.h" +#include "components/strings/grit/components_strings.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" @@ -60,6 +61,10 @@ static constexpr webui::LocalizedString kStrings[] = { // go/keep-sorted start + {"backButtonAccName", IDS_ACCNAME_BACK}, + {"backButtonTooltip", IDS_TOOLTIP_BACK}, + {"forwardButtonAccName", IDS_ACCNAME_FORWARD}, + {"forwardButtonTooltip", IDS_TOOLTIP_FORWARD}, {"reloadButtonAccNameReload", IDS_ACCNAME_RELOAD}, {"reloadButtonTooltipReload", IDS_TOOLTIP_RELOAD}, {"reloadButtonTooltipReloadWithMenu", IDS_TOOLTIP_RELOAD_WITH_MENU}, @@ -83,6 +88,8 @@ features::IsWebUIReloadButtonEnabled()); source->AddBoolean("enableLocationBar", features::IsWebUILocationBarEnabled()); + source->AddBoolean("enableBackForwardButtons", + features::IsWebUIBackForwardButtonEnabled()); BrowserWindowInterface* browser = webui::GetBrowserWindowInterface(web_ui->GetWebContents());
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt index e15fdf27..dcd5f796 100644 --- a/chrome/build/android-arm32.pgo.txt +++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@ -chrome-android32-main-1772647180-39b9b13f8fa4112f0e9c5c4fc1d4c69c0b7f2f24-54ba0ade409f536e2de108c74682020e214ec33b.profdata +chrome-android32-main-1772668655-3fa457bc2db063ea7b2955a4b71ed4b9c4b4cc68-f810f70c5a019f3ddd24095a44a8e915352b92a0.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 638fda1..bb4f338 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1772654355-b98f440ffc78314ae4c21e4c7a9dbca177c3c99c-a76f94b19aa6c4a42bfafe8726c4cb78b2b0d270.profdata +chrome-mac-arm-main-1772668655-e0d316a2757974fc006c670979776e1a4a7b6343-f810f70c5a019f3ddd24095a44a8e915352b92a0.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt index e97fdad..4d29182 100644 --- a/chrome/build/win-arm64.pgo.txt +++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@ -chrome-win-arm64-main-1772625572-c2a2cf80ac3802bc7d79f9eab998f3f86e3a8138-764bd33e0f1969c1739045f90d7fe7181581d833.profdata +chrome-win-arm64-main-1772647180-4180446e45c5630bebe52973a4a76fcd867776e2-54ba0ade409f536e2de108c74682020e214ec33b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 1749a7e..1b62f00 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1772647180-8ff0f8ab38543221aea8df215520376d7277fd48-54ba0ade409f536e2de108c74682020e214ec33b.profdata +chrome-win32-main-1772657967-d2dc98eceac66961b838bfde0e9b686a3192460b-2608e572070af2842dab47e1461677bf3c846828.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 3a94a3d..f6ded76c 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1772647180-558ab67597296b5c7bd648f84afcce601662e086-54ba0ade409f536e2de108c74682020e214ec33b.profdata +chrome-win64-main-1772657967-c96c51a0a3c11a5e7d925f670e3bcedb6025a366-2608e572070af2842dab47e1461677bf3c846828.profdata
diff --git a/chrome/common/actor.mojom b/chrome/common/actor.mojom index b532b48..c1daa26 100644 --- a/chrome/common/actor.mojom +++ b/chrome/common/actor.mojom
@@ -540,12 +540,6 @@ // The Chrome Actor UI could not be shown. kActorUiError = 1300, - /////////////////////////////////////////////////////////////////////// - // Codes 1400-1499: Errors for LoadAndExtractContentTool. - - // Content extraction failed for a URL. - kLoadAndExtractContentExtractionFailed = 1400, - // Please see the comment above about adding new values. };
diff --git a/chrome/services/file_util/single_file_tar_reader.cc b/chrome/services/file_util/single_file_tar_reader.cc index 79ca493..ac352cd5 100644 --- a/chrome/services/file_util/single_file_tar_reader.cc +++ b/chrome/services/file_util/single_file_tar_reader.cc
@@ -83,8 +83,19 @@ for (size_t i = 0; i < length; ++i) { const char as_char = static_cast<char>(buffer[i]); - if (as_char == '\0') + // POSIX requires numeric fields to be terminated with either a space or a + // NUL. Stop reading when either is encountered. + // See "POSIX ustar Archives" section at: + // https://man.freebsd.org/cgi/man.cgi?query=tar&sektion=5 + if (as_char == '\0' || as_char == ' ') { break; + } + // Ensure the character is a valid octal digit. If it is not, parsing would + // result in an invalid number, or potentially a negative value underflow + // if the character is not ASCII. + if (as_char < '0' || as_char > '7') { + return std::nullopt; + } num *= 8; num += as_char - '0'; }
diff --git a/chrome/services/file_util/single_file_tar_reader_unittest.cc b/chrome/services/file_util/single_file_tar_reader_unittest.cc index ec53915..2e32146 100644 --- a/chrome/services/file_util/single_file_tar_reader_unittest.cc +++ b/chrome/services/file_util/single_file_tar_reader_unittest.cc
@@ -64,6 +64,11 @@ 0x30, 0x30, 0x31, 0x32, 0x33, 0x00}; EXPECT_EQ(83u, SingleFileTarReader::ReadOctalNumber(kNumber)); + // Test space termination. + const std::vector<uint8_t> kSpaceTerminated{ + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x32, 0x33, 0x20}; + EXPECT_EQ(83u, SingleFileTarReader::ReadOctalNumber(kSpaceTerminated)); + // Test the case of big-endian integer with padding. // 20bc13a00(16) = 8787147264(10) const std::vector<uint8_t> kBigNumber{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -72,7 +77,16 @@ // Test if ReadOctalNumber return null when the input is shorter than 8 bytes. const std::vector<uint8_t> kShortNumber{0x30, 0x30, 0x30, 0x30, 0x30, 0x30}; - EXPECT_TRUE(!SingleFileTarReader::ReadOctalNumber(kShortNumber).has_value()); + EXPECT_FALSE(SingleFileTarReader::ReadOctalNumber(kShortNumber).has_value()); + + // Test invalid character returning `std::nullopt`. + // This explicitly prevents an underflow issue where non-ASCII characters + // (like 0xFF) would be cast to a negative char, causing an underflow into + // a massive `uint64_t` value when subtracting '0'. + const std::vector<uint8_t> kInvalidNumber{0xFF, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x31, 0x32, 0x33, 0x00}; + EXPECT_FALSE( + SingleFileTarReader::ReadOctalNumber(kInvalidNumber).has_value()); } TEST_F(SingleFileTarReaderTest, EmptyFile) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 8da9e13a..dfa8388 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -2613,6 +2613,7 @@ "//chrome/browser/headless:browser_tests", "//chrome/browser/heavy_ad_intervention:browser_tests", "//chrome/browser/history", + "//chrome/browser/history_embeddings:browser_tests", "//chrome/browser/icon_transcoder:browser_tests", "//chrome/browser/image_decoder:browser_tests", "//chrome/browser/image_fetcher", @@ -3535,13 +3536,11 @@ "../browser/history/history_browsertest.cc", "../browser/history/redirect_browsertest.cc", "../browser/history_clusters/history_clusters_metrics_browsertest.cc", - "../browser/history_embeddings/history_embeddings_service_browsertest.cc", "../browser/icon_loader_browsertest.cc", "../browser/idle/idle_browsertest.cc", "../browser/iframe_browsertest.cc", "../browser/image_fetcher/image_fetcher_impl_browsertest.cc", "../browser/infobars/infobars_browsertest.cc", - "../browser/interest_group/interest_group_permissions_browsertest.cc", "../browser/invalidation/profile_invalidation_provider_factory_browsertest.cc", "../browser/l10n_util_browsertest.cc", "../browser/lifetime/browser_close_manager_browsertest.cc", @@ -10743,7 +10742,6 @@ "../../ui/views/controls/webview/webview_unittest.cc", "../browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc", "../browser/enterprise/data_protection/data_protection_page_user_data_unittest.cc", - "../browser/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc", "../browser/enterprise/watermark/watermark_style_policy_handler_unittest.cc", "../browser/enterprise/watermark/watermark_view_unittest.cc", "../browser/ui/views/apps/app_info_dialog/app_info_dialog_views_unittest.cc",
diff --git a/chrome/test/data/webui/contextual_tasks/composebox_submit_test.ts b/chrome/test/data/webui/contextual_tasks/composebox_submit_test.ts index 72c5cbb..8085069 100644 --- a/chrome/test/data/webui/contextual_tasks/composebox_submit_test.ts +++ b/chrome/test/data/webui/contextual_tasks/composebox_submit_test.ts
@@ -667,15 +667,18 @@ }; mockSearchboxPageHandler.setResultFor( ADD_TAB_CONTEXT_FN, Promise.resolve(FAKE_TOKEN_STRING)); - await composebox.addTabContext_({ - detail: { - id: 0, - title: 'test', - url: new URL('https://google.com'), - delayUpload: false, - onContextAdded: callback, - }, - } as CustomEvent); + + const contextEntrypoint = + composebox.shadowRoot.querySelector('#contextEntrypoint'); + assertTrue(!!contextEntrypoint); + contextEntrypoint.fire('add-tab-context', { + id: 0, + title: 'test', + url: new URL('https://google.com'), + delayUpload: false, + onContextAdded: callback, + }); + await microtasksFinished(); searchboxCallbackRouterRemote.onContextualInputStatusChanged( FAKE_TOKEN_STRING, @@ -820,15 +823,16 @@ mockSearchboxPageHandler.setResultFor( ADD_TAB_CONTEXT_FN, Promise.resolve(FAKE_TOKEN_STRING)); - await composebox.addTabContext_({ - detail: { - id: 0, - title: 'test', - url: new URL('https://google.com'), - delayUpload: true, - onContextAdded: callback, - }, - } as CustomEvent); + const contextEntrypoint = + composebox.shadowRoot.querySelector('#contextEntrypoint'); + assertTrue(!!contextEntrypoint); + contextEntrypoint.fire('add-tab-context', { + id: 0, + title: 'test', + url: new URL('https://google.com'), + delayUpload: true, + onContextAdded: callback, + }); await microtasksFinished(); await composebox.updateComplete; await composebox.updateComplete;
diff --git a/chrome/test/data/webui/contextual_tasks/composebox_test.ts b/chrome/test/data/webui/contextual_tasks/composebox_test.ts index 10f7ca8..9073a4b 100644 --- a/chrome/test/data/webui/contextual_tasks/composebox_test.ts +++ b/chrome/test/data/webui/contextual_tasks/composebox_test.ts
@@ -1446,4 +1446,73 @@ // File hint should take precedence over overlay hint. assertEquals('Ask about this image', innerComposebox.$.input.placeholder); }); + + // Test that the Tab key correctly synchronizes the selected index. + test('TabFocusSyncsSelectedIndex', async () => { + const contextualComposebox = contextualTasksApp.$.composebox; + const dropdown = + (contextualComposebox as any).$.contextualTasksSuggestionsContainer; + + // Simulate focus moving to the first match (index 0) via Tab key. + dropdown.dispatchEvent(new CustomEvent('match-focusin', { + detail: {index: 0}, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + + // Verify the index is synced in both the parent and the dropdown. + assertEquals(0, (contextualComposebox as any).selectedMatchIndex_); + assertEquals(0, dropdown.selectedMatchIndex); + }); + + test('TabFocusPopulatesTextAndEnterSubmits', async () => { + const contextualComposebox = contextualTasksApp.$.composebox; + const dropdown = + (contextualComposebox as any).$.contextualTasksSuggestionsContainer; + const innerComposebox = (contextualComposebox as any).$.composebox; + + // Setup mock zero-state results. + const matches = [ + createAutocompleteMatch( + {contents: 'focus match', destinationUrl: 'https://test.com'}), + ]; + (contextualComposebox as any).zeroStateSuggestions_ = + createAutocompleteResultForTesting({ + input: '', + matches: matches, + }); + + await microtasksFinished(); + await contextualComposebox.updateComplete; + + // Simulate Tab focus (match-focusin). + dropdown.dispatchEvent(new CustomEvent('match-focusin', { + detail: {index: 0}, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + await innerComposebox.updateComplete; + + // Simulate pressing Enter to submit. + dropdown.dispatchEvent(new KeyboardEvent('keydown', { + key: 'Enter', + bubbles: true, + composed: true, + })); + + // Verify the Mojo handler was called correctly. + const [index, url] = + await mockSearchboxPageHandler.whenCalled('openAutocompleteMatch'); + assertEquals(0, index); + assertEquals('https://test.com', url); + + // After submission, verify the input is cleared by your component logic. + await microtasksFinished(); + await innerComposebox.updateComplete; + assertEquals('', innerComposebox.getInputText()); + }); });
diff --git a/chrome/test/data/webui/contextual_tasks/test_utils.ts b/chrome/test/data/webui/contextual_tasks/test_utils.ts index 2ed9a70..6cbd59e1 100644 --- a/chrome/test/data/webui/contextual_tasks/test_utils.ts +++ b/chrome/test/data/webui/contextual_tasks/test_utils.ts
@@ -136,7 +136,7 @@ const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); - composebox.$.fileInputs.dispatchEvent(new CustomEvent('on-file-change', { + composebox.$.fileInputs.dispatchEvent(new CustomEvent('file-change', { detail: {files: dataTransfer.files}, bubbles: true, composed: true,
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_file_inputs_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_file_inputs_test.ts index 2f388b615..16a8093 100644 --- a/chrome/test/data/webui/new_tab_page/composebox/composebox_file_inputs_test.ts +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_file_inputs_test.ts
@@ -43,7 +43,7 @@ new File(['foo2'], 'foo2.pdf', {type: 'application/pdf'})); // Act. - const whenFileChange = eventToPromise('on-file-change', fileInputsElement); + const whenFileChange = eventToPromise('file-change', fileInputsElement); const mockFileChange = new Event('change', {bubbles: true}); Object.defineProperty(mockFileChange, 'target', { writable: false,
diff --git a/chrome/test/data/webui/updater/event_list/filter_bar_test.ts b/chrome/test/data/webui/updater/event_list/filter_bar_test.ts index 69ed07a..24b1cfe 100644 --- a/chrome/test/data/webui/updater/event_list/filter_bar_test.ts +++ b/chrome/test/data/webui/updater/event_list/filter_bar_test.ts
@@ -143,8 +143,8 @@ assertTrue(capturedEvent!.bubbles); assertTrue(capturedEvent!.composed); - const chip = filterBar.shadowRoot.querySelector('.chip')!; - const removeButton = chip.querySelector<HTMLElement>('cr-icon-button')!; + const removeButton = filterBar.shadowRoot.querySelector<HTMLElement>( + '.chip-wrapper cr-icon-button')!; removeButton.click(); await microtasksFinished(); assertEquals(0, filterBar.filterSettings.apps.size); @@ -274,8 +274,8 @@ endDate: null, }; await microtasksFinished(); - const chip = filterBar.shadowRoot.querySelector('.chip')!; - const removeButton = chip.querySelector<HTMLElement>('cr-icon-button')!; + const removeButton = filterBar.shadowRoot.querySelector<HTMLElement>( + '.chip-wrapper cr-icon-button')!; removeButton.click(); await microtasksFinished(); assertEquals(null, filterBar.filterSettings.startDate);
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java index 46dd081..0458a20 100644 --- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java +++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java
@@ -39,7 +39,7 @@ activity, layout, () -> { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( activity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(activity),
diff --git a/components/BUILD.gn b/components/BUILD.gn index 6543f809..264972b 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn
@@ -406,6 +406,7 @@ "//components/enterprise/common:unit_tests", "//components/enterprise/connectors/core:cloud_content_scanning_unittests", "//components/enterprise/connectors/core:unit_tests", + "//components/enterprise/data_protection:unit_tests", "//components/enterprise/obfuscation/core:unit_tests", ] }
diff --git a/components/affiliations/core/browser/BUILD.gn b/components/affiliations/core/browser/BUILD.gn index 90df14a3..3293582 100644 --- a/components/affiliations/core/browser/BUILD.gn +++ b/components/affiliations/core/browser/BUILD.gn
@@ -64,8 +64,8 @@ "//net", "//services/network/public/cpp", "//sql", - "//url", ] + public_deps = [ "//url" ] configs += [ "//build/config/compiler:wexit_time_destructors" ] }
diff --git a/components/autofill/core/browser/foundations/autofill_client.cc b/components/autofill/core/browser/foundations/autofill_client.cc index 080824215..a598189 100644 --- a/components/autofill/core/browser/foundations/autofill_client.cc +++ b/components/autofill/core/browser/foundations/autofill_client.cc
@@ -37,13 +37,15 @@ std::vector<Suggestion> suggestions, AutofillSuggestionTriggerSource trigger_source, int32_t form_control_ax_id, - PopupAnchorType anchor_type) + PopupAnchorType anchor_type, + bool show_tabbed_popup) : element_bounds(element_bounds), text_direction(text_direction), suggestions(std::move(suggestions)), trigger_source(trigger_source), form_control_ax_id(form_control_ax_id), - anchor_type(anchor_type) {} + anchor_type(anchor_type), + show_tabbed_popup(show_tabbed_popup) {} AutofillClient::PopupOpenArgs::PopupOpenArgs( const AutofillClient::PopupOpenArgs&) = default; AutofillClient::PopupOpenArgs::PopupOpenArgs(AutofillClient::PopupOpenArgs&&) =
diff --git a/components/autofill/core/browser/foundations/autofill_client.h b/components/autofill/core/browser/foundations/autofill_client.h index a174b73..77ab1ff 100644 --- a/components/autofill/core/browser/foundations/autofill_client.h +++ b/components/autofill/core/browser/foundations/autofill_client.h
@@ -215,7 +215,8 @@ std::vector<Suggestion> suggestions, AutofillSuggestionTriggerSource trigger_source, int32_t form_control_ax_id, - PopupAnchorType anchor_type); + PopupAnchorType anchor_type, + bool show_tabbed_popup = false); PopupOpenArgs(const PopupOpenArgs&); PopupOpenArgs(PopupOpenArgs&&); PopupOpenArgs& operator=(const PopupOpenArgs&); @@ -232,6 +233,7 @@ AutofillSuggestionTriggerSource::kUnspecified; int32_t form_control_ax_id = 0; PopupAnchorType anchor_type = PopupAnchorType::kField; + bool show_tabbed_popup; }; using EntityImportPromptResultCallback =
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate.cc b/components/autofill/core/browser/ui/autofill_external_delegate.cc index ac6cdbc..3b1891d 100644 --- a/components/autofill/core/browser/ui/autofill_external_delegate.cc +++ b/components/autofill/core/browser/ui/autofill_external_delegate.cc
@@ -61,6 +61,7 @@ #include "components/autofill/core/browser/metrics/suggestions_list_metrics.h" #include "components/autofill/core/browser/network/autofill_ai/wallet_pass_access_manager.h" #include "components/autofill/core/browser/payments/bnpl_manager.h" +#include "components/autofill/core/browser/payments/bnpl_util.h" #include "components/autofill/core/browser/payments/credit_card_access_manager.h" #include "components/autofill/core/browser/payments/iban_access_manager.h" #include "components/autofill/core/browser/payments/payments_autofill_client.h" @@ -415,12 +416,16 @@ #else PopupAnchorType::kField; #endif + + const bool show_tabbed_popup = ShouldShowPayNowPayLaterTabs(); + AutofillClient::PopupOpenArgs open_args( should_use_caret_bounds ? gfx::RectF(caret_bounds_) : query_field_.bounds(), query_field_.text_direction(), suggestions, trigger_source_, query_field_.form_control_ax_id(), - should_use_caret_bounds ? PopupAnchorType::kCaret : default_anchor_type); + should_use_caret_bounds ? PopupAnchorType::kCaret : default_anchor_type, + show_tabbed_popup); manager_->client().ShowAutofillSuggestions(open_args, GetWeakPtr()); } @@ -1472,4 +1477,16 @@ std::move(callback).Run(auth_succeeded); } +bool AutofillExternalDelegate::ShouldShowPayNowPayLaterTabs() { + if (GetQueriedField()) { + return GetMainFillingProduct() == FillingProduct::kCreditCard && + payments::ShouldShowBnplSuggestions( + manager_->client(), + GetQueriedField()->Type().GetCreditCardType()) && + base::FeatureList::IsEnabled( + features::kAutofillEnablePayNowPayLaterTabs); + } + return false; +} + } // namespace autofill
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate.h b/components/autofill/core/browser/ui/autofill_external_delegate.h index 568c37f..1db250f 100644 --- a/components/autofill/core/browser/ui/autofill_external_delegate.h +++ b/components/autofill/core/browser/ui/autofill_external_delegate.h
@@ -246,6 +246,9 @@ // Attempts to fill an Autofill AI `suggestion` into for `query_field_`; void FillAutofillAiFormAndHidePopup(const Suggestion& suggestion); + // Returns if the Pay Now Pay Later tabs should be shown. + virtual bool ShouldShowPayNowPayLaterTabs(); + base::WeakPtr<AutofillExternalDelegate> GetWeakPtr(); // If non-negative, OnSuggestionsReturned() passes one of the suggestions
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc index 1dd726d..6b81a384 100644 --- a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc +++ b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
@@ -842,6 +842,165 @@ payments_payload), {}); } + +// Tests that `show_tabbed_popup` is false when the main filling +// product is not a credit card (e.g., an Address field). +TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_NotCreditCard) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + /*enabled_features=*/{features::kAutofillEnableAmountExtraction, + features::kAutofillEnableBuyNowPayLaterSyncing, + features::kAutofillEnableBuyNowPayLater, + features::kAutofillEnableAiBasedAmountExtraction, + features::kAutofillEnablePayNowPayLaterTabs}, + /*disabled_features=*/{}); + + TestPaymentsDataManager& paydm = + static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager()); + paydm.AddCreditCard(test::GetCreditCard()); + paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer()); + + ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>( + autofill_client().GetAutofillOptimizationGuideDecider()), + IsUrlEligibleForBnplIssuer) + .WillByDefault(Return(true)); + + test::FormDescription form_description = { + .fields = {{.role = NAME_FIRST, .heuristic_type = NAME_FIRST}}}; + IssueOnQuery(form_description); + + EXPECT_CALL(autofill_client(), + ShowAutofillSuggestions( + AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre( + SuggestionType::kAddressEntry)), + Field("show_tabbed_popup", + &AutofillClient::PopupOpenArgs::show_tabbed_popup, + false)), + _)); + + OnSuggestionsReturned(queried_field().global_id(), + {Suggestion(SuggestionType::kAddressEntry)}); +} + +// Tests that `show_tabbed_popup` is true when the main filling +// product is a credit card and the field is a credit card number field. +TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_EligibleFieldType) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + /*enabled_features=*/{features::kAutofillEnableAmountExtraction, + features::kAutofillEnableBuyNowPayLaterSyncing, + features::kAutofillEnableBuyNowPayLater, + features::kAutofillEnableAiBasedAmountExtraction, + features::kAutofillEnablePayNowPayLaterTabs}, + /*disabled_features=*/{}); + + TestPaymentsDataManager& paydm = + static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager()); + paydm.AddCreditCard(test::GetCreditCard()); + paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer()); + + ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>( + autofill_client().GetAutofillOptimizationGuideDecider()), + IsUrlEligibleForBnplIssuer) + .WillByDefault(Return(true)); + + test::FormDescription form_description = { + .fields = { + {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}}; + IssueOnQuery(form_description); + + EXPECT_CALL( + autofill_client(), + ShowAutofillSuggestions( + AllOf(PopupOpenArgsAre( + SuggestionVectorIdsAre(SuggestionType::kCreditCardEntry)), + Field("show_tabbed_popup", + &AutofillClient::PopupOpenArgs::show_tabbed_popup, true)), + _)); + + OnSuggestionsReturned(queried_field().global_id(), + {Suggestion(SuggestionType::kCreditCardEntry)}); +} + +// Tests that `show_tabbed_popup` is false when the main filling +// product is a credit card but the heuristic type of the field doesn't +// trigger BNPL suggestions (e.g., CVC fields). +TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_IneligibleFieldType_Cvc) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + /*enabled_features=*/{features::kAutofillEnableAmountExtraction, + features::kAutofillEnableBuyNowPayLaterSyncing, + features::kAutofillEnableBuyNowPayLater, + features::kAutofillEnableAiBasedAmountExtraction, + features::kAutofillEnablePayNowPayLaterTabs}, + /*disabled_features=*/{}); + + TestPaymentsDataManager& paydm = + static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager()); + paydm.AddCreditCard(test::GetCreditCard()); + paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer()); + + ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>( + autofill_client().GetAutofillOptimizationGuideDecider()), + IsUrlEligibleForBnplIssuer) + .WillByDefault(Return(true)); + + test::FormDescription form_description = { + .fields = {{.role = CREDIT_CARD_VERIFICATION_CODE, + .heuristic_type = CREDIT_CARD_VERIFICATION_CODE}}}; + IssueOnQuery(form_description); + + EXPECT_CALL(autofill_client(), + ShowAutofillSuggestions( + AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre( + SuggestionType::kCreditCardEntry)), + Field("show_tabbed_popup", + &AutofillClient::PopupOpenArgs::show_tabbed_popup, + false)), + _)); + + OnSuggestionsReturned(queried_field().global_id(), + {Suggestion(SuggestionType::kCreditCardEntry)}); +} + +// Tests that `show_tabbed_popup` is false when the flag +// `kAutofillEnablePayNowPayLaterTabs` is disabled. +TEST_F(AutofillExternalDelegateTest, ShowTabbedPopup_FeatureDisabled) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + /*enabled_features=*/{features::kAutofillEnableAmountExtraction, + features::kAutofillEnableBuyNowPayLaterSyncing, + features::kAutofillEnableBuyNowPayLater, + features::kAutofillEnableAiBasedAmountExtraction}, + /*disabled_features=*/{features::kAutofillEnablePayNowPayLaterTabs}); + + TestPaymentsDataManager& paydm = + static_cast<TestPaymentsDataManager&>(pdm().payments_data_manager()); + paydm.AddCreditCard(test::GetCreditCard()); + paydm.AddBnplIssuer(test::GetTestLinkedBnplIssuer()); + + ON_CALL(*static_cast<MockAutofillOptimizationGuideDecider*>( + autofill_client().GetAutofillOptimizationGuideDecider()), + IsUrlEligibleForBnplIssuer) + .WillByDefault(Return(true)); + + test::FormDescription form_description = { + .fields = { + {.role = CREDIT_CARD_NUMBER, .heuristic_type = CREDIT_CARD_NUMBER}}}; + IssueOnQuery(form_description); + + EXPECT_CALL(autofill_client(), + ShowAutofillSuggestions( + AllOf(PopupOpenArgsAre(SuggestionVectorIdsAre( + SuggestionType::kCreditCardEntry)), + Field("show_tabbed_popup", + &AutofillClient::PopupOpenArgs::show_tabbed_popup, + false)), + _)); + + OnSuggestionsReturned(queried_field().global_id(), + {Suggestion(SuggestionType::kCreditCardEntry)}); +} #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || // BUILDFLAG(IS_CHROMEOS)
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp index 2ac2ae4..23856a7 100644 --- a/components/autofill_payments_strings.grdp +++ b/components/autofill_payments_strings.grdp
@@ -1045,6 +1045,12 @@ <message name="IDS_AUTOFILL_CARD_BNPL_LINKED_ISSUER_PILL_LABEL" desc="Label for the linked issuer pill view, which may be displayed on the BNPL select provider screen that prompts the user to choose a pay later provider. If this label is present, it indicates that the user has previously associated this BNPL provider with their Google account." formatter_data="android_java"> Linked </message> + <message name="IDS_AUTOFILL_PAY_NOW" desc="Label for the tab in the autofill dropdown allowing the user to pay using a standard payment method, such as a credit card."> + Pay Now + </message> + <message name="IDS_AUTOFILL_PAY_LATER" desc="Label for the tab in the autofill dropdown allowing the user to pay later using a Buy Now, Pay Later (BNPL) provider."> + Pay Later + </message> <message name="IDS_AUTOFILL_CARD_INFO_RETRIEVAL_SUGGESTION_IPH_BUBBLE_FALLBACK_LABEL" desc="Text shown in the `In-Product Help(IPH)` bubble for card info retrieval suggestions on Chrome Android when the issuer_id is not present in the card. It is specifically for card info retrieval enrolled card. Card info will be copied after the user clicks the suggestion."> You can autofill this card because your account is linked to Google Pay </message>
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_LATER.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_LATER.png.sha1 new file mode 100644 index 0000000..b0f1b7e2 --- /dev/null +++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_LATER.png.sha1
@@ -0,0 +1 @@ +54442076d460aca102f09dcebef76d5e97c46338 \ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_NOW.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_NOW.png.sha1 new file mode 100644 index 0000000..b1a7480 --- /dev/null +++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_PAY_NOW.png.sha1
@@ -0,0 +1 @@ +5c006f613048c578014733348e386968d20693af \ No newline at end of file
diff --git a/components/browser_apis/browser_controls/browser_controls_api.mojom b/components/browser_apis/browser_controls/browser_controls_api.mojom index a8884c0..ad673a5 100644 --- a/components/browser_apis/browser_controls/browser_controls_api.mojom +++ b/components/browser_apis/browser_controls/browser_controls_api.mojom
@@ -24,4 +24,13 @@ // Enters split view mode for the active tab. Only called when not in split. SplitActiveTab(); + + // Navigates back. + Back(array<ClickDispositionFlag> flags); + + // Navigates forward. + Forward(array<ClickDispositionFlag> flags); + + // Notifies that the back button was hovered. + BackButtonHovered(); };
diff --git a/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom b/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom index 31cf4b2..096b4c8 100644 --- a/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom +++ b/components/browser_apis/browser_controls/browser_controls_api_data_model.mojom
@@ -11,4 +11,6 @@ kMiddleMouseButton = 1, kAltKeyDown = 2, kMetaKeyDown = 3, + kShiftKeyDown = 4, + kControlKeyDown = 5, };
diff --git a/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom index 61c4ec9..5c40366 100644 --- a/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom +++ b/components/browser_apis/ui_controllers/toolbar/toolbar_ui_api_data_model.mojom
@@ -11,6 +11,13 @@ kReload = 1, kSplitTabsAction = 2, kSplitTabsContext = 3, + kBack = 4, + kForward = 5, +}; + +struct ButtonState { + bool enabled; + bool visible; }; enum SplitTabActiveLocation { @@ -37,9 +44,16 @@ bool is_context_menu_visible; }; +struct BackForwardControlState { + ButtonState back_button_state; + ButtonState forward_button_state; + int32 back_button_leading_margin; +}; + struct NavigationControlsState { ReloadControlState reload_control_state; SplitTabsControlState split_tabs_control_state; + BackForwardControlState back_forward_control_state; // This is incremented every time a settings change may change the layout // constants. int32 layout_constants_version = 0;
diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomBarCoordinator.java b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomBarCoordinator.java index f96ccd7d..0033bfe 100644 --- a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomBarCoordinator.java +++ b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomBarCoordinator.java
@@ -62,7 +62,7 @@ * * @param webContents WebContents that this zoom UI will control. */ - public void show(WebContents webContents) { + public void show(@Nullable WebContents webContents) { PageZoomUma.logAppMenuSliderOpenedHistogram(); // If inflating for the first time or showing from hidden, start animation
diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManager.java b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManager.java index 8ebca9e..48e43e4 100644 --- a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManager.java +++ b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManager.java
@@ -95,7 +95,9 @@ */ @VisibleForTesting public void setZoomLevel(double newZoomLevel) { - HostZoomMap.setZoomLevel(mDelegate.getWebContents(), newZoomLevel); + WebContents webContents = mDelegate.getWebContents(); + assert webContents != null; + HostZoomMap.setZoomLevel(webContents, newZoomLevel); } /**
diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManagerDelegate.java b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManagerDelegate.java index 2868b24..eadeca8 100644 --- a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManagerDelegate.java +++ b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/PageZoomManagerDelegate.java
@@ -5,6 +5,7 @@ package org.chromium.components.browser_ui.accessibility; import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; import org.chromium.content_public.browser.BrowserContextHandle; import org.chromium.content_public.browser.WebContents; @@ -15,7 +16,7 @@ /** * @return the WebContents that should be used for the zoom manager. */ - WebContents getWebContents(); + @Nullable WebContents getWebContents(); /** * @return the BrowserContextHandle that should be used for the zoom manager.
diff --git a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java index 99ac3f0..d4134caf 100644 --- a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java +++ b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
@@ -145,7 +145,7 @@ ThreadUtils.runOnUiThreadBlocking( () -> { mActivity = activityTestRule.getActivity(); - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java index a83dbaf..62022dd 100644 --- a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java +++ b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java
@@ -142,7 +142,7 @@ mWindowAndroid = ThreadUtils.runOnUiThreadBlocking( () -> { - return ActivityWindowAndroid.create( + return new ActivityWindowAndroid( mActivityTestRule.getActivity(), /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(
diff --git a/components/browser_ui/widget/android/java/res/drawable/bookmark_bar_ripple_background_selector.xml b/components/browser_ui/widget/android/java/res/drawable/bookmark_bar_ripple_background_selector.xml index 64a434f..ccb3dcbb 100644 --- a/components/browser_ui/widget/android/java/res/drawable/bookmark_bar_ripple_background_selector.xml +++ b/components/browser_ui/widget/android/java/res/drawable/bookmark_bar_ripple_background_selector.xml
@@ -5,32 +5,39 @@ found in the LICENSE file. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_hovered="true"> - <shape android:shape="rectangle"> - <solid android:color="@color/color_on_surface_with_alpha_8" /> - <corners android:radius="@dimen/bookmark_bar_chip_corner_radius"/> - </shape> - </item> - <item android:state_focused="true"> - <shape android:shape="rectangle"> - <solid android:color="@android:color/transparent" /> - <corners android:radius="@dimen/bookmark_bar_chip_corner_radius"/> - <stroke - android:color="@macro/default_control_color_active" - android:width="@dimen/focused_outline_stroke_width" /> - </shape> - </item> - <item android:state_pressed="true"> - <shape android:shape="rectangle"> - <solid android:color="@color/color_on_surface_with_alpha_12" /> - <corners android:radius="@dimen/bookmark_bar_chip_corner_radius"/> - </shape> - </item> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Layer 0: Background Fill (Hovered/Pressed states) --> <item> - <shape android:shape="rectangle"> - <solid android:color="@android:color/transparent" /> - <corners android:radius="@dimen/bookmark_bar_chip_corner_radius"/> - </shape> + <selector> + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/color_on_surface_with_alpha_12" /> + <corners android:radius="@dimen/bookmark_bar_chip_corner_radius" /> + </shape> + </item> + <item android:state_hovered="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/color_on_surface_with_alpha_8" /> + <corners android:radius="@dimen/bookmark_bar_chip_corner_radius" /> + </shape> + </item> + <item android:drawable="@android:color/transparent" /> + </selector> </item> -</selector> \ No newline at end of file + + <!-- Layer 1: Focus Ring (Drawn on top if focused) --> + <item> + <selector> + <item android:state_focused="true"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent" /> + <corners android:radius="@dimen/bookmark_bar_chip_corner_radius" /> + <stroke + android:color="@macro/default_control_color_active" + android:width="@dimen/focused_outline_stroke_width" /> + </shape> + </item> + <item android:drawable="@android:color/transparent" /> + </selector> + </item> +</layer-list> \ No newline at end of file
diff --git a/components/content_settings/core/browser/BUILD.gn b/components/content_settings/core/browser/BUILD.gn index 32fdf211..602dc25 100644 --- a/components/content_settings/core/browser/BUILD.gn +++ b/components/content_settings/core/browser/BUILD.gn
@@ -53,7 +53,11 @@ "website_settings_registry.h", ] - public_deps = [ "//base" ] + public_deps = [ + "//base", + "//url", + ] + deps = [ "//build:blink_buildflags", "//components/content_settings/core/common", @@ -68,7 +72,6 @@ "//services/network/public/mojom", "//services/preferences/public/cpp", "//services/tracing/public/cpp", - "//url", ] if (use_blink) {
diff --git a/components/contextual_search/input_state_model.cc b/components/contextual_search/input_state_model.cc index 50aa3e8..6c21bb57 100644 --- a/components/contextual_search/input_state_model.cc +++ b/components/contextual_search/input_state_model.cc
@@ -4,6 +4,7 @@ #include "components/contextual_search/input_state_model.h" +#include <algorithm> #include <map> #include <set> #include <string> @@ -214,19 +215,12 @@ namespace { -// Returns if an item is allowed in a list of items. -template <typename T, typename U> -bool IsItemAllowed(const T& item, const U& allowed_items) { - return std::find(allowed_items.begin(), allowed_items.end(), item) != - allowed_items.end(); -} - // Checks if a set of items are all present in an allowed list. template <typename T, typename U> bool AreItemsAllowed(const T& items, const U& allowed_items) { return std::all_of(items.begin(), items.end(), [&allowed_items](const auto& item) { - return IsItemAllowed(item, allowed_items); + return std::ranges::contains(allowed_items, item); }); } @@ -309,6 +303,20 @@ notifySubscribers(); } +void InputStateModel::SetPermanentlyDisabledTools( + const std::vector<ToolMode>& tools) { + permanently_disabled_tools_ = tools; + updateDisabledState(); + notifySubscribers(); +} + +void InputStateModel::SetPermanentlyDisabledInputTypes( + const std::vector<InputType>& input_types) { + permanently_disabled_input_types_ = input_types; + updateDisabledState(); + notifySubscribers(); +} + void InputStateModel::updateSelectedState(ToolMode tool, ModelMode model) { state_.active_model = model; state_.image_gen_upload_active = false; @@ -367,10 +375,9 @@ } bool incompatible_with_model = - state_.active_model != omnibox::ModelMode::MODEL_MODE_UNSPECIFIED && - active_model_rule && - !active_model_rule->allow_all_tools() && - !IsItemAllowed(tool, active_model_rule->allowed_tools()); + state_.active_model != omnibox::ModelMode::MODEL_MODE_UNSPECIFIED && + active_model_rule && !active_model_rule->allow_all_tools() && + !std::ranges::contains(active_model_rule->allowed_tools(), tool); const omnibox::ToolRule* tool_rule = GetToolRule(rule_set_, tool); bool incompatible_with_inputs = @@ -379,7 +386,8 @@ !AreItemsAllowed(GetCurrentInputTypes(session_handle_.get()), tool_rule->allowed_input_types())); - if (incompatible_with_model || incompatible_with_inputs) { + if (incompatible_with_model || incompatible_with_inputs || + std::ranges::contains(permanently_disabled_tools_, tool)) { state_.disabled_tools.push_back(tool); } } @@ -401,9 +409,9 @@ bool incompatible_with_tool = state_.active_tool != omnibox::ToolMode::TOOL_MODE_UNSPECIFIED && - (!model_rule || - (!model_rule->allow_all_tools() && - !IsItemAllowed(state_.active_tool, model_rule->allowed_tools()))); + (!model_rule || (!model_rule->allow_all_tools() && + !std::ranges::contains(model_rule->allowed_tools(), + state_.active_tool))); bool incompatible_with_inputs = (!model_rule || @@ -472,17 +480,19 @@ bool incompatible_with_model = state_.active_model != omnibox::ModelMode::MODEL_MODE_UNSPECIFIED && - active_model_rule && - !active_model_rule->allow_all_input_types() && - !IsItemAllowed(input_type, active_model_rule->allowed_input_types()); + active_model_rule && !active_model_rule->allow_all_input_types() && + !std::ranges::contains(active_model_rule->allowed_input_types(), + input_type); bool incompatible_with_tool = state_.active_tool != omnibox::ToolMode::TOOL_MODE_UNSPECIFIED && active_tool_rule && !active_tool_rule->allow_all_input_types() && - !IsItemAllowed(input_type, active_tool_rule->allowed_input_types()); + !std::ranges::contains(active_tool_rule->allowed_input_types(), + input_type); if (input_limit_reached || incompatible_with_model || - incompatible_with_tool) { + incompatible_with_tool || + std::ranges::contains(permanently_disabled_input_types_, input_type)) { state_.disabled_input_types.push_back(input_type); } }
diff --git a/components/contextual_search/input_state_model.h b/components/contextual_search/input_state_model.h index 572e6ee..a44cd1d 100644 --- a/components/contextual_search/input_state_model.h +++ b/components/contextual_search/input_state_model.h
@@ -59,6 +59,13 @@ // Called when an input of type `InputType` is added or deleted. void OnContextChanged(); + // Sets the tools that should be forced to be disabled. + void SetPermanentlyDisabledTools(const std::vector<ToolMode>& tools); + + // Sets the input types that should be forced to be disabled. + void SetPermanentlyDisabledInputTypes( + const std::vector<InputType>& input_types); + // Gets additional query params for the current state. std::map<std::string, std::string> GetAdditionalQueryParams(); @@ -103,6 +110,13 @@ raw_ptr<const PrefService> pref_service_ = nullptr; const bool is_off_the_record_; + + // Stores tools that are permanently disabled by an external trigger and must + // persist through state updates. Persists after Initialize() is called. + std::vector<ToolMode> permanently_disabled_tools_; + // Stores input_types that are permanently disabled by an external trigger and + // must persist through state updates. Persists after Initialize() is called. + std::vector<InputType> permanently_disabled_input_types_; }; } // namespace contextual_search
diff --git a/components/contextual_search/input_state_model_unittest.cc b/components/contextual_search/input_state_model_unittest.cc index 24e3544..38548b78 100644 --- a/components/contextual_search/input_state_model_unittest.cc +++ b/components/contextual_search/input_state_model_unittest.cc
@@ -783,4 +783,48 @@ cloned_state.allowed_input_types.end()); } +// crbug.com/488112121: This test covers the temporary behavior of forcing +// items to be disabled based on external triggers (like URL parameters). +// Remove this test when the temporary workaround in +// ContextualTasksComposeboxHandler is removed. +TEST_F(InputStateModelCompatibilityTest, ForcedDisabledToolsAndInputTypes) { + // Enable content sharing to prevent the policy from erasing allowlisted + // inputs. + pref_service_.SetInteger( + contextual_search::kSearchContentSharingSettings, + static_cast<int>( + contextual_search::SearchContentSharingSettingsValue::kEnabled)); + + input_state_model_->SetPermanentlyDisabledTools( + {omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH}); + auto state = input_state_model_->get_state_for_testing(); + + EXPECT_THAT(state.disabled_tools, + testing::Contains(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH)); + + input_state_model_->SetPermanentlyDisabledInputTypes( + {omnibox::InputType::INPUT_TYPE_LENS_FILE, + omnibox::InputType::INPUT_TYPE_BROWSER_TAB}); + state = input_state_model_->get_state_for_testing(); + + EXPECT_THAT(state.disabled_input_types, + testing::Contains(omnibox::InputType::INPUT_TYPE_LENS_FILE)); + EXPECT_THAT(state.disabled_input_types, + testing::Contains(omnibox::InputType::INPUT_TYPE_BROWSER_TAB)); + + input_state_model_->SetPermanentlyDisabledTools({}); + input_state_model_->SetPermanentlyDisabledInputTypes({}); + state = input_state_model_->get_state_for_testing(); + + EXPECT_THAT(state.disabled_tools, + testing::Not( + testing::Contains(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH))); + EXPECT_THAT(state.disabled_input_types, + testing::Not( + testing::Contains(omnibox::InputType::INPUT_TYPE_LENS_FILE))); + EXPECT_THAT(state.disabled_input_types, + testing::Not(testing::Contains( + omnibox::InputType::INPUT_TYPE_BROWSER_TAB))); +} + } // namespace contextual_search
diff --git a/components/crash/content/browser/BUILD.gn b/components/crash/content/browser/BUILD.gn index 38704dc..498467a 100644 --- a/components/crash/content/browser/BUILD.gn +++ b/components/crash/content/browser/BUILD.gn
@@ -27,10 +27,11 @@ deps = [ "//base", "//components/crash/core/app", - "//content/public/browser", "//content/public/common", ] + public_deps = [ "//content/public/browser" ] + if (is_castos || is_android) { sources += [ "crash_handler_host_linux.cc",
diff --git a/components/cronet/PRESUBMIT.py b/components/cronet/PRESUBMIT.py index f7cb9e1..5dd1d9f 100644 --- a/components/cronet/PRESUBMIT.py +++ b/components/cronet/PRESUBMIT.py
@@ -8,12 +8,14 @@ """ import os + PRESUBMIT_VERSION = '2.0.0' # Avoid importing modules by modifying sys.path. This causes side effects that # prevent other PRESUBMIT.py scripts from correctly resolving their dependencies. # Reference: crbug.com/478930205 - https://ci.chromium.org/ui/p/chromium/builders/ci/linux-presubmit/36616/overview + def CheckPyLint(input_api, output_api): pylint_checks = input_api.canned_checks.GetPylint(input_api, output_api) return input_api.RunTests(pylint_checks)
diff --git a/components/cronet/gn2bp/.style.yapf b/components/cronet/gn2bp/.style.yapf deleted file mode 100644 index 66694fc0..0000000 --- a/components/cronet/gn2bp/.style.yapf +++ /dev/null
@@ -1,8 +0,0 @@ -[style] -based_on_style = pep8 - -# we keep this around because PRESUBMIT.py uses a linter that enforces -# this format. -column_limit = 80 -indent_width = 2 -
diff --git a/components/cronet/gn2bp/common.py b/components/cronet/gn2bp/common.py index ec49803..b6eff97 100644 --- a/components/cronet/gn2bp/common.py +++ b/components/cronet/gn2bp/common.py
@@ -2,4 +2,4 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. def is_rust_build_script(script: str) -> bool: - return script == "//build/rust/gni_impl/run_build_script.py" \ No newline at end of file + return script == "//build/rust/gni_impl/run_build_script.py"
diff --git a/components/cronet/gn2bp/gen_android_bp.py b/components/cronet/gn2bp/gen_android_bp.py index cd3d8317..daf1b4c 100755 --- a/components/cronet/gn2bp/gen_android_bp.py +++ b/components/cronet/gn2bp/gen_android_bp.py
@@ -20,8 +20,8 @@ import gn_utils import targets as gn2bp_targets -PARENT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), - os.pardir)) +PARENT_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir)) sys.path.insert(0, os.path.join(PARENT_ROOT, "license")) import license_utils @@ -59,82 +59,84 @@ LIBCRYPTO_STRIPPED = "libcrypto" LIBCRYPTO_UNSTRIPPED = "libcrypto_unstripped" + def initialize_globals(import_channel: str): - global IMPORT_CHANNEL - IMPORT_CHANNEL = import_channel + global IMPORT_CHANNEL + IMPORT_CHANNEL = import_channel - global MODULE_PREFIX - MODULE_PREFIX = f'{IMPORT_CHANNEL}_cronet_' + global MODULE_PREFIX + MODULE_PREFIX = f'{IMPORT_CHANNEL}_cronet_' - global include_dirs_denylist - include_dirs_denylist = [ - f'external/cronet/{IMPORT_CHANNEL}/third_party/zlib/', - ] + global include_dirs_denylist + include_dirs_denylist = [ + f'external/cronet/{IMPORT_CHANNEL}/third_party/zlib/', + ] - global cc_defaults_module - cc_defaults_module = f'{MODULE_PREFIX}cc_defaults' + global cc_defaults_module + cc_defaults_module = f'{MODULE_PREFIX}cc_defaults' - global java_framework_defaults_module - java_framework_defaults_module = f'{MODULE_PREFIX}java_framework_defaults' + global java_framework_defaults_module + java_framework_defaults_module = f'{MODULE_PREFIX}java_framework_defaults' - global tree_path - tree_path = f'external/cronet/{IMPORT_CHANNEL}' + global tree_path + tree_path = f'external/cronet/{IMPORT_CHANNEL}' - global additional_args - additional_args = { - # TODO: operating on the final module names means we have to use short - # names which are less readable. Find a better way. - f'{MODULE_PREFIX}39ea1a33_quiche_net_quic_test_tools_proto_gen_h': [ - ('export_include_dirs', { - "net/third_party/quiche/src", - }) - ], - f'{MODULE_PREFIX}39ea1a33_quiche_net_quic_test_tools_proto_gen__testing_h': - [('export_include_dirs', { - "net/third_party/quiche/src", - })], - # TODO: fix upstream. Both //base:base and - # //base/allocator/partition_allocator:partition_alloc do not create a - # dependency on gtest despite using gtest_prod.h. - f'{MODULE_PREFIX}base_base': [ - ('header_libs', { - 'libgtest_prod_headers', - }), - ('export_header_lib_headers', { - 'libgtest_prod_headers', - }), - ], - f'{MODULE_PREFIX}base_allocator_partition_allocator_partition_alloc': [ - ('header_libs', { - 'libgtest_prod_headers', - }), - ], - # TODO(b/309920629): Remove once upstreamed. - f'{MODULE_PREFIX}components_cronet_android_cronet_api_java__unfiltered': [ - ('srcs', { - 'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java', - 'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java', - }), - ], - f'{MODULE_PREFIX}components_cronet_android_cronet_api_java__testing__unfiltered': - [ - ('srcs', { - 'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java', - 'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java', - }), - ], - f'{MODULE_PREFIX}components_cronet_android_cronet_javatests__testing__unfiltered': - [ - # Needed to @SkipPresubmit annotations - ('static_libs', { - 'net-tests-utils-host-device-common', - }), - ], - f'{MODULE_PREFIX}components_cronet_android_cronet__testing': [ - ('target', ('android_riscv64', { - 'stem': "libmainlinecronet_riscv64" - })), - ('comment', """TODO: remove stem for riscv64 + global additional_args + additional_args = { + # TODO: operating on the final module names means we have to use short + # names which are less readable. Find a better way. + f'{MODULE_PREFIX}39ea1a33_quiche_net_quic_test_tools_proto_gen_h': [ + ('export_include_dirs', { + "net/third_party/quiche/src", + }) + ], + f'{MODULE_PREFIX}39ea1a33_quiche_net_quic_test_tools_proto_gen__testing_h': + [('export_include_dirs', { + "net/third_party/quiche/src", + })], + # TODO: fix upstream. Both //base:base and + # //base/allocator/partition_allocator:partition_alloc do not create a + # dependency on gtest despite using gtest_prod.h. + f'{MODULE_PREFIX}base_base': [ + ('header_libs', { + 'libgtest_prod_headers', + }), + ('export_header_lib_headers', { + 'libgtest_prod_headers', + }), + ], + f'{MODULE_PREFIX}base_allocator_partition_allocator_partition_alloc': [ + ('header_libs', { + 'libgtest_prod_headers', + }), + ], + # TODO(b/309920629): Remove once upstreamed. + f'{MODULE_PREFIX}components_cronet_android_cronet_api_java__unfiltered': + [ + ('srcs', { + 'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java', + 'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java', + }), + ], + f'{MODULE_PREFIX}components_cronet_android_cronet_api_java__testing__unfiltered': + [ + ('srcs', { + 'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java', + 'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java', + }), + ], + f'{MODULE_PREFIX}components_cronet_android_cronet_javatests__testing__unfiltered': + [ + # Needed to @SkipPresubmit annotations + ('static_libs', { + 'net-tests-utils-host-device-common', + }), + ], + f'{MODULE_PREFIX}components_cronet_android_cronet__testing': [ + ('target', ('android_riscv64', { + 'stem': "libmainlinecronet_riscv64" + })), + ('comment', """TODO: remove stem for riscv64 // This is essential as there can't be two different modules // with the same output. We usually got away with that because // the non-testing Cronet is part of the Tethering APEX and the @@ -143,116 +145,116 @@ // However, Cronet does not ship to Tethering APEX for RISCV64 which // raises the conflict. Once we start shipping Cronet for RISCV64, // this can be removed."""), - ], - f'{MODULE_PREFIX}third_party_netty_tcnative_netty_tcnative_so__testing': [ - ('cflags', {"-Wno-error=pointer-bool-conversion"}) - ], - f'{MODULE_PREFIX}third_party_apache_portable_runtime_apr__testing': [ - ('cflags', { - "-Wno-incompatible-pointer-types-discards-qualifiers", - }) - ], - # TODO(b/324872305): Remove when gn desc expands public_configs and update code to propagate the - # include_dir from the public_configs - # We had to add the export_include_dirs for each target because soong generates each header - # file in a specific directory named after the target. - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags__testing': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags__testing': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags__testing': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_buildflags': - [('export_include_dirs', { - ".", - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_buildflags__testing': - [('export_include_dirs', { - ".", - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags__testing': - [('export_include_dirs', { - "base/allocator/partition_allocator/src/", - })], - # Protobuf depends on Unsafe class which is used to perform unsafe native methods. This class is not - # available in the public API provided by the android platform. It's only available by compiling - # against `core_current` and adding `libcore_private.stubs` as a dependency. - # defaults have to be removed to prevent sdk_version collision. - f'{MODULE_PREFIX}third_party_protobuf_proto_runtime_lite_java__testing__unfiltered': - [ - ('libs', { - "libcore_private.stubs", - }), - ('defaults', None), - ('sdk_version', 'core_current'), - ], - # Protobuf depends on Unsafe class which is used to perform unsafe native methods. This class is not - # available in the public API provided by the android platform. It's only available by compiling - # against `core_current` and adding `libcore_private.stubs` as a dependency. - # defaults have to be removed to prevent sdk_version collision. - f'{MODULE_PREFIX}third_party_protobuf_proto_runtime_lite_java__unfiltered': - [ - ('libs', { - "libcore_private.stubs", - }), - ('defaults', None), - ('sdk_version', 'core_current'), - ], - f'{MODULE_PREFIX}base_base_java_test_support__testing': [ - ('errorprone', ('javacflags', { - "-Xep:ReturnValueIgnored:WARN", - })) - ], - f'{MODULE_PREFIX}third_party_perfetto_gn_gen_buildflags': [ - ('export_include_dirs', { - "third_party/perfetto/build_config/", - }) - ], - f'{MODULE_PREFIX}third_party_perfetto_gn_gen_buildflags__testing': [ - ('export_include_dirs', { - "third_party/perfetto/build_config/", - }) - ], - # end export_include_dir. - # TODO: https://crbug.com/418746360 - Handle //base:build_date_internal - # for os:linux_glibc. - f'{MODULE_PREFIX}base_build_date_internal__testing': [('host_supported', - True)], - } + ], + f'{MODULE_PREFIX}third_party_netty_tcnative_netty_tcnative_so__testing': + [('cflags', {"-Wno-error=pointer-bool-conversion"})], + f'{MODULE_PREFIX}third_party_apache_portable_runtime_apr__testing': [ + ('cflags', { + "-Wno-incompatible-pointer-types-discards-qualifiers", + }) + ], + # TODO(b/324872305): Remove when gn desc expands public_configs and update code to propagate the + # include_dir from the public_configs + # We had to add the export_include_dirs for each target because soong generates each header + # file in a specific directory named after the target. + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags__testing': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags__testing': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags__testing': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_buildflags': + [('export_include_dirs', { + ".", + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_buildflags__testing': + [('export_include_dirs', { + ".", + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + f'{MODULE_PREFIX}base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags__testing': + [('export_include_dirs', { + "base/allocator/partition_allocator/src/", + })], + # Protobuf depends on Unsafe class which is used to perform unsafe native methods. This class is not + # available in the public API provided by the android platform. It's only available by compiling + # against `core_current` and adding `libcore_private.stubs` as a dependency. + # defaults have to be removed to prevent sdk_version collision. + f'{MODULE_PREFIX}third_party_protobuf_proto_runtime_lite_java__testing__unfiltered': + [ + ('libs', { + "libcore_private.stubs", + }), + ('defaults', None), + ('sdk_version', 'core_current'), + ], + # Protobuf depends on Unsafe class which is used to perform unsafe native methods. This class is not + # available in the public API provided by the android platform. It's only available by compiling + # against `core_current` and adding `libcore_private.stubs` as a dependency. + # defaults have to be removed to prevent sdk_version collision. + f'{MODULE_PREFIX}third_party_protobuf_proto_runtime_lite_java__unfiltered': + [ + ('libs', { + "libcore_private.stubs", + }), + ('defaults', None), + ('sdk_version', 'core_current'), + ], + f'{MODULE_PREFIX}base_base_java_test_support__testing': [ + ('errorprone', ('javacflags', { + "-Xep:ReturnValueIgnored:WARN", + })) + ], + f'{MODULE_PREFIX}third_party_perfetto_gn_gen_buildflags': [ + ('export_include_dirs', { + "third_party/perfetto/build_config/", + }) + ], + f'{MODULE_PREFIX}third_party_perfetto_gn_gen_buildflags__testing': [ + ('export_include_dirs', { + "third_party/perfetto/build_config/", + }) + ], + # end export_include_dir. + # TODO: https://crbug.com/418746360 - Handle //base:build_date_internal + # for os:linux_glibc. + f'{MODULE_PREFIX}base_build_date_internal__testing': [ + ('host_supported', True) + ], + } - additional_args = { - "{}{}".format(key, suffix): value - for key, value in additional_args.items() - for suffix in gn_utils.POSSIBLE_SUFFIXES - } + additional_args = { + "{}{}".format(key, suffix): value + for key, value in additional_args.items() + for suffix in gn_utils.POSSIBLE_SUFFIXES + } - additional_args.update({ - f'{MODULE_PREFIX}components_cronet_android_cronet': [('afdo', True)] - }) + additional_args.update( + {f'{MODULE_PREFIX}components_cronet_android_cronet': [('afdo', True)]}) + # Shared libraries which are directly translated to Android system equivalents. shared_library_allowlist = [ @@ -291,7 +293,7 @@ def get_linker_script_ldflag(script_path): - return f'-Wl,--script,{tree_path}/{script_path}' + return f'-Wl,--script,{tree_path}/{script_path}' _FEATURE_REGEX = "feature=\\\"(.+)\\\"" @@ -310,176 +312,176 @@ class JniZeroTargetType(enum.Enum): - GENERATOR = enum.auto() - REGISTRATION_GENERATOR = enum.auto() + GENERATOR = enum.auto() + REGISTRATION_GENERATOR = enum.auto() def get_jni_zero_target_type(target): - if target.script != '//third_party/jni_zero/jni_zero.py': - return None - if target.args[0] == 'generate-final': - return JniZeroTargetType.REGISTRATION_GENERATOR - return JniZeroTargetType.GENERATOR + if target.script != '//third_party/jni_zero/jni_zero.py': + return None + if target.args[0] == 'generate-final': + return JniZeroTargetType.REGISTRATION_GENERATOR + return JniZeroTargetType.GENERATOR # Given a jni_zero generator module, returns the path to the generated proxy # and placeholders srcjars. def get_jni_zero_generator_proxy_and_placeholder_paths(module): - assert module.jni_zero_target_type == JniZeroTargetType.GENERATOR + assert module.jni_zero_target_type == JniZeroTargetType.GENERATOR - def is_placeholder(path): - return path.endswith('_placeholder.srcjar') + def is_placeholder(path): + return path.endswith('_placeholder.srcjar') - placeholder_paths = [out for out in module.out if is_placeholder(out)] - assert len(placeholder_paths) == 1, module.name - proxy_paths = [ - out for out in module.out - if out.endswith('.srcjar') and not is_placeholder(out) - ] - assert len(proxy_paths) == 1, module.name - return proxy_paths[0], placeholder_paths[0] + placeholder_paths = [out for out in module.out if is_placeholder(out)] + assert len(placeholder_paths) == 1, module.name + proxy_paths = [ + out for out in module.out + if out.endswith('.srcjar') and not is_placeholder(out) + ] + assert len(proxy_paths) == 1, module.name + return proxy_paths[0], placeholder_paths[0] def always_disable(_, __): - return None + return None def enable_zlib(module, arch): - # Requires crrev/c/4109079 - if arch == 'common': - module.shared_libs.add('libz') - else: - module.target[arch].shared_libs.add('libz') + # Requires crrev/c/4109079 + if arch == 'common': + module.shared_libs.add('libz') + else: + module.target[arch].shared_libs.add('libz') def enable_boringssl(module, arch): - # Do not add boringssl targets to cc_genrules. This happens, because protobuf targets are - # originally static_libraries, but later get converted to a cc_genrule. - if module.is_genrule(): - return - # Lets keep statically linking BoringSSL for testing target for now. This should be fixed. - if module.name.endswith(gn_utils.TESTING_SUFFIX): - return - if arch == 'common': - shared_libs = module.shared_libs - static_libs = module.static_libs - whole_static_libs = module.whole_static_libs - else: - shared_libs = module.target[arch].shared_libs - static_libs = module.target[arch].static_libs - whole_static_libs = module.target[arch].whole_static_libs - shared_libs.add(f'{MODULE_PREFIX}{LIBCRYPTO_UNSTRIPPED}') - if module.type == "cc_library_static": - static_libs.add(f'{MODULE_PREFIX}ssl_and_pki') - else: - whole_static_libs.add(f'{MODULE_PREFIX}ssl_and_pki') + # Do not add boringssl targets to cc_genrules. This happens, because protobuf targets are + # originally static_libraries, but later get converted to a cc_genrule. + if module.is_genrule(): + return + # Lets keep statically linking BoringSSL for testing target for now. This should be fixed. + if module.name.endswith(gn_utils.TESTING_SUFFIX): + return + if arch == 'common': + shared_libs = module.shared_libs + static_libs = module.static_libs + whole_static_libs = module.whole_static_libs + else: + shared_libs = module.target[arch].shared_libs + static_libs = module.target[arch].static_libs + whole_static_libs = module.target[arch].whole_static_libs + shared_libs.add(f'{MODULE_PREFIX}{LIBCRYPTO_UNSTRIPPED}') + if module.type == "cc_library_static": + static_libs.add(f'{MODULE_PREFIX}ssl_and_pki') + else: + whole_static_libs.add(f'{MODULE_PREFIX}ssl_and_pki') def add_androidx_experimental_java_deps(module, _): - module.libs.add("androidx.annotation_annotation-experimental") + module.libs.add("androidx.annotation_annotation-experimental") def add_androidx_annotation_java_deps(module, _): - module.libs.add("androidx.annotation_annotation") + module.libs.add("androidx.annotation_annotation") def add_androidx_core_java_deps(module, _): - module.libs.add("androidx.core_core") + module.libs.add("androidx.core_core") def add_jsr305_java_deps(module, _): - module.static_libs.add("jsr305") + module.static_libs.add("jsr305") def add_errorprone_annotation_java_deps(module, _): - module.libs.add("error_prone_annotations") + module.libs.add("error_prone_annotations") def add_androidx_collection_java_deps(module, _): - module.libs.add("androidx.collection_collection") + module.libs.add("androidx.collection_collection") def add_junit_java_deps(module, _): - module.static_libs.add("junit") + module.static_libs.add("junit") def add_truth_java_deps(module, _): - module.static_libs.add("truth") + module.static_libs.add("truth") def add_hamcrest_java_deps(module, _): - module.static_libs.add("hamcrest-library") - module.static_libs.add("hamcrest") + module.static_libs.add("hamcrest-library") + module.static_libs.add("hamcrest") def add_mockito_java_deps(module, _): - module.static_libs.add("mockito") + module.static_libs.add("mockito") def add_guava_java_deps(module, _): - module.static_libs.add("guava") + module.static_libs.add("guava") def add_androidx_junit_java_deps(module, _): - module.static_libs.add("androidx.test.ext.junit") + module.static_libs.add("androidx.test.ext.junit") def add_androidx_test_runner_java_deps(module, _): - module.static_libs.add("androidx.test.runner") + module.static_libs.add("androidx.test.runner") def add_androidx_test_rules_java_deps(module, _): - module.static_libs.add("androidx.test.rules") + module.static_libs.add("androidx.test.rules") def add_android_test_base_java_deps(module, _): - module.libs.add("android.test.base") + module.libs.add("android.test.base") def add_accessibility_test_framework_java_deps(_, __): - # BaseActivityTestRule.java depends on this but BaseActivityTestRule.java is not used in aosp. - pass + # BaseActivityTestRule.java depends on this but BaseActivityTestRule.java is not used in aosp. + pass def add_espresso_java_deps(module, _): - module.static_libs.add("androidx.test.espresso.contrib") + module.static_libs.add("androidx.test.espresso.contrib") def add_android_test_mock_java_deps(module, _): - module.libs.add("android.test.mock.stubs") + module.libs.add("android.test.mock.stubs") def add_androidx_multidex_java_deps(_, __): - # Androidx-multidex is disabled on unbundled branches. - pass + # Androidx-multidex is disabled on unbundled branches. + pass def add_androidx_test_monitor_java_deps(module, _): - module.libs.add("androidx.test.monitor") + module.libs.add("androidx.test.monitor") def add_androidx_ui_automator_java_deps(module, _): - module.static_libs.add("androidx.test.uiautomator_uiautomator") + module.static_libs.add("androidx.test.uiautomator_uiautomator") def add_androidx_test_annotation_java_deps(module, _): - module.static_libs.add("androidx.test.rules") + module.static_libs.add("androidx.test.rules") def add_androidx_test_core_java_deps(module, _): - module.static_libs.add("androidx.test.core") + module.static_libs.add("androidx.test.core") def add_androidx_activity_activity(module, _): - module.static_libs.add("androidx.activity_activity") + module.static_libs.add("androidx.activity_activity") def add_androidx_fragment_fragment(module, _): - module.static_libs.add("androidx.fragment_fragment") + module.static_libs.add("androidx.fragment_fragment") def add_rustversion_deps(module, _): - module.proc_macros.add("librustversion") + module.proc_macros.add("librustversion") # Android equivalents for third-party libraries that the upstream project @@ -625,22 +627,23 @@ def _parse_pydeps(repo_path): - ''' + ''' Parses a .pydeps file, returning a list of paths relative to REPOSITORY_ROOT. ''' - repo_dir_path = os.path.dirname(repo_path) - with open(f"{REPOSITORY_ROOT}/{repo_path}", encoding='utf-8') as file: - return [ - os.path.normpath(f"{repo_dir_path}/{line.strip()}") for line in file - if not line.startswith('#') - ] + repo_dir_path = os.path.dirname(repo_path) + with open(f"{REPOSITORY_ROOT}/{repo_path}", encoding='utf-8') as file: + return [ + os.path.normpath(f"{repo_dir_path}/{line.strip()}") + for line in file if not line.startswith('#') + ] + def write_blueprint_key_value(output, name, value, sort=True, list_to_multiline_string=False): - """Writes a Blueprint key-value pair to the output. + """Writes a Blueprint key-value pair to the output. If list_to_multiline_string is set, and the value is a list, then the output value will be the list elements concatenated into a single Blueprint string, @@ -648,468 +651,473 @@ purely cosmetic feature to make the Blueprint file more readable. """ - if isinstance(value, bool): - if value: - output.append(' %s: true,' % name) - else: - output.append(' %s: false,' % name) - return - if not value: - return - if isinstance(value, set): - value = sorted(value) - if isinstance(value, list) and not list_to_multiline_string: - output.append(' %s: [' % name) - for item in sorted(value) if sort else value: - output.append(' "%s",' % item) - output.append(' ],') - return - if isinstance(value, Module.Target): - value.to_string(output) - return - if isinstance(value, dict): - kv_output = [] - for k, v in value.items(): - write_blueprint_key_value(kv_output, k, v) + if isinstance(value, bool): + if value: + output.append(' %s: true,' % name) + else: + output.append(' %s: false,' % name) + return + if not value: + return + if isinstance(value, set): + value = sorted(value) + if isinstance(value, list) and not list_to_multiline_string: + output.append(' %s: [' % name) + for item in sorted(value) if sort else value: + output.append(' "%s",' % item) + output.append(' ],') + return + if isinstance(value, Module.Target): + value.to_string(output) + return + if isinstance(value, dict): + kv_output = [] + for k, v in value.items(): + write_blueprint_key_value(kv_output, k, v) - output.append(' %s: {' % name) - for line in kv_output: - output.append(' %s' % line) - output.append(' },') - return - output.append( - ' %s: "%s",' % - (name, - NEWLINE.join( - str(line).replace('\\', '\\\\').replace('"', '\\"') - for line in (value if isinstance(value, list) else [value])))) + output.append(' %s: {' % name) + for line in kv_output: + output.append(' %s' % line) + output.append(' },') + return + output.append( + ' %s: "%s",' % + (name, + NEWLINE.join( + str(line).replace('\\', '\\\\').replace('"', '\\"') + for line in (value if isinstance(value, list) else [value])))) class Module: - """A single module (e.g., cc_binary, cc_test) in a blueprint.""" + """A single module (e.g., cc_binary, cc_test) in a blueprint.""" - class Target: - """A target-scoped part of a module""" + class Target: + """A target-scoped part of a module""" - def __init__(self, name): - self.name = name - self.srcs = set() - self.shared_libs = set() - self.static_libs = set() - self.whole_static_libs = set() - self.header_libs = set() - self.cflags = list() - self.stl = None - self.cppflags = list() - self.include_dirs = set() - self.generated_headers = set() - self.export_generated_headers = set() - self.ldflags = list() - self.compile_multilib = None - self.stem = "" - self.edition = "" - self.features = set() - self.cfgs = set() - self.flags = list() - self.rustlibs = set() - self.proc_macros = set() + def __init__(self, name): + self.name = name + self.srcs = set() + self.shared_libs = set() + self.static_libs = set() + self.whole_static_libs = set() + self.header_libs = set() + self.cflags = list() + self.stl = None + self.cppflags = list() + self.include_dirs = set() + self.generated_headers = set() + self.export_generated_headers = set() + self.ldflags = list() + self.compile_multilib = None + self.stem = "" + self.edition = "" + self.features = set() + self.cfgs = set() + self.flags = list() + self.rustlibs = set() + self.proc_macros = set() + + def to_string(self, output): + nested_out = [] + self._output_field(nested_out, 'srcs') + self._output_field(nested_out, 'shared_libs') + self._output_field(nested_out, 'static_libs') + self._output_field(nested_out, 'whole_static_libs') + self._output_field(nested_out, 'header_libs') + # While sorting is a requirement for a deterministic output, sorting these flags correctly is + # challenging. Sorting requires knowing the boundaries of each flag, but we cannot simply + # assume flags are defined by whitespace, a leading - character, or something else. + # With that in mind, we choose instead not to sort and instead we rely on GN's ordering of + # these flags (and we assume that that ordering is deterministic). + self._output_field(nested_out, 'cflags', sort=False) + self._output_field(nested_out, 'stl') + # The reasoning for disabling sort is the same as cflags. + self._output_field(nested_out, 'cppflags', sort=False) + self._output_field(nested_out, 'include_dirs') + self._output_field(nested_out, 'generated_headers') + self._output_field(nested_out, 'export_generated_headers') + # The reasoning for disabling sort is the same as cflags. + self._output_field(nested_out, 'ldflags', sort=False) + self._output_field(nested_out, 'compile_multilib') + self._output_field(nested_out, 'stem') + self._output_field(nested_out, "edition") + self._output_field(nested_out, 'cfgs') + self._output_field(nested_out, 'features') + self._output_field(nested_out, 'flags', sort=False) + self._output_field(nested_out, 'rustlibs') + self._output_field(nested_out, 'proc_macros') + + if nested_out: + output.append(' %s: {' % self.name) + for line in nested_out: + output.append(' %s' % line) + output.append(' },') + + def _output_field(self, + output, + name, + sort=True, + list_to_multiline_string=False): + return write_blueprint_key_value( + output, + name, + getattr(self, name), + sort=sort, + list_to_multiline_string=list_to_multiline_string) + + def __init__(self, mod_type, name, gn_target): + self.type = mod_type + self.gn_target = gn_target + self.name = name + self.srcs = set() + self.comment = 'GN: ' + gn_target + self.shared_libs = set() + self.static_libs = set() + self.whole_static_libs = set() + self.tools = set() + self.cmd = None + self.host_supported = False + self.host_cross_supported = True + self.device_supported = True + self.init_rc = set() + self.out = set() + self.export_include_dirs = set() + self.generated_headers = set() + self.export_generated_headers = set() + self.export_static_lib_headers = set() + self.export_header_lib_headers = set() + self.defaults = set() + self.cflags = list() + self.include_dirs = set() + self.local_include_dirs = set() + self.header_libs = set() + self.tool_files = set() + # target contains a dict of Targets indexed by os_arch. + # example: { 'android_x86': Target('android_x86') + self.target = dict() + self.target['android'] = self.Target('android') + self.target['android_x86'] = self.Target('android_x86') + self.target['android_x86_64'] = self.Target('android_x86_64') + self.target['android_arm'] = self.Target('android_arm') + self.target['android_arm64'] = self.Target('android_arm64') + self.target['android_riscv64'] = self.Target('android_riscv64') + self.target['host'] = self.Target('host') + self.target['glibc'] = self.Target('glibc') + self.stl = None + self.cpp_std = None + self.strip = dict() + self.data = set() + self.apex_available = set() + self.min_sdk_version = None + self.proto = dict() + self.linker_scripts = set() + self.ldflags = list() + # The genrule_XXX below are properties that must to be propagated back + # on the module(s) that depend on the genrule. + self.genrule_headers = set() + self.genrule_srcs = set() + self.genrule_shared_libs = set() + self.genrule_header_libs = set() + self.version_script = None + self.test_suites = set() + self.test_config = None + self.cppflags = list() + self.rtti = False + # Name of the output. Used for setting .so file name for libcronet + self.libs = set() + self.stem = None + self.compile_multilib = None + self.plugins = set() + self.processor_class = None + self.sdk_version = None + self.javacflags = set() + self.c_std = None + self.default_applicable_licenses = set() + self.default_visibility = [] + self.visibility = set() + self.gn_type = None + self.jarjar_rules = "" + self.jars = set() + self.build_file_path = None + self.include_build_directory = None + self.allow_rebasing = False + self.license_kinds = set() + self.license_text = set() + self.errorprone = dict() + self.crate_name = None + # Should be arch-dependant + self.crate_root = None + self.edition = None + self.rustlibs = set() + self.proc_macros = set() + self.wrapper_src = "" + self.source_stem = "" + self.bindgen_flags = set() + self.handle_static_inline = None + self.static_inline_library = "" + self.jni_zero_target_type = None + self.unstable = "" + self.path = "" + # In the case of Java "top-level" modules, this points to the corresponding + # "unfiltered" module. The top-level module is just a dependency holder; + # it's the unfiltered module that does the actual compiling. For more + # details, see `create_java_module()`. + self.java_unfiltered_module = None + self.cargo_env_compat = None + self.cargo_pkg_version = None + self.whole_program_vtables = False + self.afdo = None + + def variant(self, arch_name): + return self if arch_name == 'common' else self.target[arch_name] def to_string(self, output): - nested_out = [] - self._output_field(nested_out, 'srcs') - self._output_field(nested_out, 'shared_libs') - self._output_field(nested_out, 'static_libs') - self._output_field(nested_out, 'whole_static_libs') - self._output_field(nested_out, 'header_libs') - # While sorting is a requirement for a deterministic output, sorting these flags correctly is - # challenging. Sorting requires knowing the boundaries of each flag, but we cannot simply - # assume flags are defined by whitespace, a leading - character, or something else. - # With that in mind, we choose instead not to sort and instead we rely on GN's ordering of - # these flags (and we assume that that ordering is deterministic). - self._output_field(nested_out, 'cflags', sort=False) - self._output_field(nested_out, 'stl') - # The reasoning for disabling sort is the same as cflags. - self._output_field(nested_out, 'cppflags', sort=False) - self._output_field(nested_out, 'include_dirs') - self._output_field(nested_out, 'generated_headers') - self._output_field(nested_out, 'export_generated_headers') - # The reasoning for disabling sort is the same as cflags. - self._output_field(nested_out, 'ldflags', sort=False) - self._output_field(nested_out, 'compile_multilib') - self._output_field(nested_out, 'stem') - self._output_field(nested_out, "edition") - self._output_field(nested_out, 'cfgs') - self._output_field(nested_out, 'features') - self._output_field(nested_out, 'flags', sort=False) - self._output_field(nested_out, 'rustlibs') - self._output_field(nested_out, 'proc_macros') + if self.comment: + output.append('// %s' % self.comment) + output.append('%s {' % self.type) + self._output_field(output, 'name') + self._output_field(output, 'srcs') + self._output_field(output, 'shared_libs') + self._output_field(output, 'static_libs') + self._output_field(output, 'whole_static_libs') + self._output_field(output, 'tools') + self._output_field(output, + 'cmd', + sort=False, + list_to_multiline_string=True) + if self.host_supported: + self._output_field(output, 'host_supported') + if not self.host_cross_supported: + self._output_field(output, 'host_cross_supported') + if not self.device_supported: + self._output_field(output, 'device_supported') + self._output_field(output, 'init_rc') + self._output_field(output, 'out') + self._output_field(output, 'export_include_dirs') + self._output_field(output, 'generated_headers') + self._output_field(output, 'export_generated_headers') + self._output_field(output, 'export_static_lib_headers') + self._output_field(output, 'export_header_lib_headers') + self._output_field(output, 'defaults') + # While sorting is a requirement for a deterministic output, sorting these flags correctly is + # challenging. Sorting requires knowing the boundaries of each flag, but we cannot simply + # assume flags are defined by whitespace, a leading - character, or something else. + # With that in mind, we choose instead not to sort and instead we rely on GN's ordering of + # these flags (and we assume that that ordering is deterministic). + self._output_field(output, 'cflags', sort=False) + self._output_field(output, 'include_dirs') + self._output_field(output, 'local_include_dirs') + self._output_field(output, 'header_libs') + self._output_field(output, 'strip') + self._output_field(output, 'tool_files') + self._output_field(output, 'data') + self._output_field(output, 'stl') + self._output_field(output, 'cpp_std') + self._output_field(output, 'apex_available') + self._output_field(output, 'min_sdk_version') + self._output_field(output, 'version_script') + self._output_field(output, 'test_suites') + self._output_field(output, 'test_config') + self._output_field(output, 'proto') + self._output_field(output, 'linker_scripts') + # The reasoning for disabling sort is the same as cflags. + self._output_field(output, 'ldflags', sort=False) + # The reasoning for disabling sort is the same as cflags. + self._output_field(output, 'cppflags', sort=False) + self._output_field(output, 'unstable') + self._output_field(output, 'path') + self._output_field(output, 'libs') + self._output_field(output, 'stem') + self._output_field(output, 'compile_multilib') + self._output_field(output, 'plugins') + self._output_field(output, 'processor_class') + self._output_field(output, 'sdk_version') + self._output_field(output, 'javacflags') + self._output_field(output, 'c_std') + self._output_field(output, 'default_applicable_licenses') + self._output_field(output, 'default_visibility') + self._output_field(output, 'visibility') + self._output_field(output, 'jarjar_rules') + self._output_field(output, 'jars') + self._output_field(output, 'include_build_directory') + self._output_field(output, 'license_text') + self._output_field(output, "license_kinds") + self._output_field(output, "errorprone") + self._output_field(output, 'crate_name') + self._output_field(output, 'crate_root') + self._output_field(output, 'rustlibs') + self._output_field(output, 'proc_macros') + self._output_field(output, 'source_stem') + self._output_field(output, 'bindgen_flags') + self._output_field(output, 'wrapper_src') + self._output_field(output, 'handle_static_inline') + self._output_field(output, 'static_inline_library') + self._output_field(output, 'cargo_env_compat') + self._output_field(output, 'cargo_pkg_version') + if self.whole_program_vtables: + self._output_field(output, 'whole_program_vtables') + if self.afdo: + self._output_field(output, 'afdo') + if self.rtti: + self._output_field(output, 'rtti') + target_out = [] + for arch, target in sorted(self.target.items()): + # _output_field calls getattr(self, arch). + setattr(self, arch, target) + self._output_field(target_out, arch) - if nested_out: - output.append(' %s: {' % self.name) - for line in nested_out: - output.append(' %s' % line) - output.append(' },') + if target_out: + output.append(' target: {') + for line in target_out: + output.append(' %s' % line) + output.append(' },') + + output.append('}') + output.append('') + + def add_android_shared_lib(self, lib): + if self.type.startswith('java'): + raise Exception( + 'Adding Android shared lib for java_* targets is unsupported') + if self.type == 'cc_binary_host': + raise Exception( + 'Adding Android shared lib for host tool is unsupported') + + if self.host_supported: + self.target['android'].shared_libs.add(lib) + else: + self.shared_libs.add(lib) + + def is_test(self): + if gn_utils.TESTING_SUFFIX in self.name: + name_without_prefix = self.name[:self.name.find(gn_utils. + TESTING_SUFFIX)] + return any(name_without_prefix == label_to_module_name(target) + for target in gn2bp_targets.DEFAULT_TESTS) + return False def _output_field(self, output, name, sort=True, list_to_multiline_string=False): - return write_blueprint_key_value( - output, - name, - getattr(self, name), - sort=sort, - list_to_multiline_string=list_to_multiline_string) + return write_blueprint_key_value( + output, + name, + getattr(self, name), + sort=sort, + list_to_multiline_string=list_to_multiline_string) - def __init__(self, mod_type, name, gn_target): - self.type = mod_type - self.gn_target = gn_target - self.name = name - self.srcs = set() - self.comment = 'GN: ' + gn_target - self.shared_libs = set() - self.static_libs = set() - self.whole_static_libs = set() - self.tools = set() - self.cmd = None - self.host_supported = False - self.host_cross_supported = True - self.device_supported = True - self.init_rc = set() - self.out = set() - self.export_include_dirs = set() - self.generated_headers = set() - self.export_generated_headers = set() - self.export_static_lib_headers = set() - self.export_header_lib_headers = set() - self.defaults = set() - self.cflags = list() - self.include_dirs = set() - self.local_include_dirs = set() - self.header_libs = set() - self.tool_files = set() - # target contains a dict of Targets indexed by os_arch. - # example: { 'android_x86': Target('android_x86') - self.target = dict() - self.target['android'] = self.Target('android') - self.target['android_x86'] = self.Target('android_x86') - self.target['android_x86_64'] = self.Target('android_x86_64') - self.target['android_arm'] = self.Target('android_arm') - self.target['android_arm64'] = self.Target('android_arm64') - self.target['android_riscv64'] = self.Target('android_riscv64') - self.target['host'] = self.Target('host') - self.target['glibc'] = self.Target('glibc') - self.stl = None - self.cpp_std = None - self.strip = dict() - self.data = set() - self.apex_available = set() - self.min_sdk_version = None - self.proto = dict() - self.linker_scripts = set() - self.ldflags = list() - # The genrule_XXX below are properties that must to be propagated back - # on the module(s) that depend on the genrule. - self.genrule_headers = set() - self.genrule_srcs = set() - self.genrule_shared_libs = set() - self.genrule_header_libs = set() - self.version_script = None - self.test_suites = set() - self.test_config = None - self.cppflags = list() - self.rtti = False - # Name of the output. Used for setting .so file name for libcronet - self.libs = set() - self.stem = None - self.compile_multilib = None - self.plugins = set() - self.processor_class = None - self.sdk_version = None - self.javacflags = set() - self.c_std = None - self.default_applicable_licenses = set() - self.default_visibility = [] - self.visibility = set() - self.gn_type = None - self.jarjar_rules = "" - self.jars = set() - self.build_file_path = None - self.include_build_directory = None - self.allow_rebasing = False - self.license_kinds = set() - self.license_text = set() - self.errorprone = dict() - self.crate_name = None - # Should be arch-dependant - self.crate_root = None - self.edition = None - self.rustlibs = set() - self.proc_macros = set() - self.wrapper_src = "" - self.source_stem = "" - self.bindgen_flags = set() - self.handle_static_inline = None - self.static_inline_library = "" - self.jni_zero_target_type = None - self.unstable = "" - self.path = "" - # In the case of Java "top-level" modules, this points to the corresponding - # "unfiltered" module. The top-level module is just a dependency holder; - # it's the unfiltered module that does the actual compiling. For more - # details, see `create_java_module()`. - self.java_unfiltered_module = None - self.cargo_env_compat = None - self.cargo_pkg_version = None - self.whole_program_vtables = False - self.afdo = None + def is_compiled(self): + return self.type not in ('cc_genrule', 'filegroup', 'java_genrule') - def variant(self, arch_name): - return self if arch_name == 'common' else self.target[arch_name] + def is_genrule(self): + return self.type == "cc_genrule" - def to_string(self, output): - if self.comment: - output.append('// %s' % self.comment) - output.append('%s {' % self.type) - self._output_field(output, 'name') - self._output_field(output, 'srcs') - self._output_field(output, 'shared_libs') - self._output_field(output, 'static_libs') - self._output_field(output, 'whole_static_libs') - self._output_field(output, 'tools') - self._output_field(output, 'cmd', sort=False, list_to_multiline_string=True) - if self.host_supported: - self._output_field(output, 'host_supported') - if not self.host_cross_supported: - self._output_field(output, 'host_cross_supported') - if not self.device_supported: - self._output_field(output, 'device_supported') - self._output_field(output, 'init_rc') - self._output_field(output, 'out') - self._output_field(output, 'export_include_dirs') - self._output_field(output, 'generated_headers') - self._output_field(output, 'export_generated_headers') - self._output_field(output, 'export_static_lib_headers') - self._output_field(output, 'export_header_lib_headers') - self._output_field(output, 'defaults') - # While sorting is a requirement for a deterministic output, sorting these flags correctly is - # challenging. Sorting requires knowing the boundaries of each flag, but we cannot simply - # assume flags are defined by whitespace, a leading - character, or something else. - # With that in mind, we choose instead not to sort and instead we rely on GN's ordering of - # these flags (and we assume that that ordering is deterministic). - self._output_field(output, 'cflags', sort=False) - self._output_field(output, 'include_dirs') - self._output_field(output, 'local_include_dirs') - self._output_field(output, 'header_libs') - self._output_field(output, 'strip') - self._output_field(output, 'tool_files') - self._output_field(output, 'data') - self._output_field(output, 'stl') - self._output_field(output, 'cpp_std') - self._output_field(output, 'apex_available') - self._output_field(output, 'min_sdk_version') - self._output_field(output, 'version_script') - self._output_field(output, 'test_suites') - self._output_field(output, 'test_config') - self._output_field(output, 'proto') - self._output_field(output, 'linker_scripts') - # The reasoning for disabling sort is the same as cflags. - self._output_field(output, 'ldflags', sort=False) - # The reasoning for disabling sort is the same as cflags. - self._output_field(output, 'cppflags', sort=False) - self._output_field(output, 'unstable') - self._output_field(output, 'path') - self._output_field(output, 'libs') - self._output_field(output, 'stem') - self._output_field(output, 'compile_multilib') - self._output_field(output, 'plugins') - self._output_field(output, 'processor_class') - self._output_field(output, 'sdk_version') - self._output_field(output, 'javacflags') - self._output_field(output, 'c_std') - self._output_field(output, 'default_applicable_licenses') - self._output_field(output, 'default_visibility') - self._output_field(output, 'visibility') - self._output_field(output, 'jarjar_rules') - self._output_field(output, 'jars') - self._output_field(output, 'include_build_directory') - self._output_field(output, 'license_text') - self._output_field(output, "license_kinds") - self._output_field(output, "errorprone") - self._output_field(output, 'crate_name') - self._output_field(output, 'crate_root') - self._output_field(output, 'rustlibs') - self._output_field(output, 'proc_macros') - self._output_field(output, 'source_stem') - self._output_field(output, 'bindgen_flags') - self._output_field(output, 'wrapper_src') - self._output_field(output, 'handle_static_inline') - self._output_field(output, 'static_inline_library') - self._output_field(output, 'cargo_env_compat') - self._output_field(output, 'cargo_pkg_version') - if self.whole_program_vtables: - self._output_field(output, 'whole_program_vtables') - if self.afdo: - self._output_field(output, 'afdo') - if self.rtti: - self._output_field(output, 'rtti') - target_out = [] - for arch, target in sorted(self.target.items()): - # _output_field calls getattr(self, arch). - setattr(self, arch, target) - self._output_field(target_out, arch) + def has_input_files(self): + if self.type in ["java_library", "java_import", "rust_bindgen"]: + return True + if len(self.srcs) > 0: + return True + if any(len(target.srcs) > 0 for target in self.target.values()): + return True + # Allow cc_static_library with export_generated_headers as those are crucial for + # the depending modules + return len(self.export_generated_headers) > 0 or len( + self.generated_headers) > 0 - if target_out: - output.append(' target: {') - for line in target_out: - output.append(' %s' % line) - output.append(' },') - - output.append('}') - output.append('') - - def add_android_shared_lib(self, lib): - if self.type.startswith('java'): - raise Exception( - 'Adding Android shared lib for java_* targets is unsupported') - if self.type == 'cc_binary_host': - raise Exception('Adding Android shared lib for host tool is unsupported') - - if self.host_supported: - self.target['android'].shared_libs.add(lib) - else: - self.shared_libs.add(lib) - - def is_test(self): - if gn_utils.TESTING_SUFFIX in self.name: - name_without_prefix = self.name[:self.name.find(gn_utils.TESTING_SUFFIX)] - return any(name_without_prefix == label_to_module_name(target) - for target in gn2bp_targets.DEFAULT_TESTS) - return False - - def _output_field(self, - output, - name, - sort=True, - list_to_multiline_string=False): - return write_blueprint_key_value( - output, - name, - getattr(self, name), - sort=sort, - list_to_multiline_string=list_to_multiline_string) - - def is_compiled(self): - return self.type not in ('cc_genrule', 'filegroup', 'java_genrule') - - def is_genrule(self): - return self.type == "cc_genrule" - - def has_input_files(self): - if self.type in ["java_library", "java_import", "rust_bindgen"]: - return True - if len(self.srcs) > 0: - return True - if any(len(target.srcs) > 0 for target in self.target.values()): - return True - # Allow cc_static_library with export_generated_headers as those are crucial for - # the depending modules - return len(self.export_generated_headers) > 0 or len( - self.generated_headers) > 0 - - def is_java_top_level_module(self): - return self.java_unfiltered_module is not None + def is_java_top_level_module(self): + return self.java_unfiltered_module is not None class Blueprint: - """In-memory representation of an Android.bp file.""" + """In-memory representation of an Android.bp file.""" - def __init__(self, buildgn_directory_path: str = ""): - self.modules = {} - # Holds the BUILD.gn path which resulted in the creation of this Android.bp. - self._buildgn_directory_path = buildgn_directory_path - self._readme_location = buildgn_directory_path - self._package_module = None - self._license_module = None + def __init__(self, buildgn_directory_path: str = ""): + self.modules = {} + # Holds the BUILD.gn path which resulted in the creation of this Android.bp. + self._buildgn_directory_path = buildgn_directory_path + self._readme_location = buildgn_directory_path + self._package_module = None + self._license_module = None - def add_module(self, module): - """Adds a new module to the blueprint, replacing any existing module + def add_module(self, module): + """Adds a new module to the blueprint, replacing any existing module with the same name. Args: module: Module instance. """ - self.modules[module.name] = module + self.modules[module.name] = module - def set_package_module(self, module): - self._package_module = module + def set_package_module(self, module): + self._package_module = module - def set_license_module(self, module): - self._license_module = module + def set_license_module(self, module): + self._license_module = module - def get_license_module(self): - return self._license_module + def get_license_module(self): + return self._license_module - def set_readme_location(self, readme_path: str): - self._readme_location = readme_path + def set_readme_location(self, readme_path: str): + self._readme_location = readme_path - def get_readme_location(self): - return self._readme_location + def get_readme_location(self): + return self._readme_location - def get_buildgn_location(self): - return self._buildgn_directory_path + def get_buildgn_location(self): + return self._buildgn_directory_path - def to_string(self): - ret = [] - if self._package_module: - self._package_module.to_string(ret) - if self._license_module: - self._license_module.to_string(ret) - for m in sorted(self.modules.values(), key=lambda m: m.name): - if (m.type != "cc_library_static" - or m.has_input_files()) and m.gn_target not in replace_deps.keys(): - # Don't print cc_library_static with empty srcs. These attributes are already - # propagated up the tree. Printing them messes the presubmits because - # every module is compiled while those targets are not reachable in - # a normal compilation path. - m.to_string(ret) - return ret + def to_string(self): + ret = [] + if self._package_module: + self._package_module.to_string(ret) + if self._license_module: + self._license_module.to_string(ret) + for m in sorted(self.modules.values(), key=lambda m: m.name): + if (m.type != "cc_library_static" or m.has_input_files() + ) and m.gn_target not in replace_deps.keys(): + # Don't print cc_library_static with empty srcs. These attributes are already + # propagated up the tree. Printing them messes the presubmits because + # every module is compiled while those targets are not reachable in + # a normal compilation path. + m.to_string(ret) + return ret def label_to_module_name(label, short=False): - """Turn a GN label (e.g., //:perfetto_tests) into a module name.""" - module = re.sub(r'^//:?', '', label) + """Turn a GN label (e.g., //:perfetto_tests) into a module name.""" + module = re.sub(r'^//:?', '', label) - if short: - # We want the module name to be short, but we still need it to be unique and - # somewhat readable. To do this we replace just the path by a short hash. - parts = module.rsplit('/', maxsplit=1) - if len(parts) > 1 and len(parts[0]) > 10: - module = hashlib.sha256( - parts[0].encode('utf-8')).hexdigest()[:8] + '_' + parts[1] + if short: + # We want the module name to be short, but we still need it to be unique and + # somewhat readable. To do this we replace just the path by a short hash. + parts = module.rsplit('/', maxsplit=1) + if len(parts) > 1 and len(parts[0]) > 10: + module = hashlib.sha256( + parts[0].encode('utf-8')).hexdigest()[:8] + '_' + parts[1] - module = re.sub(r'[^a-zA-Z0-9_]', '_', module) + module = re.sub(r'[^a-zA-Z0-9_]', '_', module) - if not module.startswith(MODULE_PREFIX): - return MODULE_PREFIX + module - return module + if not module.startswith(MODULE_PREFIX): + return MODULE_PREFIX + module + return module def get_target_name(label): - return label[label.find(":") + 1:] + return label[label.find(":") + 1:] def is_supported_source_file(name): - """Returns True if |name| can appear in a 'srcs' list.""" - return os.path.splitext(name)[1] in [ - '.c', '.cc', '.cpp', '.java', '.proto', '.S', '.aidl', '.rs' - ] + """Returns True if |name| can appear in a 'srcs' list.""" + return os.path.splitext(name)[1] in [ + '.c', '.cc', '.cpp', '.java', '.proto', '.S', '.aidl', '.rs' + ] def normalize_rust_flags( - rust_flags: List[str]) -> Dict[str, Union[Set[str], None]]: - """ + rust_flags: List[str]) -> Dict[str, Union[Set[str], None]]: + """ Normalizes the rust params where it tries to put (key, value) param as a dictionary key. A key without value will have None as value. @@ -1128,117 +1136,119 @@ :return: Dictionary of rust flags where each key will point to a list of values. """ - args_mapping = {} - previous_key = None - for rust_flag in rust_flags: - if not rust_flag: - # Ignore empty strings - continue + args_mapping = {} + previous_key = None + for rust_flag in rust_flags: + if not rust_flag: + # Ignore empty strings + continue - if not rust_flag.startswith("-"): - # This might be a key on its own, rustc supports params with no keys - # such as (@path). - if rust_flag.startswith("@"): - args_mapping[rust_flag] = None - if previous_key: - args_mapping[previous_key] = None - else: - # This is the value to the previous key (eg: ["--cfg", "val"]) - if not previous_key: - raise ValueError( - f"Field {rust_flag} does not relate to any key. Rust flags found: {rust_flags}" - ) - if previous_key not in args_mapping: - args_mapping[previous_key] = set() - args_mapping[previous_key].add(rust_flag) - previous_key = None - else: - if previous_key: - # We have a previous key, that means that the previous key is - # a no-value key. + if not rust_flag.startswith("-"): + # This might be a key on its own, rustc supports params with no keys + # such as (@path). + if rust_flag.startswith("@"): + args_mapping[rust_flag] = None + if previous_key: + args_mapping[previous_key] = None + else: + # This is the value to the previous key (eg: ["--cfg", "val"]) + if not previous_key: + raise ValueError( + f"Field {rust_flag} does not relate to any key. Rust flags found: {rust_flags}" + ) + if previous_key not in args_mapping: + args_mapping[previous_key] = set() + args_mapping[previous_key].add(rust_flag) + previous_key = None + else: + if previous_key: + # We have a previous key, that means that the previous key is + # a no-value key. + args_mapping[previous_key] = None + previous_key = None + # This can be a key-only string or key=value or + # key=foo=value (eg:--cfg=feature=X) or key and value in different strings. + if "=" in rust_flag: + # We found an equal, this is probably a key=value string. + rust_flag_split = rust_flag.split("=") + if len(rust_flag_split) > 3: + raise ValueError( + f"Could not normalize flag {rust_flag} as it has multiple equal signs." + ) + if rust_flag_split[0] not in args_mapping: + args_mapping[rust_flag_split[0]] = set() + args_mapping[rust_flag_split[0]].add("=".join( + rust_flag_split[1:])) + else: + # Assume this is a key-only string. This will be resolved in the next + # iteration. + previous_key = rust_flag + if previous_key: + # We have a previous key without a value, this must be a key-only string. args_mapping[previous_key] = None - previous_key = None - # This can be a key-only string or key=value or - # key=foo=value (eg:--cfg=feature=X) or key and value in different strings. - if "=" in rust_flag: - # We found an equal, this is probably a key=value string. - rust_flag_split = rust_flag.split("=") - if len(rust_flag_split) > 3: - raise ValueError( - f"Could not normalize flag {rust_flag} as it has multiple equal signs." - ) - if rust_flag_split[0] not in args_mapping: - args_mapping[rust_flag_split[0]] = set() - args_mapping[rust_flag_split[0]].add("=".join(rust_flag_split[1:])) - else: - # Assume this is a key-only string. This will be resolved in the next - # iteration. - previous_key = rust_flag - if previous_key: - # We have a previous key without a value, this must be a key-only string. - args_mapping[previous_key] = None - return args_mapping + return args_mapping def _set_rust_flags(module: Module.Target, rust_flags: List[str], arch_name: str) -> None: - rust_flags_dict = normalize_rust_flags(rust_flags) - if "--edition" in rust_flags_dict: - module.edition = list(rust_flags_dict["--edition"])[0] + rust_flags_dict = normalize_rust_flags(rust_flags) + if "--edition" in rust_flags_dict: + module.edition = list(rust_flags_dict["--edition"])[0] - for cfg in rust_flags_dict.get("--cfg", set()): - # This cfg is not actually used in code; Chromium only uses it to force - # rebuilds on rustc rolls. It doesn't hurt, per se, but it does create - # annoying diff noise on Android.bp files, so we drop it for - # aesthetic/convenience reasons. - if cfg.startswith("cr_rustc_revision="): - continue - feature_regex = re.match(_FEATURE_REGEX, cfg) - if feature_regex: - module.features.add(feature_regex.group(1)) - else: - module.cfgs.add(cfg.replace("\"", "\\\"")) + for cfg in rust_flags_dict.get("--cfg", set()): + # This cfg is not actually used in code; Chromium only uses it to force + # rebuilds on rustc rolls. It doesn't hurt, per se, but it does create + # annoying diff noise on Android.bp files, so we drop it for + # aesthetic/convenience reasons. + if cfg.startswith("cr_rustc_revision="): + continue + feature_regex = re.match(_FEATURE_REGEX, cfg) + if feature_regex: + module.features.add(feature_regex.group(1)) + else: + module.cfgs.add(cfg.replace("\"", "\\\"")) - pre_filter_flags = [] - for (key, values) in rust_flags_dict.items(): - if values is None: - pre_filter_flags.append(key) - else: - pre_filter_flags.extend(f"{key}={param_val}" for param_val in values) + pre_filter_flags = [] + for (key, values) in rust_flags_dict.items(): + if values is None: + pre_filter_flags.append(key) + else: + pre_filter_flags.extend(f"{key}={param_val}" + for param_val in values) - flags_to_remove = _RUST_FLAGS_TO_REMOVE - # AOSP compiles everything for host under panic=unwind instead of abort. - # In order to be consistent with the ecosystem, remove the -Cpanic flag. - if arch_name == "host": - flags_to_remove.append("-Cpanic") + flags_to_remove = _RUST_FLAGS_TO_REMOVE + # AOSP compiles everything for host under panic=unwind instead of abort. + # In order to be consistent with the ecosystem, remove the -Cpanic flag. + if arch_name == "host": + flags_to_remove.append("-Cpanic") - # Remove restricted flags - for pre_filter_flag in pre_filter_flags: - if not any( - pre_filter_flag.startswith(restricted_flag) - for restricted_flag in flags_to_remove): - module.flags.append(pre_filter_flag) + # Remove restricted flags + for pre_filter_flag in pre_filter_flags: + if not any( + pre_filter_flag.startswith(restricted_flag) + for restricted_flag in flags_to_remove): + module.flags.append(pre_filter_flag) def get_protoc_module_name(gn): - # Note we use Chromium's protoc, not AOSP's. AOSP protoc does not work for us - # because that would require us to link against AOSP's protobuf C++ runtime - # library as well (libprotobuf-cpp-lite) as the generated code is coupled to - # the runtime library. Problem is, the protobuf C++ runtime library uses the - # C++ STL extensively in its public API (e.g. public functions taking - # std::string). Because libc++ does not guarantee ABI compatibility, this in - # turn means that both the producer (libprotobuf-cpp-lite) and the consumer - # (Cronet) of the API must link against the same libc++. Unfortunately that is - # not currently the case - libprotobuf-cpp-lite links against AOSP libc++, - # while Cronet links against its own libc++ from Chromium. Therefore we cannot - # use the AOSP protobuf library - we have to use the Chromium one. - protoc_gn_target_name = gn.get_target( - '//third_party/protobuf:protoc__toolchain_clang').name - return label_to_module_name(protoc_gn_target_name) + # Note we use Chromium's protoc, not AOSP's. AOSP protoc does not work for us + # because that would require us to link against AOSP's protobuf C++ runtime + # library as well (libprotobuf-cpp-lite) as the generated code is coupled to + # the runtime library. Problem is, the protobuf C++ runtime library uses the + # C++ STL extensively in its public API (e.g. public functions taking + # std::string). Because libc++ does not guarantee ABI compatibility, this in + # turn means that both the producer (libprotobuf-cpp-lite) and the consumer + # (Cronet) of the API must link against the same libc++. Unfortunately that is + # not currently the case - libprotobuf-cpp-lite links against AOSP libc++, + # while Cronet links against its own libc++ from Chromium. Therefore we cannot + # use the AOSP protobuf library - we have to use the Chromium one. + protoc_gn_target_name = gn.get_target( + '//third_party/protobuf:protoc__toolchain_clang').name + return label_to_module_name(protoc_gn_target_name) def create_rust_cxx_modules(blueprint, gn, target, is_test_target): - """Generate genrules for a CXX GN target + """Generate genrules for a CXX GN target GN actions are used to dynamically generate files during the build. The Soong equivalent is a genrule. Currently, Chromium GN targets generates @@ -1255,51 +1265,54 @@ The source and headers genrule modules. """ - def _find_cxx_bridge_binary(deps: Set[str]) -> str: - for dep in deps: - if re.search( - r"^//third_party/rust/cxxbridge_cmd/v.*:cxxbridge__toolchain.*(__testing)?$", - dep): - return dep - raise Exception( - f"Failed to find a dependency on cxxbridge host binary! Target name: {target.name}, deps: {deps}", - ) + def _find_cxx_bridge_binary(deps: Set[str]) -> str: + for dep in deps: + if re.search( + r"^//third_party/rust/cxxbridge_cmd/v.*:cxxbridge__toolchain.*(__testing)?$", + dep): + return dep + raise Exception( + f"Failed to find a dependency on cxxbridge host binary! Target name: {target.name}, deps: {deps}", + ) - cxx_bridge_module_name = create_modules_from_target( - blueprint, gn, _find_cxx_bridge_binary(target.deps), target.type, - is_test_target)[0].name - header_genrule = Module("cc_genrule", - label_to_module_name(target.name) + "_header", - target.name) - header_genrule.tools = {cxx_bridge_module_name} - header_genrule.cmd = f"$(location {cxx_bridge_module_name}) $(in) --header > $(out)" - header_genrule.srcs = {gn_utils.label_to_path(src) for src in target.sources} - # The output of the cc_genrule is the input + ".h" suffix, this is because - # the input to a CXX genrule is just one source file. - header_genrule.out = { - f"{gn_utils.label_to_path(out)}.h" - for out in target.sources - } + cxx_bridge_module_name = create_modules_from_target( + blueprint, gn, _find_cxx_bridge_binary(target.deps), target.type, + is_test_target)[0].name + header_genrule = Module("cc_genrule", + label_to_module_name(target.name) + "_header", + target.name) + header_genrule.tools = {cxx_bridge_module_name} + header_genrule.cmd = f"$(location {cxx_bridge_module_name}) $(in) --header > $(out)" + header_genrule.srcs = { + gn_utils.label_to_path(src) + for src in target.sources + } + # The output of the cc_genrule is the input + ".h" suffix, this is because + # the input to a CXX genrule is just one source file. + header_genrule.out = { + f"{gn_utils.label_to_path(out)}.h" + for out in target.sources + } - cc_genrule = Module("cc_genrule", label_to_module_name(target.name), - target.name) - cc_genrule.tools = {cxx_bridge_module_name} - cc_genrule.cmd = f"$(location {cxx_bridge_module_name}) $(in) > $(out)" - cc_genrule.srcs = {gn_utils.label_to_path(src) for src in target.sources} - cc_genrule.genrule_srcs = {f":{cc_genrule.name}"} - # The output of the cc_genrule is the input + ".cc" suffix, this is because - # the input to a CXX genrule is just one source file. - cc_genrule.out = { - f"{gn_utils.label_to_path(out)}.cc" - for out in target.sources - } + cc_genrule = Module("cc_genrule", label_to_module_name(target.name), + target.name) + cc_genrule.tools = {cxx_bridge_module_name} + cc_genrule.cmd = f"$(location {cxx_bridge_module_name}) $(in) > $(out)" + cc_genrule.srcs = {gn_utils.label_to_path(src) for src in target.sources} + cc_genrule.genrule_srcs = {f":{cc_genrule.name}"} + # The output of the cc_genrule is the input + ".cc" suffix, this is because + # the input to a CXX genrule is just one source file. + cc_genrule.out = { + f"{gn_utils.label_to_path(out)}.cc" + for out in target.sources + } - cc_genrule.genrule_headers.add(header_genrule.name) - return (header_genrule, cc_genrule) + cc_genrule.genrule_headers.add(header_genrule.name) + return (header_genrule, cc_genrule) def create_proto_modules(blueprint, gn, target, is_test_target): - """Generate genrules for a proto GN target. + """Generate genrules for a proto GN target. GN actions are used to dynamically generate files during the build. The Soong equivalent is a genrule. This function turns a specific kind of @@ -1313,893 +1326,919 @@ Returns: The .h and .cc genrule modules. """ - assert (target.type == 'proto_library') + assert (target.type == 'proto_library') - if any(output.endswith('.descriptor') for output in target.outputs): - # One example of a proto descriptor generator target is: - # //base/tracing/protos:chrome_track_event_gen - # These targets require special logic since they generate a descriptor file - # instead of C++ code. But it looks like Cronet works just fine without - # them, so let's just ignore them to avoid the unnecessary complexity. - return () + if any(output.endswith('.descriptor') for output in target.outputs): + # One example of a proto descriptor generator target is: + # //base/tracing/protos:chrome_track_event_gen + # These targets require special logic since they generate a descriptor file + # instead of C++ code. But it looks like Cronet works just fine without + # them, so let's just ignore them to avoid the unnecessary complexity. + return () - # TODO: proto modules being treated as "special snowflakes" instead of just - # like any other action is doing more harm than good - it's weirdly - # inconsistent and we end up missing out on concepts like cross-arch merging - # and the action sanitizer arg handling framework. We should rewrite this - # proto logic to be similar to how we handle any other GN action. + # TODO: proto modules being treated as "special snowflakes" instead of just + # like any other action is doing more harm than good - it's weirdly + # inconsistent and we end up missing out on concepts like cross-arch merging + # and the action sanitizer arg handling framework. We should rewrite this + # proto logic to be similar to how we handle any other GN action. - # Retrieves the value of one of the command line arguments on the GN action, - # or None if not found. The value is filtered through `sanitize()` if - # provided. This function asserts that the sanitized value is the same across - # all archs. - def get_value_arg(arg_name, sanitize=None): - arch_values = set() - for arch in target.arch.values(): - args = arch.args - if not args: - continue - arg_count = args.count(arg_name) - if arg_count == 0: - arch_values.add(None) - continue - assert arg_count == 1, (arg_name, target.name, arch_name) - value_index = args.index(arg_name) + 1 - assert (value_index < len(args)), (arg_name, target.name, arch_name) - arch_value = args[value_index] - if sanitize is not None: - arch_value = sanitize(arch_value) - arch_values.add(arch_value) - assert len(arch_values) == 1, (target.name, arg_name, arch_values) - (single_value, ) = arch_values - return single_value + # Retrieves the value of one of the command line arguments on the GN action, + # or None if not found. The value is filtered through `sanitize()` if + # provided. This function asserts that the sanitized value is the same across + # all archs. + def get_value_arg(arg_name, sanitize=None): + arch_values = set() + for arch in target.arch.values(): + args = arch.args + if not args: + continue + arg_count = args.count(arg_name) + if arg_count == 0: + arch_values.add(None) + continue + assert arg_count == 1, (arg_name, target.name, arch_name) + value_index = args.index(arg_name) + 1 + assert (value_index + < len(args)), (arg_name, target.name, arch_name) + arch_value = args[value_index] + if sanitize is not None: + arch_value = sanitize(arch_value) + arch_values.add(arch_value) + assert len(arch_values) == 1, (target.name, arg_name, arch_values) + (single_value, ) = arch_values + return single_value - protoc_module_name = get_protoc_module_name(gn) + (gn_utils.TESTING_SUFFIX - if is_test_target else '') - # Bring in any executable binary dependencies. Typically these would be protoc - # plugins (more on plugins below). - tools = {protoc_module_name} | { - dep_module.name - for dep_modules in (create_modules_from_target( - blueprint, gn, dep, target.type, is_test_target) - for dep in target.deps) - for dep_module in dep_modules if dep_module.type.endswith('_binary') - } - plugin = get_value_arg("--plugin") - cpp_out_dir = get_value_arg( - '--cc-out-dir' if plugin is None else '--plugin-out-dir', - # Depending on the arch, sometimes the out dir starts with "gen/", sometimes - # it starts with "clang_x64/gen/". We need to remove that prefix. - sanitize=lambda value: re.sub('^([^/]+/)?gen/', '', value)) - assert cpp_out_dir is not None, target.name - absolute_cpp_out_dir = f'$(genDir)/{cpp_out_dir}/' - # We need to keep these module names short because the modules end up in - # `generated_headers` which propagate throughout the build graph. If the names - # are too long we can easily end up with long lists of generated headers with - # long names, which in turn trigger "argument list too long" errors due to the - # sheer size of `-I` include dir parameter lists being passed to the C++ - # compiler. - target_module_name = label_to_module_name(target.name, short=True) + protoc_module_name = get_protoc_module_name(gn) + ( + gn_utils.TESTING_SUFFIX if is_test_target else '') + # Bring in any executable binary dependencies. Typically these would be protoc + # plugins (more on plugins below). + tools = {protoc_module_name} | { + dep_module.name + for dep_modules in (create_modules_from_target( + blueprint, gn, dep, target.type, is_test_target) + for dep in target.deps) + for dep_module in dep_modules if dep_module.type.endswith('_binary') + } + plugin = get_value_arg("--plugin") + cpp_out_dir = get_value_arg( + '--cc-out-dir' if plugin is None else '--plugin-out-dir', + # Depending on the arch, sometimes the out dir starts with "gen/", sometimes + # it starts with "clang_x64/gen/". We need to remove that prefix. + sanitize=lambda value: re.sub('^([^/]+/)?gen/', '', value)) + assert cpp_out_dir is not None, target.name + absolute_cpp_out_dir = f'$(genDir)/{cpp_out_dir}/' + # We need to keep these module names short because the modules end up in + # `generated_headers` which propagate throughout the build graph. If the names + # are too long we can easily end up with long lists of generated headers with + # long names, which in turn trigger "argument list too long" errors due to the + # sheer size of `-I` include dir parameter lists being passed to the C++ + # compiler. + target_module_name = label_to_module_name(target.name, short=True) - # In GN builds the proto path is always relative to the output directory - # (out/tmp.xxx). - cmd = ['$(location %s)' % protoc_module_name] - cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)] + # In GN builds the proto path is always relative to the output directory + # (out/tmp.xxx). + cmd = ['$(location %s)' % protoc_module_name] + cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)] - sorted_proto_paths = sorted(target.proto_paths) - for proto_path in sorted_proto_paths: - cmd += [f'--proto_path={tree_path}/{proto_path}'] - if buildtools_protobuf_src in sorted_proto_paths: - cmd += ['--proto_path=%s' % android_protobuf_src] + sorted_proto_paths = sorted(target.proto_paths) + for proto_path in sorted_proto_paths: + cmd += [f'--proto_path={tree_path}/{proto_path}'] + if buildtools_protobuf_src in sorted_proto_paths: + cmd += ['--proto_path=%s' % android_protobuf_src] - sources = {gn_utils.label_to_path(src) for src in target.sources} - absolute_sources = sorted( - [f"external/cronet/{IMPORT_CHANNEL}/{src}" for src in sources]) + sources = {gn_utils.label_to_path(src) for src in target.sources} + absolute_sources = sorted( + [f"external/cronet/{IMPORT_CHANNEL}/{src}" for src in sources]) - # We create two genrules for each proto target: one for the headers and - # another for the sources. This is because the module that depends on the - # generated files needs to declare two different types of dependencies -- - # source files in 'srcs' and headers in 'generated_headers' -- and it's not - # valid to generate .h files from a source dependency and vice versa. - source_module_name = target_module_name - source_module = Module('cc_genrule', source_module_name, target.name) - blueprint.add_module(source_module) - source_module.srcs.update(sources) + # We create two genrules for each proto target: one for the headers and + # another for the sources. This is because the module that depends on the + # generated files needs to declare two different types of dependencies -- + # source files in 'srcs' and headers in 'generated_headers' -- and it's not + # valid to generate .h files from a source dependency and vice versa. + source_module_name = target_module_name + source_module = Module('cc_genrule', source_module_name, target.name) + blueprint.add_module(source_module) + source_module.srcs.update(sources) - header_module = Module('cc_genrule', source_module_name + '_h', target.name) - blueprint.add_module(header_module) - header_module.srcs = set(source_module.srcs) + header_module = Module('cc_genrule', source_module_name + '_h', + target.name) + blueprint.add_module(header_module) + header_module.srcs = set(source_module.srcs) - header_module.export_include_dirs = {'.', 'protos'} - # Since the .cc file and .h get created by a different gerule target, they - # are not put in the same intermediate path, so local includes do not work - # without explictily exporting the include dir. - header_module.export_include_dirs.add(cpp_out_dir) + header_module.export_include_dirs = {'.', 'protos'} + # Since the .cc file and .h get created by a different gerule target, they + # are not put in the same intermediate path, so local includes do not work + # without explictily exporting the include dir. + header_module.export_include_dirs.add(cpp_out_dir) - # This function does not return header_module so setting apex_available attribute here. - header_module.apex_available.add(tethering_apex) + # This function does not return header_module so setting apex_available attribute here. + header_module.apex_available.add(tethering_apex) - source_module.genrule_srcs.add(':' + source_module.name) - source_module.genrule_headers.add(header_module.name) + source_module.genrule_srcs.add(':' + source_module.name) + source_module.genrule_headers.add(header_module.name) - cmd += [f'--cpp_out=lite=true:{absolute_cpp_out_dir}'] + cmd += [f'--cpp_out=lite=true:{absolute_cpp_out_dir}'] - cmd += absolute_sources + cmd += absolute_sources - # protoc supports "plugins", which are executable binaries it can call into - # to customize code generation. In Chromium this feature is seldom used, but - # there is one notable exception: Perfetto, which uses custom plugins all - # over the place ("protozero", etc.). - # - # Another thing to keep in mind is the form of the plugin command line - # options is a bit different between protoc and - # //tools/protoc_wrapper/protoc_wrapper.py (which is what the GN action - # calls), which is why we have to rearrange the args somewhat. - # TODO: one could argue that it may be more robust to have the genrule call - # protoc_wrapper.py instead of bypassing it and calling protoc directly. - if plugin is not None: - # The path to the plugin executable is quite different in AOSP vs. Chromium. - # In AOSP, we assume the plugin is the only tool dependency (besides protoc - # itself) and deduce the path from there. - plugin_modules = tools - {protoc_module_name} - assert len(plugin_modules) == 1, target.name - (plugin_module, ) = plugin_modules - cmd += [f"--plugin=protoc-gen-plugin=$(location {plugin_module})"] - plugin_options = get_value_arg("--plugin-options") - if plugin_options is not None: - cmd += [f"--plugin_out={plugin_options}:{absolute_cpp_out_dir}"] + # protoc supports "plugins", which are executable binaries it can call into + # to customize code generation. In Chromium this feature is seldom used, but + # there is one notable exception: Perfetto, which uses custom plugins all + # over the place ("protozero", etc.). + # + # Another thing to keep in mind is the form of the plugin command line + # options is a bit different between protoc and + # //tools/protoc_wrapper/protoc_wrapper.py (which is what the GN action + # calls), which is why we have to rearrange the args somewhat. + # TODO: one could argue that it may be more robust to have the genrule call + # protoc_wrapper.py instead of bypassing it and calling protoc directly. + if plugin is not None: + # The path to the plugin executable is quite different in AOSP vs. Chromium. + # In AOSP, we assume the plugin is the only tool dependency (besides protoc + # itself) and deduce the path from there. + plugin_modules = tools - {protoc_module_name} + assert len(plugin_modules) == 1, target.name + (plugin_module, ) = plugin_modules + cmd += [f"--plugin=protoc-gen-plugin=$(location {plugin_module})"] + plugin_options = get_value_arg("--plugin-options") + if plugin_options is not None: + cmd += [f"--plugin_out={plugin_options}:{absolute_cpp_out_dir}"] - source_module.cmd = cmd - header_module.cmd = source_module.cmd - source_module.tools = tools - header_module.tools = tools + source_module.cmd = cmd + header_module.cmd = source_module.cmd + source_module.tools = tools + header_module.tools = tools - source_module.out.update(output for output in target.outputs - if output.endswith('.cc')) - header_module.out.update(output for output in target.outputs - if output.endswith('.h')) + source_module.out.update(output for output in target.outputs + if output.endswith('.cc')) + header_module.out.update(output for output in target.outputs + if output.endswith('.h')) - # This has proto files that will be used for reference resolution - # but not compiled into cpp files. These additional sources has no output. - proto_data_sources = sorted([ - gn_utils.label_to_path(proto_src) for proto_src in target.inputs - if proto_src.endswith(".proto") - ]) - source_module.srcs.update(proto_data_sources) - header_module.srcs.update(proto_data_sources) + # This has proto files that will be used for reference resolution + # but not compiled into cpp files. These additional sources has no output. + proto_data_sources = sorted([ + gn_utils.label_to_path(proto_src) for proto_src in target.inputs + if proto_src.endswith(".proto") + ]) + source_module.srcs.update(proto_data_sources) + header_module.srcs.update(proto_data_sources) - # Allow rebasing proto genrules according to their proper path. - source_module.allow_rebasing = True - header_module.allow_rebasing = True - header_module.build_file_path = target.build_file_path - source_module.build_file_path = target.build_file_path - return (header_module, source_module) + # Allow rebasing proto genrules according to their proper path. + source_module.allow_rebasing = True + header_module.allow_rebasing = True + header_module.build_file_path = target.build_file_path + source_module.build_file_path = target.build_file_path + return (header_module, source_module) def create_gcc_preprocess_modules(blueprint, target): - # gcc_preprocess.py internally execute host gcc which is not allowed in genrule. - # So, this function create multiple modules and realize equivalent processing - assert (len(target.sources) == 1) - source = list(target.sources)[0] - assert (Path(source).suffix == '.template') - stem = Path(source).stem + # gcc_preprocess.py internally execute host gcc which is not allowed in genrule. + # So, this function create multiple modules and realize equivalent processing + assert (len(target.sources) == 1) + source = list(target.sources)[0] + assert (Path(source).suffix == '.template') + stem = Path(source).stem - bp_module_name = label_to_module_name(target.name) + bp_module_name = label_to_module_name(target.name) - # Rename .template to .cc since cc_preprocess_no_configuration does - # not accept .template file as srcs - rename_module = Module('genrule', bp_module_name + '_rename', target.name) - rename_module.srcs.add(gn_utils.label_to_path(source)) - rename_module.out.add(stem + '.cc') - rename_module.cmd = 'cp $(in) $(out)' - blueprint.add_module(rename_module) + # Rename .template to .cc since cc_preprocess_no_configuration does + # not accept .template file as srcs + rename_module = Module('genrule', bp_module_name + '_rename', target.name) + rename_module.srcs.add(gn_utils.label_to_path(source)) + rename_module.out.add(stem + '.cc') + rename_module.cmd = 'cp $(in) $(out)' + blueprint.add_module(rename_module) - # Preprocess template file and generates java file - preprocess_module = Module('cc_preprocess_no_configuration', - bp_module_name + '_preprocess', target.name) - # -E: stop after preprocessing. - # -P: disable line markers, i.e. '#line 309' - preprocess_module.cflags.extend(['-E', '-P', '-DANDROID']) - preprocess_module.srcs.add(':' + rename_module.name) - defines = [ - '-D' + target.args[i + 1] for i, arg in enumerate(target.args) - if arg == '--define' - ] - preprocess_module.cflags.extend(defines) - blueprint.add_module(preprocess_module) + # Preprocess template file and generates java file + preprocess_module = Module('cc_preprocess_no_configuration', + bp_module_name + '_preprocess', target.name) + # -E: stop after preprocessing. + # -P: disable line markers, i.e. '#line 309' + preprocess_module.cflags.extend(['-E', '-P', '-DANDROID']) + preprocess_module.srcs.add(':' + rename_module.name) + defines = [ + '-D' + target.args[i + 1] for i, arg in enumerate(target.args) + if arg == '--define' + ] + preprocess_module.cflags.extend(defines) + blueprint.add_module(preprocess_module) - # Generates srcjar using soong_zip - module = Module('genrule', bp_module_name, target.name) - module.srcs.add(':' + preprocess_module.name) - module.out.add(stem + '.srcjar') - module.cmd = [ - f'cp $(in) $(genDir)/{stem}.java &&', - f'$(location soong_zip) -o $(out) -srcjar -C $(genDir) -f $(genDir)/{stem}.java' - ] - module.tools.add('soong_zip') - blueprint.add_module(module) - return module + # Generates srcjar using soong_zip + module = Module('genrule', bp_module_name, target.name) + module.srcs.add(':' + preprocess_module.name) + module.out.add(stem + '.srcjar') + module.cmd = [ + f'cp $(in) $(genDir)/{stem}.java &&', + f'$(location soong_zip) -o $(out) -srcjar -C $(genDir) -f $(genDir)/{stem}.java' + ] + module.tools.add('soong_zip') + blueprint.add_module(module) + return module class BaseActionSanitizer(): - def __init__(self, target, arch): - # Just to be on the safe side, create a deep-copy. - self.target = copy.deepcopy(target) - if arch: - # Merge arch specific attributes - self.target.sources |= arch.sources - self.target.inputs |= arch.inputs - self.target.outputs |= arch.outputs - self.target.script = self.target.script or arch.script - self.target.args = self.target.args or arch.args - self.target.response_file_contents = \ - self.target.response_file_contents or arch.response_file_contents - self.target.args = self._normalize_args() + def __init__(self, target, arch): + # Just to be on the safe side, create a deep-copy. + self.target = copy.deepcopy(target) + if arch: + # Merge arch specific attributes + self.target.sources |= arch.sources + self.target.inputs |= arch.inputs + self.target.outputs |= arch.outputs + self.target.script = self.target.script or arch.script + self.target.args = self.target.args or arch.args + self.target.response_file_contents = \ + self.target.response_file_contents or arch.response_file_contents + self.target.args = self._normalize_args() - def get_name(self): - return label_to_module_name(self.target.name) + def get_name(self): + return label_to_module_name(self.target.name) - def _normalize_args(self): - # Convert ['--param=value'] to ['--param', 'value'] for consistency. - normalized_args = [] - for arg in self.target.args: - if arg.startswith('-'): - normalized_args.extend(arg.split('=')) - else: - normalized_args.append(arg) - return normalized_args + def _normalize_args(self): + # Convert ['--param=value'] to ['--param', 'value'] for consistency. + normalized_args = [] + for arg in self.target.args: + if arg.startswith('-'): + normalized_args.extend(arg.split('=')) + else: + normalized_args.append(arg) + return normalized_args - # There are three types of args: - # - flags (--flag) - # - value args (--arg value) - # - list args (--arg value1 --arg value2) - # value args have exactly one arg value pair and list args have one or more arg value pairs. - # Note that the set of list args contains the set of value args. - # This is because list and value args are identical when the list args has only one arg value pair - # Some functions provide special implementations for each type, while others - # work on all of them. - def _has_arg(self, arg): - return arg in self.target.args + # There are three types of args: + # - flags (--flag) + # - value args (--arg value) + # - list args (--arg value1 --arg value2) + # value args have exactly one arg value pair and list args have one or more arg value pairs. + # Note that the set of list args contains the set of value args. + # This is because list and value args are identical when the list args has only one arg value pair + # Some functions provide special implementations for each type, while others + # work on all of them. + def _has_arg(self, arg): + return arg in self.target.args - def _get_arg_indices(self, target_arg): - return [i for i, arg in enumerate(self.target.args) if arg == target_arg] + def _get_arg_indices(self, target_arg): + return [ + i for i, arg in enumerate(self.target.args) if arg == target_arg + ] - # Whether an arg value pair appears once or more times - def _is_list_arg(self, arg): - indices = self._get_arg_indices(arg) - return len(indices) > 0 and all(not self.target.args[i + 1].startswith('--') - for i in indices) + # Whether an arg value pair appears once or more times + def _is_list_arg(self, arg): + indices = self._get_arg_indices(arg) + return len(indices) > 0 and all( + not self.target.args[i + 1].startswith('--') for i in indices) - def _update_list_arg(self, arg, func, throw_if_absent=True): - if self._should_fail_silently(arg, throw_if_absent): - return - assert (self._is_list_arg(arg)) - indices = self._get_arg_indices(arg) - for i in indices: - self._set_arg_at(i + 1, func(self.target.args[i + 1])) + def _update_list_arg(self, arg, func, throw_if_absent=True): + if self._should_fail_silently(arg, throw_if_absent): + return + assert (self._is_list_arg(arg)) + indices = self._get_arg_indices(arg) + for i in indices: + self._set_arg_at(i + 1, func(self.target.args[i + 1])) - # Whether an arg value pair appears exactly once - def _is_value_arg(self, arg): - return operator.countOf(self.target.args, - arg) == 1 and self._is_list_arg(arg) + # Whether an arg value pair appears exactly once + def _is_value_arg(self, arg): + return operator.countOf(self.target.args, + arg) == 1 and self._is_list_arg(arg) - def _get_value_arg(self, arg): - assert (self._is_value_arg(arg)) - i = self.target.args.index(arg) - return self.target.args[i + 1] + def _get_value_arg(self, arg): + assert (self._is_value_arg(arg)) + i = self.target.args.index(arg) + return self.target.args[i + 1] - # used to check whether a function call should cause an error when an arg is - # missing. - def _should_fail_silently(self, arg, throw_if_absent): - return not throw_if_absent and not self._has_arg(arg) + # used to check whether a function call should cause an error when an arg is + # missing. + def _should_fail_silently(self, arg, throw_if_absent): + return not throw_if_absent and not self._has_arg(arg) - def _set_value_arg(self, arg, value, throw_if_absent=True): - if self._should_fail_silently(arg, throw_if_absent): - return - assert (self._is_value_arg(arg)) - i = self.target.args.index(arg) - self.target.args[i + 1] = value + def _set_value_arg(self, arg, value, throw_if_absent=True): + if self._should_fail_silently(arg, throw_if_absent): + return + assert (self._is_value_arg(arg)) + i = self.target.args.index(arg) + self.target.args[i + 1] = value - def _update_value_arg(self, arg, func, throw_if_absent=True): - if self._should_fail_silently(arg, throw_if_absent): - return - self._set_value_arg(arg, func(self._get_value_arg(arg))) + def _update_value_arg(self, arg, func, throw_if_absent=True): + if self._should_fail_silently(arg, throw_if_absent): + return + self._set_value_arg(arg, func(self._get_value_arg(arg))) - def _set_arg_at(self, position, value): - self.target.args[position] = value + def _set_arg_at(self, position, value): + self.target.args[position] = value - def _update_arg_at(self, position, func): - self.target.args[position] = func(self.target.args[position]) + def _update_arg_at(self, position, func): + self.target.args[position] = func(self.target.args[position]) - def _delete_value_arg(self, arg, throw_if_absent=True): - if self._should_fail_silently(arg, throw_if_absent): - return - assert (self._is_value_arg(arg)) - i = self.target.args.index(arg) - self.target.args.pop(i) - self.target.args.pop(i) + def _delete_value_arg(self, arg, throw_if_absent=True): + if self._should_fail_silently(arg, throw_if_absent): + return + assert (self._is_value_arg(arg)) + i = self.target.args.index(arg) + self.target.args.pop(i) + self.target.args.pop(i) - def _append_arg(self, arg, value): - self.target.args.append(arg) - self.target.args.append(value) + def _append_arg(self, arg, value): + self.target.args.append(arg) + self.target.args.append(value) - def _sanitize_filepath_with_location_tag(self, arg): - if arg.startswith('../../'): - arg = self._sanitize_filepath(arg) - arg = self._add_location_tag(arg) - return arg + def _sanitize_filepath_with_location_tag(self, arg): + if arg.startswith('../../'): + arg = self._sanitize_filepath(arg) + arg = self._add_location_tag(arg) + return arg - # wrap filename in location tag. - def _add_location_tag(self, filename): - return '$(location %s)' % filename + # wrap filename in location tag. + def _add_location_tag(self, filename): + return '$(location %s)' % filename - # applies common directory transformation that *should* be universally applicable. - # TODO: verify if it actually *is* universally applicable. - def _sanitize_filepath(self, filepath): - # Careful, order matters! - # delete all leading ../ - filepath = re.sub('^(\.\./)+', '', filepath) - filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath) - filepath = re.sub('^gen', '$(genDir)', filepath) - return filepath + # applies common directory transformation that *should* be universally applicable. + # TODO: verify if it actually *is* universally applicable. + def _sanitize_filepath(self, filepath): + # Careful, order matters! + # delete all leading ../ + filepath = re.sub('^(\.\./)+', '', filepath) + filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath) + filepath = re.sub('^gen', '$(genDir)', filepath) + return filepath - # Iterate through all the args and apply function - def _update_all_args(self, func): - self.target.args = [func(arg) for arg in self.target.args] + # Iterate through all the args and apply function + def _update_all_args(self, func): + self.target.args = [func(arg) for arg in self.target.args] - def get_pre_cmd(self): - pre_cmd = [] - out_dirs = [ - out[:out.rfind("/")] for out in self.target.outputs if "/" in out - ] - # Sort the list to make the output deterministic. - for out_dir in sorted(set(out_dirs)): - pre_cmd.append("mkdir -p $(genDir)/{} && ".format(out_dir)) - return pre_cmd + def get_pre_cmd(self): + pre_cmd = [] + out_dirs = [ + out[:out.rfind("/")] for out in self.target.outputs if "/" in out + ] + # Sort the list to make the output deterministic. + for out_dir in sorted(set(out_dirs)): + pre_cmd.append("mkdir -p $(genDir)/{} && ".format(out_dir)) + return pre_cmd - def get_base_cmd(self): - # TODO: most sanitizer logic does not really handle "$" characters very - # well, and will likely do the wrong thing if the GN target contains args - # with literal "$" characters in them. Also, if a sanitizer deliberately - # shoves a $() macro in an arg, we still run that through shell quoting, - # which does preserve the "$" but that's mostly luck. We should design - # a better mechanism for handling "$" and $() macros. - return (([f"echo {shlex.quote(self.target.response_file_contents)} |"] - if self.target.response_file_contents else []) + - [f"$(location {gn_utils.label_to_path(self.target.script)})"] + - [shlex.quote(arg) for arg in self.target.args]) + def get_base_cmd(self): + # TODO: most sanitizer logic does not really handle "$" characters very + # well, and will likely do the wrong thing if the GN target contains args + # with literal "$" characters in them. Also, if a sanitizer deliberately + # shoves a $() macro in an arg, we still run that through shell quoting, + # which does preserve the "$" but that's mostly luck. We should design + # a better mechanism for handling "$" and $() macros. + return (([f"echo {shlex.quote(self.target.response_file_contents)} |"] + if self.target.response_file_contents else []) + + [f"$(location {gn_utils.label_to_path(self.target.script)})"] + + [shlex.quote(arg) for arg in self.target.args]) - def get_cmd(self): - # Note: don't be confused by the return type. This function returns a list, - # but the list is *NOT* an argv array, it's a list of lines for Blueprint - # file formatting for cosmetic purposes. The actual command is the list - # elements concatenated together into a single string, which is ultimately - # fed as a shell command at build time. This means what we are returning - # here is expected to have been properly shell-escaped beforehand. - return self.get_pre_cmd() + self.get_base_cmd() + def get_cmd(self): + # Note: don't be confused by the return type. This function returns a list, + # but the list is *NOT* an argv array, it's a list of lines for Blueprint + # file formatting for cosmetic purposes. The actual command is the list + # elements concatenated together into a single string, which is ultimately + # fed as a shell command at build time. This means what we are returning + # here is expected to have been properly shell-escaped beforehand. + return self.get_pre_cmd() + self.get_base_cmd() - def get_outputs(self): - return self.target.outputs + def get_outputs(self): + return self.target.outputs - def get_srcs(self): - # gn treats inputs and sources for actions equally. - # soong only supports source files inside srcs, non-source files are added as - # tool_files dependency. - files = self.target.sources.union(self.target.inputs) - return { - gn_utils.label_to_path(file) - for file in files if is_supported_source_file(file) - } + def get_srcs(self): + # gn treats inputs and sources for actions equally. + # soong only supports source files inside srcs, non-source files are added as + # tool_files dependency. + files = self.target.sources.union(self.target.inputs) + return { + gn_utils.label_to_path(file) + for file in files if is_supported_source_file(file) + } - def get_tools(self): - return set() + def get_tools(self): + return set() - def get_tool_files(self): - # gn treats inputs and sources for actions equally. - # soong only supports source files inside srcs, non-source files are added as - # tool_files dependency. - files = self.target.sources.union(self.target.inputs) - tool_files = { - gn_utils.label_to_path(file) - # Files that starts with "out/" are usually an output of another action. - # This is under the assumption that we generate the desc files in an - # out/ directory usually. - for file in files - if not is_supported_source_file(file) and not file.startswith("//out/") - } - tool_files.add(gn_utils.label_to_path(self.target.script)) - # Make sure there is no duplication between `srcs` and `tool_files` - Soong - # fails with a "multiple locations for label" error otherwise. - tool_files -= self.get_srcs() - return tool_files + def get_tool_files(self): + # gn treats inputs and sources for actions equally. + # soong only supports source files inside srcs, non-source files are added as + # tool_files dependency. + files = self.target.sources.union(self.target.inputs) + tool_files = { + gn_utils.label_to_path(file) + # Files that starts with "out/" are usually an output of another action. + # This is under the assumption that we generate the desc files in an + # out/ directory usually. + for file in files if not is_supported_source_file(file) + and not file.startswith("//out/") + } + tool_files.add(gn_utils.label_to_path(self.target.script)) + # Make sure there is no duplication between `srcs` and `tool_files` - Soong + # fails with a "multiple locations for label" error otherwise. + tool_files -= self.get_srcs() + return tool_files - def _sanitize_args(self): - # Handle passing parameters via response file by piping them into the script - # and reading them from /dev/stdin. + def _sanitize_args(self): + # Handle passing parameters via response file by piping them into the script + # and reading them from /dev/stdin. - use_response_file = gn_utils.RESPONSE_FILE in self.target.args - if use_response_file: - # Replace {{response_file_contents}} with /dev/stdin - self.target.args = [ - '/dev/stdin' if it == gn_utils.RESPONSE_FILE else it - for it in self.target.args - ] + use_response_file = gn_utils.RESPONSE_FILE in self.target.args + if use_response_file: + # Replace {{response_file_contents}} with /dev/stdin + self.target.args = [ + '/dev/stdin' if it == gn_utils.RESPONSE_FILE else it + for it in self.target.args + ] - def _sanitize_inputs(self): - pass + def _sanitize_inputs(self): + pass - def get_deps(self): - return self.target.deps + def get_deps(self): + return self.target.deps - def sanitize(self): - self._sanitize_args() - self._sanitize_inputs() + def sanitize(self): + self._sanitize_args() + self._sanitize_inputs() - # Whether this target generates header files - def is_header_generated(self): - return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs) + # Whether this target generates header files + def is_header_generated(self): + return any( + os.path.splitext(it)[1] == '.h' for it in self.target.outputs) class WriteBuildDateHeaderSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_arg_at(0, '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._set_arg_at(0, '$(out)') + super()._sanitize_args() class WriteGenerateAllowlistFromHistogramsFileSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_value_arg('--output_dir', '.') - self._set_value_arg('--file', '$(out)') - self._update_value_arg('--input', self._sanitize_filepath_with_location_tag) - super()._sanitize_args() + def _sanitize_args(self): + self._set_value_arg('--output_dir', '.') + self._set_value_arg('--file', '$(out)') + self._update_value_arg('--input', + self._sanitize_filepath_with_location_tag) + super()._sanitize_args() class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_value_arg('--gen-dir', '.') - self._set_value_arg('--output', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._set_value_arg('--gen-dir', '.') + self._set_value_arg('--output', '$(out)') + super()._sanitize_args() class PerfettoWriteBuildFlagHeaderSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_value_arg('--out', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._set_value_arg('--out', '$(out)') + super()._sanitize_args() class GnRunBinarySanitizer(BaseActionSanitizer): - def __init__(self, target, arch): - super().__init__(target, arch) - self.binary_to_target = { - "clang_x64/transport_security_state_generator": - f"{MODULE_PREFIX}net_tools_transport_security_state_generator_transport_security_state_generator__toolchain_clang__testing", - } - self.binary = self.binary_to_target[self.target.args[0]] + def __init__(self, target, arch): + super().__init__(target, arch) + self.binary_to_target = { + "clang_x64/transport_security_state_generator": + f"{MODULE_PREFIX}net_tools_transport_security_state_generator_transport_security_state_generator__toolchain_clang__testing", + } + self.binary = self.binary_to_target[self.target.args[0]] - def _replace_gen_with_location_tag(self, arg): - if arg.startswith("gen/"): - return "$(location %s)" % arg.replace("gen/", "") - return arg + def _replace_gen_with_location_tag(self, arg): + if arg.startswith("gen/"): + return "$(location %s)" % arg.replace("gen/", "") + return arg - def _replace_binary(self, arg): - if arg in self.binary_to_target: - return '$(location %s)' % self.binary - return arg + def _replace_binary(self, arg): + if arg in self.binary_to_target: + return '$(location %s)' % self.binary + return arg - def _remove_python_args(self): - self.target.args = [arg for arg in self.target.args if "python3" not in arg] + def _remove_python_args(self): + self.target.args = [ + arg for arg in self.target.args if "python3" not in arg + ] - def _sanitize_args(self): - self._update_all_args(self._sanitize_filepath_with_location_tag) - self._update_all_args(self._replace_gen_with_location_tag) - self._update_all_args(self._replace_binary) - self._remove_python_args() - super()._sanitize_args() + def _sanitize_args(self): + self._update_all_args(self._sanitize_filepath_with_location_tag) + self._update_all_args(self._replace_gen_with_location_tag) + self._update_all_args(self._replace_binary) + self._remove_python_args() + super()._sanitize_args() - def get_tools(self): - tools = super().get_tools() - tools.add(self.binary) - return tools + def get_tools(self): + tools = super().get_tools() + tools.add(self.binary) + return tools - def get_cmd(self): - # Remove the script and use the binary right away - return self.get_pre_cmd() + [shlex.quote(arg) for arg in self.target.args] + def get_cmd(self): + # Remove the script and use the binary right away + return self.get_pre_cmd() + [ + shlex.quote(arg) for arg in self.target.args + ] class JniGeneratorSanitizer(BaseActionSanitizer): - def __init__(self, target, arch, is_test_target): - self.is_test_target = is_test_target - super().__init__(target, arch) + def __init__(self, target, arch, is_test_target): + self.is_test_target = is_test_target + super().__init__(target, arch) - def get_srcs(self): - all_srcs = super().get_srcs() - all_srcs.update({ - gn_utils.label_to_path(file) - for file in self.target.transitive_jni_java_sources - if is_supported_source_file(file) - }) - return set(src for src in all_srcs if src.endswith(".java")) + def get_srcs(self): + all_srcs = super().get_srcs() + all_srcs.update({ + gn_utils.label_to_path(file) + for file in self.target.transitive_jni_java_sources + if is_supported_source_file(file) + }) + return set(src for src in all_srcs if src.endswith(".java")) - def _add_location_tag_to_filepath(self, arg): - if not arg.endswith('.class'): - # --input_file supports both .class specifiers or source files as arguments. - # Only source files need to be wrapped inside a $(location <label>) tag. - arg = self._add_location_tag(arg) - return arg + def _add_location_tag_to_filepath(self, arg): + if not arg.endswith('.class'): + # --input_file supports both .class specifiers or source files as arguments. + # Only source files need to be wrapped inside a $(location <label>) tag. + arg = self._add_location_tag(arg) + return arg - def _sanitize_args(self): - self._set_value_arg('--jar-file', '$(location :current_android_jar)', False) - if self._has_arg('--jar-file'): - self._set_value_arg('--javap', '$(location :javap)') - self._update_value_arg('--srcjar-path', self._sanitize_filepath, False) - self._update_value_arg('--output-dir', self._sanitize_filepath) - self._update_value_arg('--extra-include', self._sanitize_filepath, False) - self._update_value_arg('--placeholder-srcjar-path', self._sanitize_filepath, - False) - self._update_list_arg('--input-file', self._sanitize_filepath) - self._update_list_arg('--input-file', self._add_location_tag_to_filepath) + def _sanitize_args(self): + self._set_value_arg('--jar-file', '$(location :current_android_jar)', + False) + if self._has_arg('--jar-file'): + self._set_value_arg('--javap', '$(location :javap)') + self._update_value_arg('--srcjar-path', self._sanitize_filepath, False) + self._update_value_arg('--output-dir', self._sanitize_filepath) + self._update_value_arg('--extra-include', self._sanitize_filepath, + False) + self._update_value_arg('--placeholder-srcjar-path', + self._sanitize_filepath, False) + self._update_list_arg('--input-file', self._sanitize_filepath) + self._update_list_arg('--input-file', + self._add_location_tag_to_filepath) - self._delete_value_arg('--package-prefix', throw_if_absent=False) - self._delete_value_arg('--package-prefix-filter', throw_if_absent=False) - if not self.is_test_target and not self._has_arg('--jar-file'): - # Don't jarjar classes that already exists within the java SDK. The headers generated - # from those genrule can simply call into the original class as it exists outside - # of cronet's jar. - # Only jarjar platform code - self._append_arg('--package-prefix', 'android.net.http.internal') - super()._sanitize_args() + self._delete_value_arg('--package-prefix', throw_if_absent=False) + self._delete_value_arg('--package-prefix-filter', + throw_if_absent=False) + if not self.is_test_target and not self._has_arg('--jar-file'): + # Don't jarjar classes that already exists within the java SDK. The headers generated + # from those genrule can simply call into the original class as it exists outside + # of cronet's jar. + # Only jarjar platform code + self._append_arg('--package-prefix', 'android.net.http.internal') + super()._sanitize_args() - def get_outputs(self): - outputs = set() - for out in super().get_outputs(): - # fix target.output directory to match #include statements. - outputs.add(re.sub('^jni_headers/', '', out)) - return outputs + def get_outputs(self): + outputs = set() + for out in super().get_outputs(): + # fix target.output directory to match #include statements. + outputs.add(re.sub('^jni_headers/', '', out)) + return outputs - def get_tool_files(self): - tool_files = super().get_tool_files() + def get_tool_files(self): + tool_files = super().get_tool_files() - # Filter android.jar and add :current_android_jar - tool_files = { - file if not file.endswith('android.jar') else ':current_android_jar' - for file in tool_files - } - # Filter bin/javap - tool_files = {file for file in tool_files if not file.endswith('bin/javap')} + # Filter android.jar and add :current_android_jar + tool_files = { + file + if not file.endswith('android.jar') else ':current_android_jar' + for file in tool_files + } + # Filter bin/javap + tool_files = { + file + for file in tool_files if not file.endswith('bin/javap') + } - # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made - # its way to AOSP - # Files not specified in anywhere but jni_generator.py imports this file - tool_files.add('third_party/jni_zero/codegen/header_common.py') - tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py') + # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made + # its way to AOSP + # Files not specified in anywhere but jni_generator.py imports this file + tool_files.add('third_party/jni_zero/codegen/header_common.py') + tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py') - return tool_files + return tool_files - def get_tools(self): - tools = super().get_tools() - if self._has_arg('--jar-file'): - tools.add(":javap") - return tools + def get_tools(self): + tools = super().get_tools() + if self._has_arg('--jar-file'): + tools.add(":javap") + return tools class JavaJniGeneratorSanitizer(JniGeneratorSanitizer): - def __init__(self, target, arch, is_test_target): - self.is_test_target = is_test_target - super().__init__(target, arch, is_test_target) + def __init__(self, target, arch, is_test_target): + self.is_test_target = is_test_target + super().__init__(target, arch, is_test_target) - def get_outputs(self): - # fix target.output directory to match #include statements. - outputs = { - re.sub('^jni_headers/', '', out) - for out in super().get_outputs() - } - self.target.outputs = [out for out in outputs if out.endswith(".srcjar")] - return outputs + def get_outputs(self): + # fix target.output directory to match #include statements. + outputs = { + re.sub('^jni_headers/', '', out) + for out in super().get_outputs() + } + self.target.outputs = [ + out for out in outputs if out.endswith(".srcjar") + ] + return outputs - def get_deps(self): - return {} + def get_deps(self): + return {} - def get_name(self): - name = super().get_name() + "__java" - return name + def get_name(self): + name = super().get_name() + "__java" + return name class JniRegistrationGeneratorSanitizer(BaseActionSanitizer): - def __init__(self, target, arch, is_test_target): - self.is_test_target = is_test_target - super().__init__(target, arch) + def __init__(self, target, arch, is_test_target): + self.is_test_target = is_test_target + super().__init__(target, arch) - def get_srcs(self): - all_srcs = super().get_srcs() - all_srcs.update({ - gn_utils.label_to_path(file) - for file in self.target.transitive_jni_java_sources - if is_supported_source_file(file) - }) - return set(src for src in all_srcs if src.endswith(".java")) + def get_srcs(self): + all_srcs = super().get_srcs() + all_srcs.update({ + gn_utils.label_to_path(file) + for file in self.target.transitive_jni_java_sources + if is_supported_source_file(file) + }) + return set(src for src in all_srcs if src.endswith(".java")) - def _sanitize_inputs(self): - self.target.inputs = [ - file for file in self.target.inputs if not file.startswith('//out/') - ] + def _sanitize_inputs(self): + self.target.inputs = [ + file for file in self.target.inputs + if not file.startswith('//out/') + ] - def get_outputs(self): - outputs = set() - for out in super().get_outputs(): - # placeholder.srcjar contains empty placeholder classes used to compile generated java files - # without any other deps. This is not used in aosp. - if out.endswith("_placeholder.srcjar"): - continue - # fix target.output directory to match #include statements. - outputs.add(re.sub('^jni_headers/', '', out)) - return outputs + def get_outputs(self): + outputs = set() + for out in super().get_outputs(): + # placeholder.srcjar contains empty placeholder classes used to compile generated java files + # without any other deps. This is not used in aosp. + if out.endswith("_placeholder.srcjar"): + continue + # fix target.output directory to match #include statements. + outputs.add(re.sub('^jni_headers/', '', out)) + return outputs - def _sanitize_args(self): - self._update_value_arg('--depfile', self._sanitize_filepath) - self._update_value_arg('--srcjar-path', self._sanitize_filepath) - self._update_value_arg('--header-path', self._sanitize_filepath) - self._update_value_arg('--placeholder-srcjar-path', self._sanitize_filepath, - False) - self._delete_value_arg('--depfile', False) - self._set_value_arg('--java-sources-file', '$(genDir)/java.sources') + def _sanitize_args(self): + self._update_value_arg('--depfile', self._sanitize_filepath) + self._update_value_arg('--srcjar-path', self._sanitize_filepath) + self._update_value_arg('--header-path', self._sanitize_filepath) + self._update_value_arg('--placeholder-srcjar-path', + self._sanitize_filepath, False) + self._delete_value_arg('--depfile', False) + self._set_value_arg('--java-sources-file', '$(genDir)/java.sources') - self._delete_value_arg('--package-prefix', throw_if_absent=False) - self._delete_value_arg('--package-prefix-filter', throw_if_absent=False) - if not self.is_test_target: - # Only jarjar platform code - self._append_arg('--package-prefix', 'android.net.http.internal') - super()._sanitize_args() + self._delete_value_arg('--package-prefix', throw_if_absent=False) + self._delete_value_arg('--package-prefix-filter', + throw_if_absent=False) + if not self.is_test_target: + # Only jarjar platform code + self._append_arg('--package-prefix', 'android.net.http.internal') + super()._sanitize_args() - def get_cmd(self): - base_cmd = super().get_base_cmd() - # Path in the original sources file does not work in genrule. - # So creating sources file in cmd based on the srcs of this target. - # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files - # whose path startswith(..) - base_cmd = ([ - "current_dir=`basename \\`pwd\\``;", - "for f in $(in);", - "do", - "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources;", - "done;", - ] + - # jni_registration_generator.py doesn't work with python2 - [f"python3 {base_cmd[0]}"] + base_cmd[1:]) + def get_cmd(self): + base_cmd = super().get_base_cmd() + # Path in the original sources file does not work in genrule. + # So creating sources file in cmd based on the srcs of this target. + # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files + # whose path startswith(..) + base_cmd = ([ + "current_dir=`basename \\`pwd\\``;", + "for f in $(in);", + "do", + "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources;", + "done;", + ] + + # jni_registration_generator.py doesn't work with python2 + [f"python3 {base_cmd[0]}"] + base_cmd[1:]) - return self.get_pre_cmd() + base_cmd + return self.get_pre_cmd() + base_cmd - def get_tool_files(self): - tool_files = super().get_tool_files() - # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made - # its way to AOSP - # Files not specified in anywhere but jni_generator.py imports this file - tool_files.add('third_party/jni_zero/codegen/header_common.py') - tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py') - return tool_files + def get_tool_files(self): + tool_files = super().get_tool_files() + # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made + # its way to AOSP + # Files not specified in anywhere but jni_generator.py imports this file + tool_files.add('third_party/jni_zero/codegen/header_common.py') + tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py') + return tool_files class JavaJniRegistrationGeneratorSanitizer(JniRegistrationGeneratorSanitizer): - def get_name(self): - name = super().get_name() + "__java" - return name + def get_name(self): + name = super().get_name() + "__java" + return name - def get_outputs(self): - return [out for out in super().get_outputs() if out.endswith(".srcjar")] + def get_outputs(self): + return [ + out for out in super().get_outputs() if out.endswith(".srcjar") + ] - def get_deps(self): - return {} + def get_deps(self): + return {} class VersionSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_value_arg('-o', '$(out)') - # args for the version.py contain file path without leading --arg key. So apply sanitize - # function for all the args. - self._update_all_args(self._sanitize_filepath_with_location_tag) - super()._sanitize_args() + def _sanitize_args(self): + self._set_value_arg('-o', '$(out)') + # args for the version.py contain file path without leading --arg key. So apply sanitize + # function for all the args. + self._update_all_args(self._sanitize_filepath_with_location_tag) + super()._sanitize_args() - def get_tool_files(self): - tool_files = super().get_tool_files() - # android_chrome_version.py is not specified in anywhere but version.py imports this file - tool_files.add('build/util/android_chrome_version.py') - return tool_files + def get_tool_files(self): + tool_files = super().get_tool_files() + # android_chrome_version.py is not specified in anywhere but version.py imports this file + tool_files.add('build/util/android_chrome_version.py') + return tool_files class JavaCppEnumSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._update_all_args(self._sanitize_filepath_with_location_tag) - self._set_value_arg('--srcjar', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._update_all_args(self._sanitize_filepath_with_location_tag) + self._set_value_arg('--srcjar', '$(out)') + super()._sanitize_args() class MakeDafsaSanitizer(BaseActionSanitizer): - def is_header_generated(self): - # This script generates .cc files but they are #included by other sources - # (e.g. registry_controlled_domain.cc) - return True + def is_header_generated(self): + # This script generates .cc files but they are #included by other sources + # (e.g. registry_controlled_domain.cc) + return True - def _sanitize_args(self): - self._update_all_args(self._sanitize_filepath_with_location_tag) - self._update_all_args(self._sanitize_filepath) - super()._sanitize_args() + def _sanitize_args(self): + self._update_all_args(self._sanitize_filepath_with_location_tag) + self._update_all_args(self._sanitize_filepath) + super()._sanitize_args() class JavaCppFeatureSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._update_all_args(self._sanitize_filepath_with_location_tag) - self._set_value_arg('--srcjar', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._update_all_args(self._sanitize_filepath_with_location_tag) + self._set_value_arg('--srcjar', '$(out)') + super()._sanitize_args() class JavaCppStringSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._update_all_args(self._sanitize_filepath_with_location_tag) - self._set_value_arg('--srcjar', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._update_all_args(self._sanitize_filepath_with_location_tag) + self._set_value_arg('--srcjar', '$(out)') + super()._sanitize_args() class WriteNativeLibrariesJavaSanitizer(BaseActionSanitizer): - def _sanitize_args(self): - self._set_value_arg('--output', '$(out)') - super()._sanitize_args() + def _sanitize_args(self): + self._set_value_arg('--output', '$(out)') + super()._sanitize_args() class CopyActionSanitizer(BaseActionSanitizer): - def get_tool_files(self): - # CopyAction makes use of no tools, it simply relies on cp. - return set() + def get_tool_files(self): + # CopyAction makes use of no tools, it simply relies on cp. + return set() - def get_cmd(self): - return (super().get_pre_cmd() + ['cp'] + - [shlex.quote(arg) for arg in self.target.args]) + def get_cmd(self): + return (super().get_pre_cmd() + ['cp'] + + [shlex.quote(arg) for arg in self.target.args]) - def get_srcs(self): - srcs = super().get_srcs() - if srcs: - raise Exception( - f'CopyAction {self.target.name} specifies {srcs=}. Only deps are supported' - ) - deps = self.get_deps() - if len(deps) > 1: - raise Exception( - f'CopyAction {self.target.name} specifies multiple {deps=}. Only a single dep is supported' - ) - return set(f':{label_to_module_name(dep)}' for dep in deps) + def get_srcs(self): + srcs = super().get_srcs() + if srcs: + raise Exception( + f'CopyAction {self.target.name} specifies {srcs=}. Only deps are supported' + ) + deps = self.get_deps() + if len(deps) > 1: + raise Exception( + f'CopyAction {self.target.name} specifies multiple {deps=}. Only a single dep is supported' + ) + return set(f':{label_to_module_name(dep)}' for dep in deps) - def sanitize(self): - # By convention, copy targets use their deps as args for the copy (see get_srcs). - if len(self.target.args) > 1: - raise Exception( - f'CopyAction {self.target.name} specifies {self.target.args=}. Only deps are supported' - ) - self.target.args = [f'$(location {src})' for src in self.get_srcs()] - self.target.args.append('$(out)') - super().sanitize() + def sanitize(self): + # By convention, copy targets use their deps as args for the copy (see get_srcs). + if len(self.target.args) > 1: + raise Exception( + f'CopyAction {self.target.name} specifies {self.target.args=}. Only deps are supported' + ) + self.target.args = [f'$(location {src})' for src in self.get_srcs()] + self.target.args.append('$(out)') + super().sanitize() class ProtocJavaSanitizer(BaseActionSanitizer): - def __init__(self, target, arch, gn): - super().__init__(target, arch) - self._protoc = get_protoc_module_name(gn) + def __init__(self, target, arch, gn): + super().__init__(target, arch) + self._protoc = get_protoc_module_name(gn) - def _sanitize_proto_path(self, arg): - arg = self._sanitize_filepath(arg) - return tree_path + '/' + arg + def _sanitize_proto_path(self, arg): + arg = self._sanitize_filepath(arg) + return tree_path + '/' + arg - def _sanitize_args(self): - super()._sanitize_args() - self._delete_value_arg('--depfile') - self._set_value_arg('--protoc', '$(location %s)' % self._protoc) - self._update_value_arg('--proto-path', self._sanitize_proto_path) - self._set_value_arg('--srcjar', '$(out)') - for i, arg in enumerate(self.target.args): - if arg == '--import-dir': - self.target.args[ - i + - 1] = f"{tree_path}/{self.target.args[i+1].removeprefix('../../')}" - elif arg.startswith('../../') and arg.removeprefix( - '../../') in self.get_srcs(): - self.target.args[i] = self._sanitize_filepath_with_location_tag(arg) + def _sanitize_args(self): + super()._sanitize_args() + self._delete_value_arg('--depfile') + self._set_value_arg('--protoc', '$(location %s)' % self._protoc) + self._update_value_arg('--proto-path', self._sanitize_proto_path) + self._set_value_arg('--srcjar', '$(out)') + for i, arg in enumerate(self.target.args): + if arg == '--import-dir': + self.target.args[ + i + + 1] = f"{tree_path}/{self.target.args[i+1].removeprefix('../../')}" + elif arg.startswith('../../') and arg.removeprefix( + '../../') in self.get_srcs(): + self.target.args[ + i] = self._sanitize_filepath_with_location_tag(arg) - def _sanitize_inputs(self): - super()._sanitize_inputs() - # https://crrev.com/c/5840231 adds - # //third_party/android_build_tools/protoc/cipd/protoc - # to the input list. We don't import that protoc prebuilt binary; instead we - # build protoc from source from //third_party/protobuf:protoc. We don't - # need to add that as an input because it's already a tool dependency in - # the generated module. - self.target.inputs.remove( - "//third_party/android_build_tools/protoc/cipd/protoc") + def _sanitize_inputs(self): + super()._sanitize_inputs() + # https://crrev.com/c/5840231 adds + # //third_party/android_build_tools/protoc/cipd/protoc + # to the input list. We don't import that protoc prebuilt binary; instead we + # build protoc from source from //third_party/protobuf:protoc. We don't + # need to add that as an input because it's already a tool dependency in + # the generated module. + self.target.inputs.remove( + "//third_party/android_build_tools/protoc/cipd/protoc") - def get_tools(self): - tools = super().get_tools() - tools.add(self._protoc) - return tools + def get_tools(self): + tools = super().get_tools() + tools.add(self._protoc) + return tools def get_action_sanitizer(gn, target, gn_type, arch, is_test_target): - if target.script == "//build/write_buildflag_header.py" or target.script == "//base/allocator/partition_allocator/src/partition_alloc/write_buildflag_header.py": - # PartitionAlloc has forked the same write_buildflag_header.py script from - # Chromium to break its dependency on //build. - return WriteBuildFlagHeaderSanitizer(target, arch) - if target.script == "//third_party/perfetto/gn/write_buildflag_header.py": - return PerfettoWriteBuildFlagHeaderSanitizer(target, arch) - if target.script == "//base/write_build_date_header.py": - return WriteBuildDateHeaderSanitizer(target, arch) - if target.script == "//tools/metrics/histograms/generate_allowlist_from_histograms_file.py": - return WriteGenerateAllowlistFromHistogramsFileSanitizer(target, arch) - if target.script == "//build/util/version.py": - return VersionSanitizer(target, arch) - if target.script == "//build/android/gyp/java_cpp_enum.py": - return JavaCppEnumSanitizer(target, arch) - if target.script == "//net/tools/dafsa/make_dafsa.py": - return MakeDafsaSanitizer(target, arch) - if target.script == '//build/android/gyp/java_cpp_features.py': - return JavaCppFeatureSanitizer(target, arch) - if target.script == '//build/android/gyp/java_cpp_strings.py': - return JavaCppStringSanitizer(target, arch) - if target.script == '//build/android/gyp/write_native_libraries_java.py': - return WriteNativeLibrariesJavaSanitizer(target, arch) - if target.script == '//cp': - return CopyActionSanitizer(target, arch) - if target.script == '//build/gn_run_binary.py': - return GnRunBinarySanitizer(target, arch) - if target.script == '//build/protoc_java.py': - return ProtocJavaSanitizer(target, arch, gn) - if jni_zero_target_type := get_jni_zero_target_type(target): - if jni_zero_target_type == JniZeroTargetType.REGISTRATION_GENERATOR: - if gn_type == 'java_genrule': - # Fill up the sources of the target for JniRegistrationGenerator - # actions with all the java sources found under targets of type - # `generate_jni`. Note 1: Only do this for the java part in order to - # generate a complete GEN_JNI. The C++ part MUST only include java - # source files that are listed explicitly in `generate_jni` targets - # in the transitive dependency, this is handled inside the action - # sanitizer itself (See `get_srcs`). Adding java sources that are not - # listed to the C++ version of JniRegistrationGenerator will result - # in undefined symbols as the C++ part generates declarations that - # would have no definitions. Note 2: This is only done for the - # testing targets because their JniRegistration is not complete, - # Chromium generates Jni files for testing targets implicitly (See - # https://source.chromium.org/chromium/chromium/src/+/main:testing - # /test.gni;l=422;bpv=1;bpt=0;drc - # =02820c1b362c3a00f426d7c4eab18703d89cda03) to avoid having to - # replicate the same setup, just fill up the java JniRegistration - # with all java sources found under `generate_jni` targets and fill - # the C++ version with the exact files. - if is_test_target: - target.sources.update(gn.jni_java_sources) - return JavaJniRegistrationGeneratorSanitizer(target, arch, + if target.script == "//build/write_buildflag_header.py" or target.script == "//base/allocator/partition_allocator/src/partition_alloc/write_buildflag_header.py": + # PartitionAlloc has forked the same write_buildflag_header.py script from + # Chromium to break its dependency on //build. + return WriteBuildFlagHeaderSanitizer(target, arch) + if target.script == "//third_party/perfetto/gn/write_buildflag_header.py": + return PerfettoWriteBuildFlagHeaderSanitizer(target, arch) + if target.script == "//base/write_build_date_header.py": + return WriteBuildDateHeaderSanitizer(target, arch) + if target.script == "//tools/metrics/histograms/generate_allowlist_from_histograms_file.py": + return WriteGenerateAllowlistFromHistogramsFileSanitizer(target, arch) + if target.script == "//build/util/version.py": + return VersionSanitizer(target, arch) + if target.script == "//build/android/gyp/java_cpp_enum.py": + return JavaCppEnumSanitizer(target, arch) + if target.script == "//net/tools/dafsa/make_dafsa.py": + return MakeDafsaSanitizer(target, arch) + if target.script == '//build/android/gyp/java_cpp_features.py': + return JavaCppFeatureSanitizer(target, arch) + if target.script == '//build/android/gyp/java_cpp_strings.py': + return JavaCppStringSanitizer(target, arch) + if target.script == '//build/android/gyp/write_native_libraries_java.py': + return WriteNativeLibrariesJavaSanitizer(target, arch) + if target.script == '//cp': + return CopyActionSanitizer(target, arch) + if target.script == '//build/gn_run_binary.py': + return GnRunBinarySanitizer(target, arch) + if target.script == '//build/protoc_java.py': + return ProtocJavaSanitizer(target, arch, gn) + if jni_zero_target_type := get_jni_zero_target_type(target): + if jni_zero_target_type == JniZeroTargetType.REGISTRATION_GENERATOR: + if gn_type == 'java_genrule': + # Fill up the sources of the target for JniRegistrationGenerator + # actions with all the java sources found under targets of type + # `generate_jni`. Note 1: Only do this for the java part in order to + # generate a complete GEN_JNI. The C++ part MUST only include java + # source files that are listed explicitly in `generate_jni` targets + # in the transitive dependency, this is handled inside the action + # sanitizer itself (See `get_srcs`). Adding java sources that are not + # listed to the C++ version of JniRegistrationGenerator will result + # in undefined symbols as the C++ part generates declarations that + # would have no definitions. Note 2: This is only done for the + # testing targets because their JniRegistration is not complete, + # Chromium generates Jni files for testing targets implicitly (See + # https://source.chromium.org/chromium/chromium/src/+/main:testing + # /test.gni;l=422;bpv=1;bpt=0;drc + # =02820c1b362c3a00f426d7c4eab18703d89cda03) to avoid having to + # replicate the same setup, just fill up the java JniRegistration + # with all java sources found under `generate_jni` targets and fill + # the C++ version with the exact files. + if is_test_target: + target.sources.update(gn.jni_java_sources) + return JavaJniRegistrationGeneratorSanitizer( + target, arch, is_test_target) + return JniRegistrationGeneratorSanitizer(target, arch, is_test_target) - return JniRegistrationGeneratorSanitizer(target, arch, is_test_target) - if gn_type == 'cc_genrule': - return JniGeneratorSanitizer(target, arch, is_test_target) - return JavaJniGeneratorSanitizer(target, arch, is_test_target) - raise Exception('Unsupported action %s from %s' % - (target.script, target.name)) + if gn_type == 'cc_genrule': + return JniGeneratorSanitizer(target, arch, is_test_target) + return JavaJniGeneratorSanitizer(target, arch, is_test_target) + raise Exception('Unsupported action %s from %s' % + (target.script, target.name)) def create_action_foreach_modules(blueprint, gn, target, is_test_target): - """ The following assumes that rebase_path exists in the args. + """ The following assumes that rebase_path exists in the args. The args of an action_foreach contains hints about which output files are generated by which source files. This is copied directly from the args @@ -2207,47 +2246,49 @@ So each source file will generate an output whose name is the {source_name-reversed-inc.cc} """ - # We create one genrule per individual source, with numbered names (e.g. - # "foo_0", "foo_1", etc.). - # Note: currently we return the collection of the resulting genrules, instead - # of a single module. Arguably this is a bit cumbersome. We could centralize - # the outputs into a single "cp everything" genrule so that dependent modules - # only have to depend on a single module. + # We create one genrule per individual source, with numbered names (e.g. + # "foo_0", "foo_1", etc.). + # Note: currently we return the collection of the resulting genrules, instead + # of a single module. Arguably this is a bit cumbersome. We could centralize + # the outputs into a single "cp everything" genrule so that dependent modules + # only have to depend on a single module. - def create_subtarget(i, src): - subtarget = copy.deepcopy(target) - subtarget.name += f"_{i}" - subtarget.sources = {src} - new_args = [] - for arg in target.args: - if '{{source}}' in arg: - new_args.append('$(location %s)' % (gn_utils.label_to_path(src))) - elif '{{source_name_part}}' in arg: - source_name_part = src.split("/")[-1] # Get the file name only - source_name_part = source_name_part.split(".")[ - 0] # Remove the extension (Ex: .cc) - file_name = arg.replace('{{source_name_part}}', - source_name_part).split("/")[-1] - # file_name represent the output file name. But we need the whole path - # This can be found from target.outputs. - for out in target.outputs: - if out.endswith(file_name): - new_args.append('$(location %s)' % out) - subtarget.outputs = {out} + def create_subtarget(i, src): + subtarget = copy.deepcopy(target) + subtarget.name += f"_{i}" + subtarget.sources = {src} + new_args = [] + for arg in target.args: + if '{{source}}' in arg: + new_args.append('$(location %s)' % + (gn_utils.label_to_path(src))) + elif '{{source_name_part}}' in arg: + source_name_part = src.split("/")[-1] # Get the file name only + source_name_part = source_name_part.split(".")[ + 0] # Remove the extension (Ex: .cc) + file_name = arg.replace('{{source_name_part}}', + source_name_part).split("/")[-1] + # file_name represent the output file name. But we need the whole path + # This can be found from target.outputs. + for out in target.outputs: + if out.endswith(file_name): + new_args.append('$(location %s)' % out) + subtarget.outputs = {out} - for file in (target.sources | target.inputs): - if file.endswith(file_name): - new_args.append('$(location %s)' % gn_utils.label_to_path(file)) - else: - new_args.append(arg) - subtarget.args = new_args - return subtarget + for file in (target.sources | target.inputs): + if file.endswith(file_name): + new_args.append('$(location %s)' % + gn_utils.label_to_path(file)) + else: + new_args.append(arg) + subtarget.args = new_args + return subtarget - return [ - create_action_module(blueprint, gn, create_subtarget(i, src), - 'cc_genrule', is_test_target) - for i, src in enumerate(sorted(target.sources)) - ] + return [ + create_action_module(blueprint, gn, create_subtarget(i, src), + 'cc_genrule', is_test_target) + for i, src in enumerate(sorted(target.sources)) + ] def create_action_module_internal(gn, @@ -2256,202 +2297,207 @@ is_test_target, blueprint, arch=None): - if target.script == '//build/android/gyp/gcc_preprocess.py': - return create_gcc_preprocess_modules(blueprint, target) - sanitizer = get_action_sanitizer(gn, target, gn_type, arch, is_test_target) - sanitizer.sanitize() + if target.script == '//build/android/gyp/gcc_preprocess.py': + return create_gcc_preprocess_modules(blueprint, target) + sanitizer = get_action_sanitizer(gn, target, gn_type, arch, is_test_target) + sanitizer.sanitize() - module = Module(gn_type, sanitizer.get_name(), target.name) - module.cmd = sanitizer.get_cmd() - module.out = sanitizer.get_outputs() - if sanitizer.is_header_generated(): - module.genrule_headers.add(module.name) - module.srcs = sanitizer.get_srcs() - module.tool_files = sanitizer.get_tool_files() - module.tools = sanitizer.get_tools() - target.deps = sanitizer.get_deps() + module = Module(gn_type, sanitizer.get_name(), target.name) + module.cmd = sanitizer.get_cmd() + module.out = sanitizer.get_outputs() + if sanitizer.is_header_generated(): + module.genrule_headers.add(module.name) + module.srcs = sanitizer.get_srcs() + module.tool_files = sanitizer.get_tool_files() + module.tools = sanitizer.get_tools() + target.deps = sanitizer.get_deps() - return module + return module def get_cmd_condition(arch): - ''' + ''' :param arch: archtecture name e.g. android_x86_64, android_arm64 :return: condition that can be used in cc_genrule cmd to switch the behavior based on arch ''' - if arch == "android_x86_64": - return "( $$CC_ARCH == 'x86_64' && $$CC_OS == 'android' )" - if arch == "android_x86": - return "( $$CC_ARCH == 'x86' && $$CC_OS == 'android' )" - if arch == "android_arm": - return "( $$CC_ARCH == 'arm' && $$CC_OS == 'android' )" - if arch == "android_arm64": - return "( $$CC_ARCH == 'arm64' && $$CC_OS == 'android' )" - if arch == "android_riscv64": - return "( $$CC_ARCH == 'riscv64' && $$CC_OS == 'android' )" - if arch == "host": - return "$$CC_OS != 'android'" - raise Exception(f'Unknown architecture type {arch}') + if arch == "android_x86_64": + return "( $$CC_ARCH == 'x86_64' && $$CC_OS == 'android' )" + if arch == "android_x86": + return "( $$CC_ARCH == 'x86' && $$CC_OS == 'android' )" + if arch == "android_arm": + return "( $$CC_ARCH == 'arm' && $$CC_OS == 'android' )" + if arch == "android_arm64": + return "( $$CC_ARCH == 'arm64' && $$CC_OS == 'android' )" + if arch == "android_riscv64": + return "( $$CC_ARCH == 'riscv64' && $$CC_OS == 'android' )" + if arch == "host": + return "$$CC_OS != 'android'" + raise Exception(f'Unknown architecture type {arch}') def merge_cmd(modules, genrule_type): - ''' + ''' :param modules: dictionary whose key is arch name and value is module :param genrule_type: cc_genrule or java_genrule :return: merged command or common command if all the archs have the same command. ''' - commands = list({"\n".join(module.cmd) for module in modules.values()}) - if len(commands) == 1: - # If all the archs have the same command, return the command - return list(modules.values())[0].cmd + commands = list({"\n".join(module.cmd) for module in modules.values()}) + if len(commands) == 1: + # If all the archs have the same command, return the command + return list(modules.values())[0].cmd - if genrule_type != 'cc_genrule': - raise Exception(f'{genrule_type} can not have different cmd between archs') + if genrule_type != 'cc_genrule': + raise Exception( + f'{genrule_type} can not have different cmd between archs') - merged_cmd = [] - for arch, module in sorted(modules.items()): - merged_cmd.append(f'if [[ {get_cmd_condition(arch)} ]];') - merged_cmd.append('then') - merged_cmd.extend(module.cmd) - merged_cmd.append(';fi;') - return merged_cmd + merged_cmd = [] + for arch, module in sorted(modules.items()): + merged_cmd.append(f'if [[ {get_cmd_condition(arch)} ]];') + merged_cmd.append('then') + merged_cmd.extend(module.cmd) + merged_cmd.append(';fi;') + return merged_cmd def merge_modules(modules, genrule_type): - ''' + ''' :param modules: dictionary whose key is arch name and value is module :param genrule_type: cc_genrule or java_genrule :return: merged module of input modules ''' - merged_module = list(modules.values())[0] + merged_module = list(modules.values())[0] - # Following attributes must be the same between archs - for key in ('genrule_headers', 'srcs', 'tool_files'): - if any( - getattr(merged_module, key) != getattr(module, key) - for module in modules.values()): - raise Exception( - f'{merged_module.name} has different values for {key} between archs') + # Following attributes must be the same between archs + for key in ('genrule_headers', 'srcs', 'tool_files'): + if any( + getattr(merged_module, key) != getattr(module, key) + for module in modules.values()): + raise Exception( + f'{merged_module.name} has different values for {key} between archs' + ) - merged_module.cmd = merge_cmd(modules, genrule_type) - return merged_module + merged_module.cmd = merge_cmd(modules, genrule_type) + return merged_module def create_java_module(bp_module_name, target, blueprint): - def add_java_library_properties(module): - module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP - module.apex_available = [tethering_apex] - module.defaults.add(java_framework_defaults_module) - module.build_file_path = target.build_file_path + def add_java_library_properties(module): + module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP + module.apex_available = [tethering_apex] + module.defaults.add(java_framework_defaults_module) + module.build_file_path = target.build_file_path - # As hinted in `parse_gn_desc()`, Java GN targets are... complicated. - # - # Here the main source of complexity is the need to support the - # jar_excluded_patterns and jar_included_patterns options of Chromium's - # `java_library()` GN rule. Most java_library targets don't really use this - # feature, but there are notable exceptions: for example some jni_zero - # generator targets rely on this to remove placeholder classes, which would - # conflict with the real classes otherwise. - # - # Soong doesn't provide an equivalent to jar_excluded_patterns and - # jar_included_patterns, so we need to implement that ourselves. To do so, we - # introduce the concept of "filtering". Instead of generating just one module - # for a given Java target, we generate 3: - # - # - A top-level `java_library` module, that doesn't build anything and only - # acts as a dependency holder; - # - A `java_library` (or `java_import`) module with a `__unfiltered` suffix, - # that does the actual building; - # - A `java_genrule` module with a `__filtered` prefix, that takes the output - # of the unfiltered module and applies the jar exclusion/inclusion rules. - # - # If you're wondering why the top-level module is needed (i.e. why can't we go - # directly to the filter module), the reason is because otherwise there would - # be no way to correctly set up parallel filtered vs. unfiltered dependency - # trees. See the Java dependency handling logic in - # create_modules_from_target() for details. - # - # For even more more background, see https://crbug.com/397396295. + # As hinted in `parse_gn_desc()`, Java GN targets are... complicated. + # + # Here the main source of complexity is the need to support the + # jar_excluded_patterns and jar_included_patterns options of Chromium's + # `java_library()` GN rule. Most java_library targets don't really use this + # feature, but there are notable exceptions: for example some jni_zero + # generator targets rely on this to remove placeholder classes, which would + # conflict with the real classes otherwise. + # + # Soong doesn't provide an equivalent to jar_excluded_patterns and + # jar_included_patterns, so we need to implement that ourselves. To do so, we + # introduce the concept of "filtering". Instead of generating just one module + # for a given Java target, we generate 3: + # + # - A top-level `java_library` module, that doesn't build anything and only + # acts as a dependency holder; + # - A `java_library` (or `java_import`) module with a `__unfiltered` suffix, + # that does the actual building; + # - A `java_genrule` module with a `__filtered` prefix, that takes the output + # of the unfiltered module and applies the jar exclusion/inclusion rules. + # + # If you're wondering why the top-level module is needed (i.e. why can't we go + # directly to the filter module), the reason is because otherwise there would + # be no way to correctly set up parallel filtered vs. unfiltered dependency + # trees. See the Java dependency handling logic in + # create_modules_from_target() for details. + # + # For even more more background, see https://crbug.com/397396295. - sources = target.sources - source_is_jar = any(source.endswith('.jar') for source in sources) - unfiltered_module = Module("java_import" if source_is_jar else "java_library", - f"{bp_module_name}__unfiltered", target.name) - add_java_library_properties(unfiltered_module) - if source_is_jar: - assert all(source.endswith('.jar') for source in sources), target.name - unfiltered_module.jars = [ - gn_utils.label_to_path(source) for source in sources + sources = target.sources + source_is_jar = any(source.endswith('.jar') for source in sources) + unfiltered_module = Module( + "java_import" if source_is_jar else "java_library", + f"{bp_module_name}__unfiltered", target.name) + add_java_library_properties(unfiltered_module) + if source_is_jar: + assert all(source.endswith('.jar') for source in sources), target.name + unfiltered_module.jars = [ + gn_utils.label_to_path(source) for source in sources + ] + blueprint.add_module(unfiltered_module) + + # Potential optimization opportunity: we could skip the filtered module if + # there are no jar exclusion/inclusion rules, and have the top module depend + # on the unfiltered module directly. This would avoid a pointless call to + # filter_zip.py with no rules. (But note that, even then, we would still need + # a distinction between filtered and unfiltered modules: an unfiltered module + # should not depend on *any* filtered module, even indirectly, so we need to + # keep the dependency chains separate throughout the entire build tree no + # matter what.) + filtered_module = Module("java_genrule", f"{bp_module_name}__filtered", + target.name) + filtered_module.srcs = [f":{unfiltered_module.name}"] + + jar_excluded_patterns = target.java_jar_excluded_patterns + # HACK: don't strip the placeholder org.chromium.build.NativeLibraries from + # //build/android:build_java, as we don't generate the real one. + # TODO(https://crbug.com/405373567): generate a proper NativeLibraries + # instead. + if target.name in ("//build/android:build_java", + "//build/android:build_java__testing"): + jar_excluded_patterns = [ + jar_excluded_pattern + for jar_excluded_pattern in jar_excluded_patterns + if jar_excluded_pattern != "*/NativeLibraries.class" + ] + + def array_to_arg(array): + return shlex.quote( + # filter_zip.py array arguments expect "GN-string" syntax. + build.gn_helpers.ToGNString(array)).replace('$', '$$') + + # Chromium conveniently provides a script, `filter_zip.py`, that we can use to + # process the jar and apply the exclusion/inclusion rules. + # + # Note this is different to how Chromium does it. In Chromium the rules are + # applied directly by `compile_java.py`. We can't do that here because we + # don't use `compile_java.py` - instead we use Soong's `java_library` module + # to compile Java code. + # + # That said, Chromium does use `filter_zip.py` to filter prebuilt jars, so + # it's likely this script will keep working for the foreseeable future. + FILTER_ZIP_PATH = "build/android/gyp/filter_zip.py" + filtered_module.cmd = [ + f"$(location {FILTER_ZIP_PATH})", + "--input", + "'$(in)'", + "--output", + "'$(out)'", + "--exclude-globs", + array_to_arg(jar_excluded_patterns), + "--include-globs", + array_to_arg(target.java_jar_included_patterns), ] - blueprint.add_module(unfiltered_module) + filtered_module.out = [f"{filtered_module.name}.jar"] + # Normally we would get `tool_files` from the gn desc, but here we don't have + # an action target to extract this from, so we compute it ourselves. + filtered_module.tool_files = _parse_pydeps(f"{FILTER_ZIP_PATH}deps") + filtered_module.visibility = {"//external/cronet:__subpackages__"} + blueprint.add_module(filtered_module) - # Potential optimization opportunity: we could skip the filtered module if - # there are no jar exclusion/inclusion rules, and have the top module depend - # on the unfiltered module directly. This would avoid a pointless call to - # filter_zip.py with no rules. (But note that, even then, we would still need - # a distinction between filtered and unfiltered modules: an unfiltered module - # should not depend on *any* filtered module, even indirectly, so we need to - # keep the dependency chains separate throughout the entire build tree no - # matter what.) - filtered_module = Module("java_genrule", f"{bp_module_name}__filtered", - target.name) - filtered_module.srcs = [f":{unfiltered_module.name}"] + top_module = Module("java_library", bp_module_name, target.name) + top_module.java_unfiltered_module = unfiltered_module + add_java_library_properties(top_module) + top_module.static_libs.add(filtered_module.name) + return top_module - jar_excluded_patterns = target.java_jar_excluded_patterns - # HACK: don't strip the placeholder org.chromium.build.NativeLibraries from - # //build/android:build_java, as we don't generate the real one. - # TODO(https://crbug.com/405373567): generate a proper NativeLibraries - # instead. - if target.name in ("//build/android:build_java", - "//build/android:build_java__testing"): - jar_excluded_patterns = [ - jar_excluded_pattern for jar_excluded_pattern in jar_excluded_patterns - if jar_excluded_pattern != "*/NativeLibraries.class" - ] - - def array_to_arg(array): - return shlex.quote( - # filter_zip.py array arguments expect "GN-string" syntax. - build.gn_helpers.ToGNString(array)).replace('$', '$$') - - # Chromium conveniently provides a script, `filter_zip.py`, that we can use to - # process the jar and apply the exclusion/inclusion rules. - # - # Note this is different to how Chromium does it. In Chromium the rules are - # applied directly by `compile_java.py`. We can't do that here because we - # don't use `compile_java.py` - instead we use Soong's `java_library` module - # to compile Java code. - # - # That said, Chromium does use `filter_zip.py` to filter prebuilt jars, so - # it's likely this script will keep working for the foreseeable future. - FILTER_ZIP_PATH = "build/android/gyp/filter_zip.py" - filtered_module.cmd = [ - f"$(location {FILTER_ZIP_PATH})", - "--input", - "'$(in)'", - "--output", - "'$(out)'", - "--exclude-globs", - array_to_arg(jar_excluded_patterns), - "--include-globs", - array_to_arg(target.java_jar_included_patterns), - ] - filtered_module.out = [f"{filtered_module.name}.jar"] - # Normally we would get `tool_files` from the gn desc, but here we don't have - # an action target to extract this from, so we compute it ourselves. - filtered_module.tool_files = _parse_pydeps(f"{FILTER_ZIP_PATH}deps") - filtered_module.visibility = {"//external/cronet:__subpackages__"} - blueprint.add_module(filtered_module) - - top_module = Module("java_library", bp_module_name, target.name) - top_module.java_unfiltered_module = unfiltered_module - add_java_library_properties(top_module) - top_module.static_libs.add(filtered_module.name) - return top_module def get_bindgen_source_stem(outputs: List[str]) -> str: - """Returns the appropriate source_stem for a bindgen module + """Returns the appropriate source_stem for a bindgen module Args: outputs: The appropriate source stem to be used. @@ -2460,26 +2506,26 @@ source stem to be used for the bindgen module or raises ValueError if more than a single .rs file is found """ - rs_output = None - for output in outputs: - if output.endswith(".rs"): - if rs_output: + rs_output = None + for output in outputs: + if output.endswith(".rs"): + if rs_output: + raise ValueError( + f"Expected a single rust file in the target output but found more than one! Outputs: {outputs}" + ) + rs_output = output + if not rs_output: raise ValueError( - f"Expected a single rust file in the target output but found more than one! Outputs: {outputs}" + f"Expected a single rust file in the target output but found none! Outputs: {outputs}" ) - rs_output = output - if not rs_output: - raise ValueError( - f"Expected a single rust file in the target output but found none! Outputs: {outputs}" - ) - file_name = rs_output[:-3] - if "/" in file_name: - file_name = file_name.rsplit("/", 1)[1] - return file_name + file_name = rs_output[:-3] + if "/" in file_name: + file_name = file_name.rsplit("/", 1)[1] + return file_name def get_bindgen_flags(args: List[str]) -> List[str]: - """Gets the appropriate bindgen_flags from the GN target args + """Gets the appropriate bindgen_flags from the GN target args Args: args: GN target args @@ -2490,102 +2536,105 @@ Returns: Gets the appropriate bindgen_flags from the GN target args """ - if "--bindgen-flags" not in args: - return [] + if "--bindgen-flags" not in args: + return [] - bindgen_flags = [] - for arg in args[args.index("--bindgen-flags") + 1:]: - if arg.startswith("--"): - # This is a new argument for the python script and not a bindgen argument. - break - bindgen_flags.append("--" + arg) + bindgen_flags = [] + for arg in args[args.index("--bindgen-flags") + 1:]: + if arg.startswith("--"): + # This is a new argument for the python script and not a bindgen argument. + break + bindgen_flags.append("--" + arg) - return bindgen_flags + return bindgen_flags def _create_extract_rust_files_target(bindgen_module, blueprint): - module = Module("cc_genrule", bindgen_module.name + "__extract_rust_files", - f"Extract rust files from {bindgen_module.name}") - module.srcs = [f":{bindgen_module.name}"] - module.cmd = [ - f'for f in $(locations :{bindgen_module.name}); do', - 'if [[ "$$f" == *.rs ]]; then', 'cp "$$f" $(out);', 'fi;', 'done' - ] - module.out = [f"{bindgen_module.source_stem}.rs"] - module.device_supported = bindgen_module.device_supported - module.host_supported = bindgen_module.host_supported - module.host_cross_supported = bindgen_module.host_cross_supported - module.target['host'].compile_multilib = '64' - module.apex_available = [tethering_apex] - blueprint.add_module(module) - return module + module = Module("cc_genrule", bindgen_module.name + "__extract_rust_files", + f"Extract rust files from {bindgen_module.name}") + module.srcs = [f":{bindgen_module.name}"] + module.cmd = [ + f'for f in $(locations :{bindgen_module.name}); do', + 'if [[ "$$f" == *.rs ]]; then', 'cp "$$f" $(out);', 'fi;', 'done' + ] + module.out = [f"{bindgen_module.source_stem}.rs"] + module.device_supported = bindgen_module.device_supported + module.host_supported = bindgen_module.host_supported + module.host_cross_supported = bindgen_module.host_cross_supported + module.target['host'].compile_multilib = '64' + module.apex_available = [tethering_apex] + blueprint.add_module(module) + return module + def create_bindgen_module(blueprint: Blueprint, target, module_name: str) -> Module: - module = Module("rust_bindgen", "lib" + module_name, target.name) - if len(target.sources) > 1: - raise ValueError( - f"Expected a single source file for bindgen but found {target.sources}." - ) + module = Module("rust_bindgen", "lib" + module_name, target.name) + if len(target.sources) > 1: + raise ValueError( + f"Expected a single source file for bindgen but found {target.sources}." + ) - if len(target.outputs) > 2: - raise ValueError( - f"Expected at most two output files for bindgen but found {target.outputs}" - ) - module.wrapper_src = gn_utils.label_to_path(list(target.sources)[0]) - module.crate_name = module_name + if len(target.outputs) > 2: + raise ValueError( + f"Expected at most two output files for bindgen but found {target.outputs}" + ) + module.wrapper_src = gn_utils.label_to_path(list(target.sources)[0]) + module.crate_name = module_name - if "c++" in target.args: - # This is defined in the rust_bindgen templates where "C++" will - # be added to the args if `cpp` field is defined. Soong depends - # on `cpp_std` field to identify that this is a C++ header. - module.cpp_std = CPP_VERSION + if "c++" in target.args: + # This is defined in the rust_bindgen templates where "C++" will + # be added to the args if `cpp` field is defined. Soong depends + # on `cpp_std` field to identify that this is a C++ header. + module.cpp_std = CPP_VERSION - module.source_stem = get_bindgen_source_stem(target.outputs) + module.source_stem = get_bindgen_source_stem(target.outputs) - if "--wrap-static-fns" in target.args: - module.handle_static_inline = True + if "--wrap-static-fns" in target.args: + module.handle_static_inline = True - module.bindgen_flags = get_bindgen_flags(target.args) - # This ensures that any CC file that is being processed through the - # rust_bindgen module is able to #include files relative to the root of the - # repository. - # - # Note: this module is not part of the generated build rules; it is expected - # to already be present in AOSP (currently, in Android.extras.bp). See - # https://r.android.com/3413202. - module.header_libs = {f"{MODULE_PREFIX}repository_root_include_dirs_anchor"} - module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP - module.apex_available = [tethering_apex] - blueprint.add_module(module) - return module + module.bindgen_flags = get_bindgen_flags(target.args) + # This ensures that any CC file that is being processed through the + # rust_bindgen module is able to #include files relative to the root of the + # repository. + # + # Note: this module is not part of the generated build rules; it is expected + # to already be present in AOSP (currently, in Android.extras.bp). See + # https://r.android.com/3413202. + module.header_libs = { + f"{MODULE_PREFIX}repository_root_include_dirs_anchor" + } + module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP + module.apex_available = [tethering_apex] + blueprint.add_module(module) + return module -def create_generated_headers_export_module(blueprint: Blueprint, - cc_genrule_module: Module) -> Module: - ''' +def create_generated_headers_export_module( + blueprint: Blueprint, cc_genrule_module: Module) -> Module: + ''' Creates a cc_library_headers module that merely re-exports headers that are generated by a cc_genrule module. This is useful in scenarios where a module has no way of directly depending on generated headers. ''' - cc_genrule_module_name = cc_genrule_module.name - module = Module("cc_library_headers", - f"{cc_genrule_module_name}_export_generated_headers", - cc_genrule_module.gn_target) - module.export_generated_headers = module.generated_headers = [ - cc_genrule_module_name - ] - module.build_file_path = cc_genrule_module.build_file_path - module.defaults = [cc_defaults_module] - module.host_supported = cc_genrule_module.host_supported - module.host_cross_supported = cc_genrule_module.host_cross_supported - module.device_supported = cc_genrule_module.device_supported - blueprint.add_module(module) - return module + cc_genrule_module_name = cc_genrule_module.name + module = Module("cc_library_headers", + f"{cc_genrule_module_name}_export_generated_headers", + cc_genrule_module.gn_target) + module.export_generated_headers = module.generated_headers = [ + cc_genrule_module_name + ] + module.build_file_path = cc_genrule_module.build_file_path + module.defaults = [cc_defaults_module] + module.host_supported = cc_genrule_module.host_supported + module.host_cross_supported = cc_genrule_module.host_cross_supported + module.device_supported = cc_genrule_module.device_supported + blueprint.add_module(module) + return module def create_action_module(blueprint, gn, target, genrule_type, is_test_target): - ''' + ''' Create module for action target and add to the blueprint. If target has arch specific attributes this function merge them and create a single module. :param blueprint: @@ -2593,28 +2642,29 @@ :param genrule_type: cc_genrule or java_genrule :return: created module ''' - # TODO: Handle this target correctly, this target generates java_genrule but this target has - # different value for cpu-family arg between archs - if re.match('//build/android:native_libraries_gen(__testing)?$', target.name): - module = create_action_module_internal(gn, target, genrule_type, - is_test_target, blueprint, - target.arch['android_arm']) + # TODO: Handle this target correctly, this target generates java_genrule but this target has + # different value for cpu-family arg between archs + if re.match('//build/android:native_libraries_gen(__testing)?$', + target.name): + module = create_action_module_internal(gn, target, genrule_type, + is_test_target, blueprint, + target.arch['android_arm']) + blueprint.add_module(module) + return module + + modules = { + arch_name: + create_action_module_internal(gn, target, genrule_type, is_test_target, + blueprint, arch) + for arch_name, arch in target.get_archs().items() + } + module = merge_modules(modules, genrule_type) blueprint.add_module(module) return module - modules = { - arch_name: - create_action_module_internal(gn, target, genrule_type, is_test_target, - blueprint, arch) - for arch_name, arch in target.get_archs().items() - } - module = merge_modules(modules, genrule_type) - blueprint.add_module(module) - return module - def create_jni_zero_proxy_only_module(jni_zero_generator_module): - ''' + ''' Creates a module that filters the output of an existing jni_zero generator action module, outputting the proxy classes only, leaving out the placeholder classes. @@ -2625,318 +2675,324 @@ original output files. We can then depend on these genrules to pull the files we want. ''' - assert jni_zero_generator_module.jni_zero_target_type == JniZeroTargetType.GENERATOR - proxy_path, _ = get_jni_zero_generator_proxy_and_placeholder_paths( - jni_zero_generator_module) + assert jni_zero_generator_module.jni_zero_target_type == JniZeroTargetType.GENERATOR + proxy_path, _ = get_jni_zero_generator_proxy_and_placeholder_paths( + jni_zero_generator_module) - proxy_only_module = Module(jni_zero_generator_module.type, - f"{jni_zero_generator_module.name}_proxy_only", - jni_zero_generator_module.gn_target) - proxy_only_module.cmd = "cp $(in) $(genDir)" - proxy_only_module.srcs = [f":{jni_zero_generator_module.name}"] - proxy_only_module.out = [os.path.basename(proxy_path)] + proxy_only_module = Module(jni_zero_generator_module.type, + f"{jni_zero_generator_module.name}_proxy_only", + jni_zero_generator_module.gn_target) + proxy_only_module.cmd = "cp $(in) $(genDir)" + proxy_only_module.srcs = [f":{jni_zero_generator_module.name}"] + proxy_only_module.out = [os.path.basename(proxy_path)] - return proxy_only_module + return proxy_only_module def _is_cflag_allowed(cflag): - if cflag.startswith("-Wno-"): - # Allow all -Wno- flags as those demote errors to warning. - return True - return all(not cflag.startswith(denied_prefix) for denied_prefix in [ - # Soong handles this according to the module's attributes. - "--sysroot=", - # Soong handles this according to the architecture. - "--target=", - "--warning-suppression-mappings=", - # Remove all promotions of warning to errors. The code is developed in - # chromium, and the checks should be there. - "-W", - # Soong handles this according to the module's attributes. - "-isystem", - # Best handled by Soong according to the build configuration. - '-fcrash-diagnostics-dir=', - # Enabled by default in Soong. - '-flto', - # Enabled by default in Soong. - '-fsplit-lto-unit', - # Enabled by a special attribute instead. - '-fwhole-program-vtables', - # LLVM in AOSP fails when this is added. It's mostly used to warn - # against non-standard compiler extensions. It's forbidden by Soong as it's - # in the list of the IllegalFlags: http://ac/build/soong/cc/config/global.go?l=405-413 - '-pedantic', - # Same as above. - '-w', - # This is used by a clang-plugin to show errors / warning for unsafe buffers during - # compilation. We don't care about static analysis errors / warnings as they're - # shown on the Chromium side. The reason why we're excluding this flag is because - # it introduces build breakages as clang's toolchain does not understand the - # pragma enabled by this define. - '-DUNSAFE_BUFFERS_BUILD', - '-Wunsafe-buffer-usage', - '-Wno-error=unsafe-buffer-usage', - # Causes Soong to fail with: - # "Bad flag: `-gsplit-dwarf`, soong cannot track dependencies to split dwarf debuginfo" - # See https://crbug.com/481594099 - '-gsplit-dwarf', - # Causes the build to fail with: - # clang++-real: error: unknown argument: '-fsanitize-ignore-for-ubsan-feature=array-bounds' - # See https://crbug.com/481594099 - '-fsanitize-ignore-for-ubsan-feature=array-bounds', - # Causes the build to fail with: - # clang++-real: error: unknown argument: '-fno-lifetime-dse' - # See https://crbug.com/484919839 - '-fno-lifetime-dse', - # C++ version is picked via a Soong attribute. - '-std=', - # Added automatically by specifying `afdo: true` - '-fdebug-info-for-profiling', - # Any cflag that starts with '-g' is solely for debugging information. - # This is best left handled by Soong according to the build configuration. - # See https://clang.llvm.org/docs/ClangCommandLineReference.html#debug-information-options - '-g', - # MLGO optimizations are handled automatically by Soong when AFDO is - # applied. - '-mllvm -enable-ml-inliner=', - '-mllvm -ml-inliner-model-selector=', - ]) + if cflag.startswith("-Wno-"): + # Allow all -Wno- flags as those demote errors to warning. + return True + return all(not cflag.startswith(denied_prefix) for denied_prefix in [ + # Soong handles this according to the module's attributes. + "--sysroot=", + # Soong handles this according to the architecture. + "--target=", + "--warning-suppression-mappings=", + # Remove all promotions of warning to errors. The code is developed in + # chromium, and the checks should be there. + "-W", + # Soong handles this according to the module's attributes. + "-isystem", + # Best handled by Soong according to the build configuration. + '-fcrash-diagnostics-dir=', + # Enabled by default in Soong. + '-flto', + # Enabled by default in Soong. + '-fsplit-lto-unit', + # Enabled by a special attribute instead. + '-fwhole-program-vtables', + # LLVM in AOSP fails when this is added. It's mostly used to warn + # against non-standard compiler extensions. It's forbidden by Soong as it's + # in the list of the IllegalFlags: http://ac/build/soong/cc/config/global.go?l=405-413 + '-pedantic', + # Same as above. + '-w', + # This is used by a clang-plugin to show errors / warning for unsafe buffers during + # compilation. We don't care about static analysis errors / warnings as they're + # shown on the Chromium side. The reason why we're excluding this flag is because + # it introduces build breakages as clang's toolchain does not understand the + # pragma enabled by this define. + '-DUNSAFE_BUFFERS_BUILD', + '-Wunsafe-buffer-usage', + '-Wno-error=unsafe-buffer-usage', + # Causes Soong to fail with: + # "Bad flag: `-gsplit-dwarf`, soong cannot track dependencies to split dwarf debuginfo" + # See https://crbug.com/481594099 + '-gsplit-dwarf', + # Causes the build to fail with: + # clang++-real: error: unknown argument: '-fsanitize-ignore-for-ubsan-feature=array-bounds' + # See https://crbug.com/481594099 + '-fsanitize-ignore-for-ubsan-feature=array-bounds', + # Causes the build to fail with: + # clang++-real: error: unknown argument: '-fno-lifetime-dse' + # See https://crbug.com/484919839 + '-fno-lifetime-dse', + # C++ version is picked via a Soong attribute. + '-std=', + # Added automatically by specifying `afdo: true` + '-fdebug-info-for-profiling', + # Any cflag that starts with '-g' is solely for debugging information. + # This is best left handled by Soong according to the build configuration. + # See https://clang.llvm.org/docs/ClangCommandLineReference.html#debug-information-options + '-g', + # MLGO optimizations are handled automatically by Soong when AFDO is + # applied. + '-mllvm -enable-ml-inliner=', + '-mllvm -ml-inliner-model-selector=', + ]) + def _merge_key_value_cflags(cflags: List[str]) -> List[str]: - cflags_merged = [] - iterator = iter(cflags) - for flag in iterator: + cflags_merged = [] + iterator = iter(cflags) + for flag in iterator: - if flag == '-mllvm': - # Merge the two consecutive flags together and skip the next iteration. - cflags_merged.append(f'{flag} {next(iterator)}') - else: - cflags_merged.append(flag) - return cflags_merged + if flag == '-mllvm': + # Merge the two consecutive flags together and skip the next iteration. + cflags_merged.append(f'{flag} {next(iterator)}') + else: + cflags_merged.append(flag) + return cflags_merged + def _get_cflags(cflags, defines): - cflags = _merge_key_value_cflags(cflags) - cflags = [flag for flag in cflags if _is_cflag_allowed(flag)] + cflags = _merge_key_value_cflags(cflags) + cflags = [flag for flag in cflags if _is_cflag_allowed(flag)] - # Android _may_ set a platform default for _LIBCPP_HARDENING_MODE. If that - # conflicts with the level specified on this target, we'll get build errors. - # - # Allow Android's default to apply to builds where we don't specify one, but - # prefer our default for builds that do. - libcpp_hardening_flag = "_LIBCPP_HARDENING_MODE" - if any(define.startswith(libcpp_hardening_flag) for define in defines): - cflags.append(f"-U{libcpp_hardening_flag}") + # Android _may_ set a platform default for _LIBCPP_HARDENING_MODE. If that + # conflicts with the level specified on this target, we'll get build errors. + # + # Allow Android's default to apply to builds where we don't specify one, but + # prefer our default for builds that do. + libcpp_hardening_flag = "_LIBCPP_HARDENING_MODE" + if any(define.startswith(libcpp_hardening_flag) for define in defines): + cflags.append(f"-U{libcpp_hardening_flag}") - # Consider proper allowlist or denylist if needed - cflags.extend( - sorted(["-D%s" % define.replace("\"", "\\\"") for define in defines])) - return cflags + # Consider proper allowlist or denylist if needed + cflags.extend( + sorted(["-D%s" % define.replace("\"", "\\\"") for define in defines])) + return cflags def _set_linker_script(module, libs): - for lib in libs: - if lib.endswith(".lds"): - module.ldflags.append( - get_linker_script_ldflag(gn_utils.label_to_path(lib))) + for lib in libs: + if lib.endswith(".lds"): + module.ldflags.append( + get_linker_script_ldflag(gn_utils.label_to_path(lib))) def _get_cpp_std(cflags: List[str]) -> Union[str, None]: - cpp_stds = [ - cflag.removeprefix('-std=') for cflag in cflags - if cflag.startswith('-std=') - ] - if cpp_stds: - # There can be multiple cpp std in cflags list. Return the last one as this will - # override any previous version. - return cpp_stds[-1] - return None + cpp_stds = [ + cflag.removeprefix('-std=') for cflag in cflags + if cflag.startswith('-std=') + ] + if cpp_stds: + # There can be multiple cpp std in cflags list. Return the last one as this will + # override any previous version. + return cpp_stds[-1] + return None def _extract_linker_script(ldflags): - new_ldflags = [] - linker_script = None - for flag in ldflags: - if flag.startswith("-Wl,--version-script="): - # Everything after the = is the path and delete all leading ../ - linker_path = re.sub('^(\.\./)+', '', flag.split("=", maxsplit=2)[1]) - assert linker_script is None, f"Found two different linker script for a single target! First script: {linker_script}, Second script: {linker_path}" - linker_script = linker_path - else: - new_ldflags.append(flag) - return new_ldflags, linker_script + new_ldflags = [] + linker_script = None + for flag in ldflags: + if flag.startswith("-Wl,--version-script="): + # Everything after the = is the path and delete all leading ../ + linker_path = re.sub('^(\.\./)+', '', + flag.split("=", maxsplit=2)[1]) + assert linker_script is None, f"Found two different linker script for a single target! First script: {linker_script}, Second script: {linker_path}" + linker_script = linker_path + else: + new_ldflags.append(flag) + return new_ldflags, linker_script def _create_linker_script_filegroup(linker_script_path): - filegroup_name = linker_script_path.replace('/', '_').replace('.', '_') - filegroup_module = Module("filegroup", - f"{MODULE_PREFIX}{filegroup_name}_filegroup", - f"Created to reference {linker_script_path}") - filegroup_module.srcs = [linker_script_path] - # TODO(aymanm): Change the default for build_file_path to be top-level. - filegroup_module.build_file_path = "" - return filegroup_module + filegroup_name = linker_script_path.replace('/', '_').replace('.', '_') + filegroup_module = Module("filegroup", + f"{MODULE_PREFIX}{filegroup_name}_filegroup", + f"Created to reference {linker_script_path}") + filegroup_module.srcs = [linker_script_path] + # TODO(aymanm): Change the default for build_file_path to be top-level. + filegroup_module.build_file_path = "" + return filegroup_module def _is_allowed_ldflag(flag): - return all(not flag.startswith(denied_prefix) for denied_prefix in [ - # Already applied by Soong according to module's attributes. - "--sysroot=", - # Already applied by Soong. - "--target=", - # Throws an error for some unknown reason? - "--unwindlib=", - # Tries to write to disk which is disallowed by Soong. It also - # simply controls the caching behaviour of thinLTO which is - # not essential. - "-Wl,--thinlto-cache-dir=", - # Controls the caching behaviour of thinLTO which is - # not essential. - "-Wl,--thinlto-cache-policy=", - # Controls the threading behaviour of thinLTO which is - # not essential. - "-Wl,--thinlto-jobs=", - # Applied by Soong by default - "-flto=", - # Throws an error currently because GNU_PROPERTY_AARCH64_FEATURE_1_BTI is - # not defined in some object files. This requires further investigation - # to enable. It's fine to disable for now as it has never been enabled in - # HttpEngine. - "-Wl,-z,force-bti", - # Soong handles this automatically based on the lunch options. - "-Wl,-z,max-page-size=", - # Let Soong handle the stripping of debug library according to the - # lunch configuration. - "-Wl,--strip-debug", - # Android is experimenting with XOM(crbug.com/379071663) which conflicts with - # rosegment flag. Disable this flag until XOM has landed, and we have - # an attribute which we can use to enable --no-rosegment. - "-Wl,--no-rosegment", - # This is already the default in AOSP. - "-fuse-ld", - # Specified by a Soong attribute instead. - "-fwhole-program-vtables", - # All MLGO is left best-handled by Soong as we're now employing AFDO - # profiles. - "-Wl,-mllvm,-enable-ml-inliner", - "-Wl,-mllvm,-ml-inliner-model-selector", - "-Wl,-mllvm,-ml-inliner-skip-policy", - ]) + return all(not flag.startswith(denied_prefix) for denied_prefix in [ + # Already applied by Soong according to module's attributes. + "--sysroot=", + # Already applied by Soong. + "--target=", + # Throws an error for some unknown reason? + "--unwindlib=", + # Tries to write to disk which is disallowed by Soong. It also + # simply controls the caching behaviour of thinLTO which is + # not essential. + "-Wl,--thinlto-cache-dir=", + # Controls the caching behaviour of thinLTO which is + # not essential. + "-Wl,--thinlto-cache-policy=", + # Controls the threading behaviour of thinLTO which is + # not essential. + "-Wl,--thinlto-jobs=", + # Applied by Soong by default + "-flto=", + # Throws an error currently because GNU_PROPERTY_AARCH64_FEATURE_1_BTI is + # not defined in some object files. This requires further investigation + # to enable. It's fine to disable for now as it has never been enabled in + # HttpEngine. + "-Wl,-z,force-bti", + # Soong handles this automatically based on the lunch options. + "-Wl,-z,max-page-size=", + # Let Soong handle the stripping of debug library according to the + # lunch configuration. + "-Wl,--strip-debug", + # Android is experimenting with XOM(crbug.com/379071663) which conflicts with + # rosegment flag. Disable this flag until XOM has landed, and we have + # an attribute which we can use to enable --no-rosegment. + "-Wl,--no-rosegment", + # This is already the default in AOSP. + "-fuse-ld", + # Specified by a Soong attribute instead. + "-fwhole-program-vtables", + # All MLGO is left best-handled by Soong as we're now employing AFDO + # profiles. + "-Wl,-mllvm,-enable-ml-inliner", + "-Wl,-mllvm,-ml-inliner-model-selector", + "-Wl,-mllvm,-ml-inliner-skip-policy", + ]) def configure_cc_module(module, cflags, defines, ldflags, libs, main_module, blueprint): - module.cflags = _get_cflags(cflags, defines) - ldflags, linker_script = _extract_linker_script(ldflags) - module.ldflags = [flag for flag in ldflags if _is_allowed_ldflag(flag)] - if linker_script: - # Unfortunately, Soong does not allow accessing linker scripts from parent - # path. So create a filegroup at the top-level Android.bp and reference it instead. - filegroup_module = _create_linker_script_filegroup(linker_script) - blueprint.add_module(filegroup_module) - module.version_script = f":{filegroup_module.name}" - _set_linker_script(module, libs) - for lib in libs: - # Generally library names should be mangled as 'libXXX', unless they - # are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK - # libraries (e.g. "android.hardware.power.stats-V1-cpp") - android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \ - else 'lib' + lib - if lib in shared_library_allowlist: - module.shared_libs.add(android_lib) - # TODO: implement proper cflag parsing. - for flag in cflags: - if '-fexceptions' in flag: - module.cppflags.append('-fexceptions') - cpp_std = _get_cpp_std(cflags) - if cpp_std: - assert main_module.cpp_std is None or main_module.cpp_std == cpp_std, f"Found different CPP version across different architectures!, target name: {main_module.name}, first cpp version: {main_module.cpp_std}, current cpp version: {cpp_std}" - # The -std= compiler option has a dedicated property in Android.bp, called cpp_std. That property - # can only be set at module top level; it cannot be set per-target. However in GN - # cflags are arch-specific, so we will find -std= when running on the - # arch-specific module. Hence we need to go back to the main module and set it there. - main_module.cpp_std = cpp_std + module.cflags = _get_cflags(cflags, defines) + ldflags, linker_script = _extract_linker_script(ldflags) + module.ldflags = [flag for flag in ldflags if _is_allowed_ldflag(flag)] + if linker_script: + # Unfortunately, Soong does not allow accessing linker scripts from parent + # path. So create a filegroup at the top-level Android.bp and reference it instead. + filegroup_module = _create_linker_script_filegroup(linker_script) + blueprint.add_module(filegroup_module) + module.version_script = f":{filegroup_module.name}" + _set_linker_script(module, libs) + for lib in libs: + # Generally library names should be mangled as 'libXXX', unless they + # are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK + # libraries (e.g. "android.hardware.power.stats-V1-cpp") + android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \ + else 'lib' + lib + if lib in shared_library_allowlist: + module.shared_libs.add(android_lib) + # TODO: implement proper cflag parsing. + for flag in cflags: + if '-fexceptions' in flag: + module.cppflags.append('-fexceptions') + cpp_std = _get_cpp_std(cflags) + if cpp_std: + assert main_module.cpp_std is None or main_module.cpp_std == cpp_std, f"Found different CPP version across different architectures!, target name: {main_module.name}, first cpp version: {main_module.cpp_std}, current cpp version: {cpp_std}" + # The -std= compiler option has a dedicated property in Android.bp, called cpp_std. That property + # can only be set at module top level; it cannot be set per-target. However in GN + # cflags are arch-specific, so we will find -std= when running on the + # arch-specific module. Hence we need to go back to the main module and set it there. + main_module.cpp_std = cpp_std def _create_rust_build_script_output_copy_genrule(module_name, path_to_directory, files): - module = Module( - "genrule", module_name, - "Copies generated Rust build script files somewhere the dependent code can find them" - ) - module.srcs = [f"{path_to_directory}/{file_name}" for file_name in files] - module.cmd = "cp $(in) $(genDir)" - module.out = files - return module + module = Module( + "genrule", module_name, + "Copies generated Rust build script files somewhere the dependent code can find them" + ) + module.srcs = [f"{path_to_directory}/{file_name}" for file_name in files] + module.cmd = "cp $(in) $(genDir)" + module.out = files + return module + def set_module_include_dirs(module, cflags, include_dirs): - for flag in cflags: - if '-isystem' in flag: - module.include_dirs.add( - f"external/cronet/{IMPORT_CHANNEL}/{flag[len('-isystem../../'):]}") + for flag in cflags: + if '-isystem' in flag: + module.include_dirs.add( + f"external/cronet/{IMPORT_CHANNEL}/{flag[len('-isystem../../'):]}" + ) - depends_on_binder_ndk = any("libbinder_ndk_cpp" in include_dir - for include_dir in include_dirs) - if depends_on_binder_ndk: - module.shared_libs.add("libbinder_ndk") - include_dirs = [ - include_dir for include_dir in include_dirs - if "libbinder_ndk_cpp" not in include_dir + depends_on_binder_ndk = any("libbinder_ndk_cpp" in include_dir + for include_dir in include_dirs) + if depends_on_binder_ndk: + module.shared_libs.add("libbinder_ndk") + include_dirs = [ + include_dir for include_dir in include_dirs + if "libbinder_ndk_cpp" not in include_dir + ] + # Adding include_dirs is necessary due to source_sets / filegroups + # which do not properly propagate include directories. + # Filter any directory inside //out as a) this directory does not exist for + # aosp / soong builds and b) the include directory should already be + # configured via library dependency. + # Note: include_dirs is used instead of local_include_dirs as an Android.bp + # can't access other directories outside of its current directory. This + # is worked around by using include_dirs. + module.include_dirs.update([ + f"external/cronet/{IMPORT_CHANNEL}/{gn_utils.label_to_path(d)}" + for d in include_dirs if not d.startswith('//out') + ]) + # Remove prohibited include directories + module.include_dirs = [ + d for d in module.include_dirs if d not in include_dirs_denylist ] - # Adding include_dirs is necessary due to source_sets / filegroups - # which do not properly propagate include directories. - # Filter any directory inside //out as a) this directory does not exist for - # aosp / soong builds and b) the include directory should already be - # configured via library dependency. - # Note: include_dirs is used instead of local_include_dirs as an Android.bp - # can't access other directories outside of its current directory. This - # is worked around by using include_dirs. - module.include_dirs.update([ - f"external/cronet/{IMPORT_CHANNEL}/{gn_utils.label_to_path(d)}" - for d in include_dirs if not d.startswith('//out') - ]) - # Remove prohibited include directories - module.include_dirs = [ - d for d in module.include_dirs if d not in include_dirs_denylist - ] def create_aidl_module(bp_module_name, target, blueprint): - module = Module("aidl_interface", bp_module_name, target.name) - module.unstable = True - module.include_dirs = [ - f"external/cronet/{IMPORT_CHANNEL}/{path}" - for path in sorted(target.aidl_includes) - ] - # This is necessary as Soong adds a dependency behind the scenes ;( - # https://cs.android.com/android/platform/superproject/main/+/main:system/tools/aidl/build/aidl_interface_backends.go;l=162 - module.visibility.add("//system/tools/aidl/build") - filegroup_module_name = f"{bp_module_name}_filegroup" - module.srcs = {f":{filegroup_module_name}"} - # Filegroup exists here because Soong's genrule for AIDL contains a bug where there's - # a discrepancy between the expected generated file path and the actual path. - # See crbug.com/418726870 for more information. - filegroup_module = Module("filegroup", filegroup_module_name, target.name) - filegroup_module.srcs = [ - gn_utils.label_to_path(src) for src in sorted(target.sources) - ] - filegroup_module.build_file_path = target.build_file_path - # The following lines will trim an absolute path to the path - # of the java package. There's an assumption here that AIDL files - # live in java-kind packages. - # e.g. A/B/C/src/package/path/path.aidl -> A/B/C - source_file_path = list(filegroup_module.srcs)[0] - path_to_package = source_file_path[:source_file_path.find("src/") + - len("src/")] - assert all( - src.startswith(path_to_package) for src in filegroup_module.srcs - ), f"AIDL module {target.name} has sources from different packages which is not supported." - filegroup_module.path = path_to_package - blueprint.add_module(filegroup_module) - return (module, ) + module = Module("aidl_interface", bp_module_name, target.name) + module.unstable = True + module.include_dirs = [ + f"external/cronet/{IMPORT_CHANNEL}/{path}" + for path in sorted(target.aidl_includes) + ] + # This is necessary as Soong adds a dependency behind the scenes ;( + # https://cs.android.com/android/platform/superproject/main/+/main:system/tools/aidl/build/aidl_interface_backends.go;l=162 + module.visibility.add("//system/tools/aidl/build") + filegroup_module_name = f"{bp_module_name}_filegroup" + module.srcs = {f":{filegroup_module_name}"} + # Filegroup exists here because Soong's genrule for AIDL contains a bug where there's + # a discrepancy between the expected generated file path and the actual path. + # See crbug.com/418726870 for more information. + filegroup_module = Module("filegroup", filegroup_module_name, target.name) + filegroup_module.srcs = [ + gn_utils.label_to_path(src) for src in sorted(target.sources) + ] + filegroup_module.build_file_path = target.build_file_path + # The following lines will trim an absolute path to the path + # of the java package. There's an assumption here that AIDL files + # live in java-kind packages. + # e.g. A/B/C/src/package/path/path.aidl -> A/B/C + source_file_path = list(filegroup_module.srcs)[0] + path_to_package = source_file_path[:source_file_path.find("src/") + + len("src/")] + assert all( + src.startswith(path_to_package) for src in filegroup_module.srcs + ), f"AIDL module {target.name} has sources from different packages which is not supported." + filegroup_module.path = path_to_package + blueprint.add_module(filegroup_module) + return (module, ) + def create_modules_from_target(blueprint, gn, gn_target_name, parent_gn_type, is_test_target): - """Generate module(s) for a given GN target. + """Generate module(s) for a given GN target. Given a GN target name, generate one or more corresponding modules into a blueprint. Most of the time this will only generate one module, with some @@ -2948,759 +3004,784 @@ gn_target_name: GN target for module generation. parent_gn_type: GN type of the parent node. """ - bp_module_name = label_to_module_name(gn_target_name) - target = gn.get_target(gn_target_name) + bp_module_name = label_to_module_name(gn_target_name) + target = gn.get_target(gn_target_name) - # Append __java suffix to actions reachable from java_library. This is necessary - # to differentiate them from cc actions. - # This means that a GN action of name X will be translated to two different modules of names - # X and X__java(only if X is reachable from a java target). - if target.type == "action" and parent_gn_type == "java_library": - bp_module_name += "__java" + # Append __java suffix to actions reachable from java_library. This is necessary + # to differentiate them from cc actions. + # This means that a GN action of name X will be translated to two different modules of names + # X and X__java(only if X is reachable from a java target). + if target.type == "action" and parent_gn_type == "java_library": + bp_module_name += "__java" - target_types_to_hash_module_name = [ - "rust_executable", - "rust_library", - "rust_proc_macro", - ] - if target.type in target_types_to_hash_module_name: - # "lib{crate_name}" must be a prefix of the module name, this is a Soong - # restriction. - # https://cs.android.com/android/_/android/platform/build/soong/+/31934a55a8a1f9e4d56d68810f4a646f12ab6eb5:rust/library.go;l=724;drc=fdec8723d574daf54b956cc0f6dc879087da70a6;bpv=0;bpt=0 - # Use the hash of the module_name instead of the entire name otherwise we will - # exceed the maximum file name length (b/376452102). - bp_module_hash = hashlib.sha256( - bp_module_name.encode('utf-8')).hexdigest()[:4] - bp_module_name = f"lib{target.crate_name}__{bp_module_hash}" - - if bp_module_name in blueprint.modules: - return (blueprint.modules[bp_module_name], ) - - log.info('create modules for %s (%s)', target.name, target.type) - - if gn2bp_common.is_rust_build_script(target.script): - # Build scripts are generated via `generate_build_scripts_output.py`. See the header - # of that script for more details. - generated_files = [ - output.split("/")[-1] for output in target.outputs - if output.endswith(".rs") and not output.endswith("/cargo_flags.rs") + target_types_to_hash_module_name = [ + "rust_executable", + "rust_library", + "rust_proc_macro", ] - if len(generated_files) == 0: - # No files were generated by this build script. Just ignore it and return None. - return (None, ) - # The `generated_outputs` is hardcoded as we assume that the `generate_build_scripts_output.py` script has executed - # and generated all the necessary files in that destination. This creates some kind of hard dependencies between - # those two scripts. - # TODO(b/447593242): Find a better way to indicate to GN2BP that generate_build_scripts_output has generated those files. - # TODO(b/447592983): Use architecture-specific fields instead of harcoding arm64. - # Rust code typically consumes generated files using the following pattern: - # include!(concat!(env!("OUT_DIR"), "/somefile.rs")); - # Because this uses OUT_DIR the generated files will not be found if we just leave this - # in the source tree - we need to copy them to the output directory. Hence this genrule. - module = _create_rust_build_script_output_copy_genrule( - bp_module_name, - f"{target.rust_source_dir}/gn2bp_rust_build_script_outputs/arm64", - generated_files) - blueprint.add_module(module) - return (module, ) + if target.type in target_types_to_hash_module_name: + # "lib{crate_name}" must be a prefix of the module name, this is a Soong + # restriction. + # https://cs.android.com/android/_/android/platform/build/soong/+/31934a55a8a1f9e4d56d68810f4a646f12ab6eb5:rust/library.go;l=724;drc=fdec8723d574daf54b956cc0f6dc879087da70a6;bpv=0;bpt=0 + # Use the hash of the module_name instead of the entire name otherwise we will + # exceed the maximum file name length (b/376452102). + bp_module_hash = hashlib.sha256( + bp_module_name.encode('utf-8')).hexdigest()[:4] + bp_module_name = f"lib{target.crate_name}__{bp_module_hash}" - if target.type == 'executable': - if target.testonly: - module_type = 'cc_test' - else: - # Can be used for both host and device targets. - module_type = 'cc_binary' - modules = (Module(module_type, bp_module_name, gn_target_name), ) - elif target.type == 'rust_executable': - modules = (Module("rust_binary", bp_module_name, gn_target_name), ) - elif target.type == "rust_library": - # Here we have to choose between rust_library_rlib and rust_ffi_static. - # - # Ideally we should pick rust_library_rlib if there are rust_library - # dependents, or rust_ffi_static if there are cc_library dependents. - # This is a bit tricky, however, because it's theoretically possible for - # *both* Rust and C++ code to directly depend on the library. - # - # In practice, there is currently no real difference between - # rust_library_rlib and rust_ffi_static as far as the actual build process - # is concerned - they are practically interchangeable. So, to keep things - # simple, we just arbitrarily pick one - here rust_ffi_static on - # suggestion of AOSP Rust people. See http://b/383552450. - # - # This decision may need to be revisited if the AOSP build system starts - # treating rust_library_rlib and rust_ffi_static differently. - modules = (Module("rust_ffi_static", bp_module_name, gn_target_name), ) - elif target.type == "rust_proc_macro": - modules = (Module("rust_proc_macro", bp_module_name, gn_target_name), ) - elif target.type in ['static_library', 'source_set']: - modules = (Module('cc_library_static', bp_module_name, gn_target_name), ) - elif target.type == 'shared_library': - modules = (Module('cc_library_shared', bp_module_name, gn_target_name), ) - elif target.type == 'proto_library': - modules = create_proto_modules(blueprint, gn, target, is_test_target) - if modules is None: - return () - elif target.type == "rust_bindgen": - modules = (create_bindgen_module(blueprint, target, bp_module_name), ) - elif target.type == 'action': - module = create_action_module( - blueprint, gn, target, - 'java_genrule' if parent_gn_type == "java_library" else 'cc_genrule', - is_test_target) - module.jni_zero_target_type = get_jni_zero_target_type(target) - modules = (module, ) - elif target.type == 'action_foreach': - if target.script == "//third_party/rust/cxx/chromium_integration/run_cxxbridge.py": - modules = create_rust_cxx_modules(blueprint, gn, target, is_test_target) - else: - modules = create_action_foreach_modules(blueprint, gn, target, - is_test_target) - elif target.type == 'copy': - # Copy targets are not supported: currently, we stop traversing the - # dependency tree when we encounter one. - return () - elif target.type == 'java_library': - modules = (create_java_module(bp_module_name, target, blueprint), ) - elif target.type == 'aidl_interface': - modules = create_aidl_module(bp_module_name, target, blueprint) - else: - # Note we don't have to handle `group` targets because parse_gn_desc() never - # returns any; it just recurses through them and bubbles their dependencies - # upwards. - raise Exception('Unknown target %s (%s)' % (target.name, target.type)) + if bp_module_name in blueprint.modules: + return (blueprint.modules[bp_module_name], ) - for module in modules: - blueprint.add_module(module) - if target.type not in ['action', 'action_foreach', 'aidl_interface']: - # Actions should get their srcs from their corresponding ActionSanitizer as actionSanitizer - # filters srcs differently according to the type of the action. - module.srcs.update( - gn_utils.label_to_path(src) for src in target.sources - if is_supported_source_file(src)) + log.info('create modules for %s (%s)', target.name, target.type) - # Add arch-specific properties - for arch_name, arch in target.get_archs().items(): - module.target[arch_name].srcs.update( - gn_utils.label_to_path(src) for src in arch.sources - if is_supported_source_file(src)) - - module.rtti = target.rtti - - if target.type in gn_utils.LINKER_UNIT_TYPES: - configure_cc_module(module, target.cflags, target.defines, target.ldflags, - target.libs, module, blueprint) - set_module_include_dirs(module, target.cflags, target.include_dirs) - # TODO: set_module_xxx is confusing, apply similar function to module and target in better way. - for arch_name, arch in target.get_archs().items(): - # TODO(aymanm): Make libs arch-specific. - configure_cc_module(module.target[arch_name], arch.cflags, arch.defines, - arch.ldflags, arch.libs, module, blueprint) - # -Xclang -target-feature -Xclang +mte are used to enable MTE (Memory Tagging Extensions). - # Flags which does not start with '-' could not be in the cflags so enabling MTE by - # -march and -mcpu Feature Modifiers. MTE is only available on arm64. This is needed for - # building //base/allocator/partition_allocator:partition_alloc for arm64. - if '+mte' in arch.cflags and arch_name == 'android_arm64': - module.target[arch_name].cflags.add('-march=armv8-a+memtag') - set_module_include_dirs(module.target[arch_name], arch.cflags, - arch.include_dirs) - - if not module.type == "rust_proc_macro": - # rust_proc_macro modules does not support the fields of `host_supported` - # or `device_supported`. In a different world, we would have classes for - # each different module that specifies what it can support to avoid - # those kind of conditions. - # - # See go/android.bp for additional information. - module.host_supported = target.host_supported() - module.device_supported = target.device_supported() - - module.gn_type = target.type - module.build_file_path = target.build_file_path - # Chromium does not use visibility at all, in order to avoid visibility issues - # in AOSP. Make every module visible to any module in external/cronet. - module.visibility.add("//external/cronet:__subpackages__") - - if module.type in ["rust_proc_macro", "rust_binary", "rust_ffi_static"]: - module.crate_name = target.crate_name - module.crate_root = gn_utils.label_to_path(target.crate_root) - if target.rust_package_version: - module.cargo_env_compat = True - module.cargo_pkg_version = target.rust_package_version - module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP - module.apex_available = [tethering_apex] - for arch_name, arch in target.get_archs().items(): - _set_rust_flags(module.target[arch_name], arch.rust_flags, arch_name) - - if module.type in ("rust_proc_macro", "rust_binary", "rust_ffi_static", - "rust_bindgen"): - # We may end up (in)directly depending on cc modules, e.g. through the - # rust bindgen "generated headers" library we may generate. Our cc modules - # set this. We need to be consistent, otherwise Soong will complain about - # the incompatible dependency. - module.target['host'].compile_multilib = '64' - - if module.type in ("rust_bindgen", "rust_ffi_static", "cc_genrule", - "cc_library_static", "cc_binary", "rust_binary"): - # If we don't add this, then some types of AOSP builds fail due to an - # issue with proc_macro2 - see https://crbug.com/392704960. - # Note: technically we only need this on modules that ultimately depend - # on proc_macro2, but there doesn't seem to be any downside to just set - # it everywhere, so for simplicity we do just that. - module.host_cross_supported = False - - if module.is_genrule(): - module.apex_available.add(tethering_apex) - - if (module.is_compiled() and not module.type.startswith("java") - and not module.type.startswith("rust")): - # Don't try to inject library/source dependencies into genrules or - # filegroups because they are not compiled in the traditional sense. - module.defaults = [cc_defaults_module] - - if module.type == 'cc_library_static': - module.export_generated_headers = module.generated_headers - - if module.type == 'cc_library_shared': - output_name = target.output_name - if output_name is None: - module.stem = 'lib' + target.get_target_name().removesuffix( - gn_utils.TESTING_SUFFIX) - else: - module.stem = 'lib' + output_name - - # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)). - all_deps = [(dep_name, 'common') for dep_name in target.proto_deps] - for arch_name, arch in target.arch.items(): - all_deps += [(dep_name, arch_name) for dep_name in arch.deps] - - if gn_target_name in replace_deps: - # Do not recurse into replace_deps target's dependencies. - return (module, ) - - # Sort deps before iteration to make result deterministic. - for (dep_name, arch_name) in sorted(all_deps): - module_target = module.target[ - arch_name] if arch_name != 'common' else module - # |builtin_deps| override GN deps with Android-specific ones. See the - # config in the top of this file. - if dep_name in builtin_deps: - builtin_deps[dep_name](module.java_unfiltered_module - if module.is_java_top_level_module() else module, - arch_name) - continue - - for dep_module in create_modules_from_target(blueprint, gn, dep_name, - target.type, is_test_target): - if dep_name in replace_deps: - replace_deps[dep_name](module.java_unfiltered_module if - module.is_java_top_level_module() else module, - arch_name) - continue - - if dep_module is None: - continue - - # TODO: Proper dependency check for genrule. - # Currently, only propagating genrule dependencies. - # Also, currently, all the dependencies are propagated upwards. - # in gn, public_deps should be propagated but deps should not. - # Not sure this information is available in the desc.json. - # Following rule works for adding android_runtime_jni_headers to base:base. - # If this doesn't work for other target, hardcoding for specific target - # might be better. - if module.is_genrule() and dep_module.is_genrule(): - if module_target.gn_type != "proto_library": - # proto_library are treated differently because each proto action - # is split into two different targets, a cpp target and a header target. - # the cpp target is used as the entry point to the proto action, hence - # it should not be propagated as a genrule header because it generates - # cpp files only. - module_target.genrule_headers.add(dep_module.name) - module_target.genrule_headers.update(dep_module.genrule_headers) - - # For filegroups, and genrule, recurse but don't apply the - # deps. - if not module.is_compiled() or module.is_genrule(): - continue - - # Drop compiled modules that doesn't provide any benefit. This is mostly - # applicable to source_sets when converted to cc_static_library, sometimes - # the source set only has header files which are dropped so the module becomes empty. - # is_compiled is there to prevent dropping of genrules. - if dep_module.is_compiled() and not dep_module.has_input_files(): - continue - - module_is_cc = module.type in [ - 'cc_library_shared', 'cc_binary', 'cc_library_static' + if gn2bp_common.is_rust_build_script(target.script): + # Build scripts are generated via `generate_build_scripts_output.py`. See the header + # of that script for more details. + generated_files = [ + output.split("/")[-1] for output in target.outputs if + output.endswith(".rs") and not output.endswith("/cargo_flags.rs") ] + if len(generated_files) == 0: + # No files were generated by this build script. Just ignore it and return None. + return (None, ) + # The `generated_outputs` is hardcoded as we assume that the `generate_build_scripts_output.py` script has executed + # and generated all the necessary files in that destination. This creates some kind of hard dependencies between + # those two scripts. + # TODO(b/447593242): Find a better way to indicate to GN2BP that generate_build_scripts_output has generated those files. + # TODO(b/447592983): Use architecture-specific fields instead of harcoding arm64. + # Rust code typically consumes generated files using the following pattern: + # include!(concat!(env!("OUT_DIR"), "/somefile.rs")); + # Because this uses OUT_DIR the generated files will not be found if we just leave this + # in the source tree - we need to copy them to the output directory. Hence this genrule. + module = _create_rust_build_script_output_copy_genrule( + bp_module_name, + f"{target.rust_source_dir}/gn2bp_rust_build_script_outputs/arm64", + generated_files) + blueprint.add_module(module) + return (module, ) - if dep_module.type == 'cc_library_shared': - module_target.shared_libs.add(dep_module.name) - elif dep_module.type == 'cc_library_static' or ( - dep_module.type == "rust_ffi_static" and module_is_cc): - if module.type in [ - 'cc_library_shared', 'cc_binary', 'rust_binary', - 'cc_library_static' - ]: - if module.type != 'cc_library_static': - module_target.whole_static_libs.add(dep_module.name) - else: - module_target.generated_headers.update( - dep_module.generated_headers) - module_target.shared_libs.update(dep_module.shared_libs) - module_target.header_libs.update(dep_module.header_libs) - elif module.type in ('rust_ffi_static', 'rust_bindgen'): - module_target.shared_libs.update(dep_module.shared_libs) - elif module.type == 'rust_proc_macro' and dep_module.type == 'cc_library_static': - # rust_proc_macro cannot depend on cc_library_static. Having said - # that, we still need these dependencies to further bubble them up - # to rust_proc_macro targets dependencies, so simply ignore them. - # See https:/crbug.com/417429009. - pass - else: - raise Exception( - f"Cannot add {dep_module.name} ({dep_module.type}) to {module.name} ({module.type})" - ) - elif dep_module.type == "rust_bindgen": - if module.type.startswith("rust"): - # Soong does not support using `rust_bindgen` modules directly as an input because - # it produces more than a single output (b/467420029). Create an intermediate - # genrule that copies that rust file and use it instead. - intermediate_target = _create_extract_rust_files_target( - dep_module, blueprint).name - module.srcs.add(":" + intermediate_target) - if module.crate_root and module.crate_root.startswith("out/"): - # Sometimes the crate_root is an output of another module which is indicated - # by a path starting with "out/". The only case where this happens at the moment - # is when a rust_library is created for the rust_bindgen output. - module.crate_root = f":{intermediate_target}" - else: - module.srcs.add(":" + dep_module.name) - if module_target.type == "cc_library_static": - # This is a bindgen _static_fns GN target. We need to translate that - # to the Soong rust_bindgen "static inline library" concept. - - # AOSP Rust team wants every bindgen static inline library module to - # have a "lib" prefix. Due to the way Chromium //build/rust bindgen - # generator rules work, we know the _static_fns target is only - # referenced by its corresponding bindgen target and nothing else; - # therefore, we can safely assume we are only going to enter this - # path once, so there is no need to protect against the prefix being - # added multiple times - nor is there a need to go back and fix - # previous references. - module.name = "lib" + module.name - # rust_bindgen generates a .c / .cc file which has include - # defined from the root of the android tree. - module_target.include_dirs.append(".") - # The rust_bindgen has to know the name of the cc library which is going to - # consume it. We don't know that until we add the `rust_bindgen` as a dep. - dep_module.static_inline_library = module.name - elif dep_module.type == "rust_ffi_static": - if module.type in [ - "rust_binary", "rust_proc_macro", "rust_ffi_static" - ]: - module_target.rustlibs.add(dep_module.name) - elif dep_module.type == "rust_proc_macro": - module_target.proc_macros.add(dep_module.name) - elif dep_module.type == "aidl_interface": - # See https://cs.android.com/android/platform/superproject/main/+/main:system/tools/aidl/build/aidl_interface_backends.go - # for how those modules "-lang-source" is generated. - if module.type.startswith("cc_"): - module.srcs.add(f":{dep_module.name}-ndk-source") - module.generated_headers.add(f"{dep_module.name}-ndk-source") - module.export_generated_headers.add(f"{dep_module.name}-ndk-source") - elif module.type.startswith("java_"): - module.srcs.add(f":{dep_module.name}-java-source") - elif module.type.startswith("rust_"): - module.srcs.add(f":{dep_module.name}-rust-source") - elif dep_module.type == 'cc_genrule': - if dep_module.genrule_headers: - if module.type == "rust_ffi_static": - # Don't bubble up generated_headers on Rust modules, as that doesn't make sense - # (Rust cannot use C++ headers directly) and is not supported anyway. See also - # https://crbug.com/405987939. - # TODO: https://crbug.com/406267472 - how we end up in this situation in the - # first place is not entirely clear. We may have to revisit how generated - # headers interact with cxx/bindgen targets. - pass - elif module.type == "rust_bindgen": - # rust_bindgen modules don't support the `generated_headers` attribute; - # see http://crbug.com/394615281. We work around this limitation by - # inserting a module whose sole purpose is to export the generated - # headers, and then depending on that. See also - # http://crbug.com/394069879. - module_target.header_libs.add( - create_generated_headers_export_module(blueprint, - dep_module).name) - else: - module_target.generated_headers.update(dep_module.genrule_headers) - module_target.srcs.update(dep_module.genrule_srcs) - module_target.shared_libs.update(dep_module.genrule_shared_libs) - module_target.header_libs.update(dep_module.genrule_header_libs) - elif dep_module.is_java_top_level_module(): - # A module depending on a module with system_current sdk version should also compile against - # the system sdk. This is because a module's SDK API surface should be >= its deps SDK API surface. - # And system_current has a larger API surface than current or module_current. - if dep_module.sdk_version == 'system_current': - module_target.sdk_version = module_target.java_unfiltered_module.sdk_version = 'system_current' - - module_target.static_libs.add(dep_module.name) - - # `create_java_module()` implements Chromium's Java jar filtering - # feature. Here we deal with another subtlety around that feature, - # which is how jar filtering affects the inputs of the various build - # steps. - # - # When Chromium runs javac, it runs it against the raw output of - # javac from the dependencies. In other words, the javac classpath is - # made of *unfiltered* jars. However, it is the *filtered* jars that - # eventually get shipped in the final build outputs. javac running - # against unfiltered jars is important - some targets rely on this - # (e.g. //base:log_java pulling BuildConfig from - # //build/android:build_java), so we need to preserve this behavior. - # - # Reproducing this in Soong is somewhat of a headache. The difficulty - # is, in Soong `static_libs` dependencies on `java_library` modules - # automatically bubble up the dependency tree. If we just list - # `__unfiltered` modules in `static_libs`, the unfiltered jars will - # propagate all the way to the final build outputs, which is not what - # we want. - # - # To solve this problem, we generate two dependency trees: a filtered - # tree that links top-level Java modules together, and an unfiltered - # tree that links unfiltered Java modules together. When one depends - # on the top-level modules one gets the filtered jars; when one - # depends on the unfiltered module one gets the unfiltered jars. (This - # is the reason why we have to have a separate top-level module and - # can't just merge it with the filtered module: the dependency tree of - # filtered modules indirectly includes unfiltered jars, which we don't - # want to pull in top-level modules.) - # - # A keen eye will notice we still have a problem, because the - # unfiltered dependencies of unfiltered modules will bubble up through - # filtered modules and then to top-level targets. This would result in - # top-level targets producing unfiltered jars, which is not what we - # want. - # - # To solve this problem, we don't use `static_libs` on unfiltered - # modules. Instead, we use `libs`. Indeed, Soong does *not* bubble up - # `libs` dependencies, thus preventing unfiltered jars from bubbling - # up and appearing in final build outputs. - # - # TODO: as if this wasn't complicated enough, in GN a `java_library` - # can use a flag, `prevent_excluded_classes_from_classpath`, that - # flips the above behavior and makes dependent compile targets pull - # the *filtered* jars in the javac classpath instead of the unfiltered - # ones. This flag is notably used in `generate_jni()` autogenerated - # java_library targets to prevent the jni_zero placeholder classes - # from bubbling up and potentially conflicting with their real - # counterparts up the build tree. We currently do not support this - # flag, i.e. we behave as if it is false. Surprisingly the resulting - # build rules work anyway - presumably by sheer luck (classpath - # ordering maybe?). In the future we may have to support it. This - # should be easy - just depend on the filtered target instead of the - # unfiltered target when the flag is true on the dependency. - # - # For even more more background, see https://crbug.com/397396295. - module_target.java_unfiltered_module.libs.add( - dep_module.java_unfiltered_module.name) - # As mentioned above, `libs` does not bubble up, so we have to - # recurse and collect all the transitive dependencies ourselves. This - # is not necessary when using `static_libs` as Soong does that for us - # at build time. - # - # (You may wonder: "wait, doesn't Chromium already enforce that a Java - # target list all the classes it refers to in its direct dependencies? - # Why do we need to pull indirect dependencies then?" Well the problem - # is javac needs to see some of the indirect dependencies in some - # cases - see https://crbug.com/400952169#comment4 - which means the - # direct dependencies may not be enough.) - module_target.java_unfiltered_module.libs.update( - dep_module.java_unfiltered_module.libs) - elif dep_module.type in ['genrule', 'java_genrule']: - if dep_module.jni_zero_target_type == JniZeroTargetType.GENERATOR: - # TODO: we are special-casing jni_zero here. Ideally this should be - # handled more generically, by making gn2bp understand the general - # concept of a target depending on only a subset of the outputs of - # an action. - _, placeholder_path = get_jni_zero_generator_proxy_and_placeholder_paths( - dep_module) - if placeholder_path in target.inputs: - # The target depends on both jni_zero generator outputs (proxy and - # placeholder). We can simply pull both of them at the same time - # by depending on the jni_zero generator module directly. In - # practice this branch is taken when a standalone jni_zero library - # is being built separately from the JNI user code, such as the - # java_library generated by jni_zero's generate_jni() GN rule. One - # example is //base:command_line_jni_java. - module_target.srcs.add(":" + dep_module.name) - else: - # The target only depends on the generated proxy classes but not - # the placeholder classes. Typically this happens when the - # proxy classes are being compiled alongside the JNI user code: in - # this case there is no need for the placeholder classes since the - # user code provides all the necessary definitions. One example is - # //components/cronet/android:cronet_impl_native_java. In this - # situation it is imperative that we do *not* pull the - # placeholder classes, as they would conflict with user code. See - # https://crbug.com/397396295 for more background. - proxy_only_module = create_jni_zero_proxy_only_module(dep_module) - blueprint.add_module(proxy_only_module) - module_target.srcs.add(f":{proxy_only_module.name}") - else: - module_target.srcs.add(":" + dep_module.name) + if target.type == 'executable': + if target.testonly: + module_type = 'cc_test' else: - raise Exception( - 'Unsupported arch-specific dependency %s of target %s with type %s' - % (dep_module.name, target.name, dep_module.type)) + # Can be used for both host and device targets. + module_type = 'cc_binary' + modules = (Module(module_type, bp_module_name, gn_target_name), ) + elif target.type == 'rust_executable': + modules = (Module("rust_binary", bp_module_name, gn_target_name), ) + elif target.type == "rust_library": + # Here we have to choose between rust_library_rlib and rust_ffi_static. + # + # Ideally we should pick rust_library_rlib if there are rust_library + # dependents, or rust_ffi_static if there are cc_library dependents. + # This is a bit tricky, however, because it's theoretically possible for + # *both* Rust and C++ code to directly depend on the library. + # + # In practice, there is currently no real difference between + # rust_library_rlib and rust_ffi_static as far as the actual build process + # is concerned - they are practically interchangeable. So, to keep things + # simple, we just arbitrarily pick one - here rust_ffi_static on + # suggestion of AOSP Rust people. See http://b/383552450. + # + # This decision may need to be revisited if the AOSP build system starts + # treating rust_library_rlib and rust_ffi_static differently. + modules = (Module("rust_ffi_static", bp_module_name, gn_target_name), ) + elif target.type == "rust_proc_macro": + modules = (Module("rust_proc_macro", bp_module_name, gn_target_name), ) + elif target.type in ['static_library', 'source_set']: + modules = (Module('cc_library_static', bp_module_name, + gn_target_name), ) + elif target.type == 'shared_library': + modules = (Module('cc_library_shared', bp_module_name, + gn_target_name), ) + elif target.type == 'proto_library': + modules = create_proto_modules(blueprint, gn, target, is_test_target) + if modules is None: + return () + elif target.type == "rust_bindgen": + modules = (create_bindgen_module(blueprint, target, bp_module_name), ) + elif target.type == 'action': + module = create_action_module( + blueprint, gn, target, 'java_genrule' if parent_gn_type + == "java_library" else 'cc_genrule', is_test_target) + module.jni_zero_target_type = get_jni_zero_target_type(target) + modules = (module, ) + elif target.type == 'action_foreach': + if target.script == "//third_party/rust/cxx/chromium_integration/run_cxxbridge.py": + modules = create_rust_cxx_modules(blueprint, gn, target, + is_test_target) + else: + modules = create_action_foreach_modules(blueprint, gn, target, + is_test_target) + elif target.type == 'copy': + # Copy targets are not supported: currently, we stop traversing the + # dependency tree when we encounter one. + return () + elif target.type == 'java_library': + modules = (create_java_module(bp_module_name, target, blueprint), ) + elif target.type == 'aidl_interface': + modules = create_aidl_module(bp_module_name, target, blueprint) + else: + # Note we don't have to handle `group` targets because parse_gn_desc() never + # returns any; it just recurses through them and bubbles their dependencies + # upwards. + raise Exception('Unknown target %s (%s)' % (target.name, target.type)) - if module.is_java_top_level_module(): - # The Java top-level module is not the one doing the actual compiling; the - # unfiltered module is, so it should get the srcs. - module.java_unfiltered_module.srcs = module.srcs - module.srcs = () + for module in modules: + blueprint.add_module(module) + if target.type not in ['action', 'action_foreach', 'aidl_interface']: + # Actions should get their srcs from their corresponding ActionSanitizer as actionSanitizer + # filters srcs differently according to the type of the action. + module.srcs.update( + gn_utils.label_to_path(src) for src in target.sources + if is_supported_source_file(src)) - return modules + # Add arch-specific properties + for arch_name, arch in target.get_archs().items(): + module.target[arch_name].srcs.update( + gn_utils.label_to_path(src) for src in arch.sources + if is_supported_source_file(src)) + + module.rtti = target.rtti + + if target.type in gn_utils.LINKER_UNIT_TYPES: + configure_cc_module(module, target.cflags, target.defines, + target.ldflags, target.libs, module, blueprint) + set_module_include_dirs(module, target.cflags, target.include_dirs) + # TODO: set_module_xxx is confusing, apply similar function to module and target in better way. + for arch_name, arch in target.get_archs().items(): + # TODO(aymanm): Make libs arch-specific. + configure_cc_module(module.target[arch_name], arch.cflags, + arch.defines, arch.ldflags, arch.libs, + module, blueprint) + # -Xclang -target-feature -Xclang +mte are used to enable MTE (Memory Tagging Extensions). + # Flags which does not start with '-' could not be in the cflags so enabling MTE by + # -march and -mcpu Feature Modifiers. MTE is only available on arm64. This is needed for + # building //base/allocator/partition_allocator:partition_alloc for arm64. + if '+mte' in arch.cflags and arch_name == 'android_arm64': + module.target[arch_name].cflags.add( + '-march=armv8-a+memtag') + set_module_include_dirs(module.target[arch_name], arch.cflags, + arch.include_dirs) + + if not module.type == "rust_proc_macro": + # rust_proc_macro modules does not support the fields of `host_supported` + # or `device_supported`. In a different world, we would have classes for + # each different module that specifies what it can support to avoid + # those kind of conditions. + # + # See go/android.bp for additional information. + module.host_supported = target.host_supported() + module.device_supported = target.device_supported() + + module.gn_type = target.type + module.build_file_path = target.build_file_path + # Chromium does not use visibility at all, in order to avoid visibility issues + # in AOSP. Make every module visible to any module in external/cronet. + module.visibility.add("//external/cronet:__subpackages__") + + if module.type in [ + "rust_proc_macro", "rust_binary", "rust_ffi_static" + ]: + module.crate_name = target.crate_name + module.crate_root = gn_utils.label_to_path(target.crate_root) + if target.rust_package_version: + module.cargo_env_compat = True + module.cargo_pkg_version = target.rust_package_version + module.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP + module.apex_available = [tethering_apex] + for arch_name, arch in target.get_archs().items(): + _set_rust_flags(module.target[arch_name], arch.rust_flags, + arch_name) + + if module.type in ("rust_proc_macro", "rust_binary", "rust_ffi_static", + "rust_bindgen"): + # We may end up (in)directly depending on cc modules, e.g. through the + # rust bindgen "generated headers" library we may generate. Our cc modules + # set this. We need to be consistent, otherwise Soong will complain about + # the incompatible dependency. + module.target['host'].compile_multilib = '64' + + if module.type in ("rust_bindgen", "rust_ffi_static", "cc_genrule", + "cc_library_static", "cc_binary", "rust_binary"): + # If we don't add this, then some types of AOSP builds fail due to an + # issue with proc_macro2 - see https://crbug.com/392704960. + # Note: technically we only need this on modules that ultimately depend + # on proc_macro2, but there doesn't seem to be any downside to just set + # it everywhere, so for simplicity we do just that. + module.host_cross_supported = False + + if module.is_genrule(): + module.apex_available.add(tethering_apex) + + if (module.is_compiled() and not module.type.startswith("java") + and not module.type.startswith("rust")): + # Don't try to inject library/source dependencies into genrules or + # filegroups because they are not compiled in the traditional sense. + module.defaults = [cc_defaults_module] + + if module.type == 'cc_library_static': + module.export_generated_headers = module.generated_headers + + if module.type == 'cc_library_shared': + output_name = target.output_name + if output_name is None: + module.stem = 'lib' + target.get_target_name().removesuffix( + gn_utils.TESTING_SUFFIX) + else: + module.stem = 'lib' + output_name + + # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)). + all_deps = [(dep_name, 'common') for dep_name in target.proto_deps] + for arch_name, arch in target.arch.items(): + all_deps += [(dep_name, arch_name) for dep_name in arch.deps] + + if gn_target_name in replace_deps: + # Do not recurse into replace_deps target's dependencies. + return (module, ) + + # Sort deps before iteration to make result deterministic. + for (dep_name, arch_name) in sorted(all_deps): + module_target = module.target[ + arch_name] if arch_name != 'common' else module + # |builtin_deps| override GN deps with Android-specific ones. See the + # config in the top of this file. + if dep_name in builtin_deps: + builtin_deps[dep_name]( + module.java_unfiltered_module if + module.is_java_top_level_module() else module, arch_name) + continue + + for dep_module in create_modules_from_target( + blueprint, gn, dep_name, target.type, is_test_target): + if dep_name in replace_deps: + replace_deps[dep_name]( + module.java_unfiltered_module + if module.is_java_top_level_module() else module, + arch_name) + continue + + if dep_module is None: + continue + + # TODO: Proper dependency check for genrule. + # Currently, only propagating genrule dependencies. + # Also, currently, all the dependencies are propagated upwards. + # in gn, public_deps should be propagated but deps should not. + # Not sure this information is available in the desc.json. + # Following rule works for adding android_runtime_jni_headers to base:base. + # If this doesn't work for other target, hardcoding for specific target + # might be better. + if module.is_genrule() and dep_module.is_genrule(): + if module_target.gn_type != "proto_library": + # proto_library are treated differently because each proto action + # is split into two different targets, a cpp target and a header target. + # the cpp target is used as the entry point to the proto action, hence + # it should not be propagated as a genrule header because it generates + # cpp files only. + module_target.genrule_headers.add(dep_module.name) + module_target.genrule_headers.update( + dep_module.genrule_headers) + + # For filegroups, and genrule, recurse but don't apply the + # deps. + if not module.is_compiled() or module.is_genrule(): + continue + + # Drop compiled modules that doesn't provide any benefit. This is mostly + # applicable to source_sets when converted to cc_static_library, sometimes + # the source set only has header files which are dropped so the module becomes empty. + # is_compiled is there to prevent dropping of genrules. + if dep_module.is_compiled( + ) and not dep_module.has_input_files(): + continue + + module_is_cc = module.type in [ + 'cc_library_shared', 'cc_binary', 'cc_library_static' + ] + + if dep_module.type == 'cc_library_shared': + module_target.shared_libs.add(dep_module.name) + elif dep_module.type == 'cc_library_static' or ( + dep_module.type == "rust_ffi_static" and module_is_cc): + if module.type in [ + 'cc_library_shared', 'cc_binary', 'rust_binary', + 'cc_library_static' + ]: + if module.type != 'cc_library_static': + module_target.whole_static_libs.add( + dep_module.name) + else: + module_target.generated_headers.update( + dep_module.generated_headers) + module_target.shared_libs.update( + dep_module.shared_libs) + module_target.header_libs.update( + dep_module.header_libs) + elif module.type in ('rust_ffi_static', 'rust_bindgen'): + module_target.shared_libs.update( + dep_module.shared_libs) + elif module.type == 'rust_proc_macro' and dep_module.type == 'cc_library_static': + # rust_proc_macro cannot depend on cc_library_static. Having said + # that, we still need these dependencies to further bubble them up + # to rust_proc_macro targets dependencies, so simply ignore them. + # See https:/crbug.com/417429009. + pass + else: + raise Exception( + f"Cannot add {dep_module.name} ({dep_module.type}) to {module.name} ({module.type})" + ) + elif dep_module.type == "rust_bindgen": + if module.type.startswith("rust"): + # Soong does not support using `rust_bindgen` modules directly as an input because + # it produces more than a single output (b/467420029). Create an intermediate + # genrule that copies that rust file and use it instead. + intermediate_target = _create_extract_rust_files_target( + dep_module, blueprint).name + module.srcs.add(":" + intermediate_target) + if module.crate_root and module.crate_root.startswith( + "out/"): + # Sometimes the crate_root is an output of another module which is indicated + # by a path starting with "out/". The only case where this happens at the moment + # is when a rust_library is created for the rust_bindgen output. + module.crate_root = f":{intermediate_target}" + else: + module.srcs.add(":" + dep_module.name) + if module_target.type == "cc_library_static": + # This is a bindgen _static_fns GN target. We need to translate that + # to the Soong rust_bindgen "static inline library" concept. + + # AOSP Rust team wants every bindgen static inline library module to + # have a "lib" prefix. Due to the way Chromium //build/rust bindgen + # generator rules work, we know the _static_fns target is only + # referenced by its corresponding bindgen target and nothing else; + # therefore, we can safely assume we are only going to enter this + # path once, so there is no need to protect against the prefix being + # added multiple times - nor is there a need to go back and fix + # previous references. + module.name = "lib" + module.name + # rust_bindgen generates a .c / .cc file which has include + # defined from the root of the android tree. + module_target.include_dirs.append(".") + # The rust_bindgen has to know the name of the cc library which is going to + # consume it. We don't know that until we add the `rust_bindgen` as a dep. + dep_module.static_inline_library = module.name + elif dep_module.type == "rust_ffi_static": + if module.type in [ + "rust_binary", "rust_proc_macro", "rust_ffi_static" + ]: + module_target.rustlibs.add(dep_module.name) + elif dep_module.type == "rust_proc_macro": + module_target.proc_macros.add(dep_module.name) + elif dep_module.type == "aidl_interface": + # See https://cs.android.com/android/platform/superproject/main/+/main:system/tools/aidl/build/aidl_interface_backends.go + # for how those modules "-lang-source" is generated. + if module.type.startswith("cc_"): + module.srcs.add(f":{dep_module.name}-ndk-source") + module.generated_headers.add( + f"{dep_module.name}-ndk-source") + module.export_generated_headers.add( + f"{dep_module.name}-ndk-source") + elif module.type.startswith("java_"): + module.srcs.add(f":{dep_module.name}-java-source") + elif module.type.startswith("rust_"): + module.srcs.add(f":{dep_module.name}-rust-source") + elif dep_module.type == 'cc_genrule': + if dep_module.genrule_headers: + if module.type == "rust_ffi_static": + # Don't bubble up generated_headers on Rust modules, as that doesn't make sense + # (Rust cannot use C++ headers directly) and is not supported anyway. See also + # https://crbug.com/405987939. + # TODO: https://crbug.com/406267472 - how we end up in this situation in the + # first place is not entirely clear. We may have to revisit how generated + # headers interact with cxx/bindgen targets. + pass + elif module.type == "rust_bindgen": + # rust_bindgen modules don't support the `generated_headers` attribute; + # see http://crbug.com/394615281. We work around this limitation by + # inserting a module whose sole purpose is to export the generated + # headers, and then depending on that. See also + # http://crbug.com/394069879. + module_target.header_libs.add( + create_generated_headers_export_module( + blueprint, dep_module).name) + else: + module_target.generated_headers.update( + dep_module.genrule_headers) + module_target.srcs.update(dep_module.genrule_srcs) + module_target.shared_libs.update( + dep_module.genrule_shared_libs) + module_target.header_libs.update( + dep_module.genrule_header_libs) + elif dep_module.is_java_top_level_module(): + # A module depending on a module with system_current sdk version should also compile against + # the system sdk. This is because a module's SDK API surface should be >= its deps SDK API surface. + # And system_current has a larger API surface than current or module_current. + if dep_module.sdk_version == 'system_current': + module_target.sdk_version = module_target.java_unfiltered_module.sdk_version = 'system_current' + + module_target.static_libs.add(dep_module.name) + + # `create_java_module()` implements Chromium's Java jar filtering + # feature. Here we deal with another subtlety around that feature, + # which is how jar filtering affects the inputs of the various build + # steps. + # + # When Chromium runs javac, it runs it against the raw output of + # javac from the dependencies. In other words, the javac classpath is + # made of *unfiltered* jars. However, it is the *filtered* jars that + # eventually get shipped in the final build outputs. javac running + # against unfiltered jars is important - some targets rely on this + # (e.g. //base:log_java pulling BuildConfig from + # //build/android:build_java), so we need to preserve this behavior. + # + # Reproducing this in Soong is somewhat of a headache. The difficulty + # is, in Soong `static_libs` dependencies on `java_library` modules + # automatically bubble up the dependency tree. If we just list + # `__unfiltered` modules in `static_libs`, the unfiltered jars will + # propagate all the way to the final build outputs, which is not what + # we want. + # + # To solve this problem, we generate two dependency trees: a filtered + # tree that links top-level Java modules together, and an unfiltered + # tree that links unfiltered Java modules together. When one depends + # on the top-level modules one gets the filtered jars; when one + # depends on the unfiltered module one gets the unfiltered jars. (This + # is the reason why we have to have a separate top-level module and + # can't just merge it with the filtered module: the dependency tree of + # filtered modules indirectly includes unfiltered jars, which we don't + # want to pull in top-level modules.) + # + # A keen eye will notice we still have a problem, because the + # unfiltered dependencies of unfiltered modules will bubble up through + # filtered modules and then to top-level targets. This would result in + # top-level targets producing unfiltered jars, which is not what we + # want. + # + # To solve this problem, we don't use `static_libs` on unfiltered + # modules. Instead, we use `libs`. Indeed, Soong does *not* bubble up + # `libs` dependencies, thus preventing unfiltered jars from bubbling + # up and appearing in final build outputs. + # + # TODO: as if this wasn't complicated enough, in GN a `java_library` + # can use a flag, `prevent_excluded_classes_from_classpath`, that + # flips the above behavior and makes dependent compile targets pull + # the *filtered* jars in the javac classpath instead of the unfiltered + # ones. This flag is notably used in `generate_jni()` autogenerated + # java_library targets to prevent the jni_zero placeholder classes + # from bubbling up and potentially conflicting with their real + # counterparts up the build tree. We currently do not support this + # flag, i.e. we behave as if it is false. Surprisingly the resulting + # build rules work anyway - presumably by sheer luck (classpath + # ordering maybe?). In the future we may have to support it. This + # should be easy - just depend on the filtered target instead of the + # unfiltered target when the flag is true on the dependency. + # + # For even more more background, see https://crbug.com/397396295. + module_target.java_unfiltered_module.libs.add( + dep_module.java_unfiltered_module.name) + # As mentioned above, `libs` does not bubble up, so we have to + # recurse and collect all the transitive dependencies ourselves. This + # is not necessary when using `static_libs` as Soong does that for us + # at build time. + # + # (You may wonder: "wait, doesn't Chromium already enforce that a Java + # target list all the classes it refers to in its direct dependencies? + # Why do we need to pull indirect dependencies then?" Well the problem + # is javac needs to see some of the indirect dependencies in some + # cases - see https://crbug.com/400952169#comment4 - which means the + # direct dependencies may not be enough.) + module_target.java_unfiltered_module.libs.update( + dep_module.java_unfiltered_module.libs) + elif dep_module.type in ['genrule', 'java_genrule']: + if dep_module.jni_zero_target_type == JniZeroTargetType.GENERATOR: + # TODO: we are special-casing jni_zero here. Ideally this should be + # handled more generically, by making gn2bp understand the general + # concept of a target depending on only a subset of the outputs of + # an action. + _, placeholder_path = get_jni_zero_generator_proxy_and_placeholder_paths( + dep_module) + if placeholder_path in target.inputs: + # The target depends on both jni_zero generator outputs (proxy and + # placeholder). We can simply pull both of them at the same time + # by depending on the jni_zero generator module directly. In + # practice this branch is taken when a standalone jni_zero library + # is being built separately from the JNI user code, such as the + # java_library generated by jni_zero's generate_jni() GN rule. One + # example is //base:command_line_jni_java. + module_target.srcs.add(":" + dep_module.name) + else: + # The target only depends on the generated proxy classes but not + # the placeholder classes. Typically this happens when the + # proxy classes are being compiled alongside the JNI user code: in + # this case there is no need for the placeholder classes since the + # user code provides all the necessary definitions. One example is + # //components/cronet/android:cronet_impl_native_java. In this + # situation it is imperative that we do *not* pull the + # placeholder classes, as they would conflict with user code. See + # https://crbug.com/397396295 for more background. + proxy_only_module = create_jni_zero_proxy_only_module( + dep_module) + blueprint.add_module(proxy_only_module) + module_target.srcs.add( + f":{proxy_only_module.name}") + else: + module_target.srcs.add(":" + dep_module.name) + else: + raise Exception( + 'Unsupported arch-specific dependency %s of target %s with type %s' + % (dep_module.name, target.name, dep_module.type)) + + if module.is_java_top_level_module(): + # The Java top-level module is not the one doing the actual compiling; the + # unfiltered module is, so it should get the srcs. + module.java_unfiltered_module.srcs = module.srcs + module.srcs = () + + return modules def turn_off_allocator_shim_for_musl(module): - allocation_shim = "base/allocator/partition_allocator/shim/allocator_shim.cc" - allocator_shim_files = { - allocation_shim, - "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc", - } - module.srcs -= allocator_shim_files - for arch in module.target.values(): - arch.srcs -= allocator_shim_files - module.target['android'].srcs.add(allocation_shim) - if gn_utils.TESTING_SUFFIX in module.name: - # allocator_shim_default_dispatch_to_glibc is only added to the __testing version of base - # since base_base__testing is compiled for host. When compiling for host. Soong compiles - # using glibc or musl(experimental). We currently only support compiling for glibc. - module.target['glibc'].srcs.update(allocator_shim_files) - else: - # allocator_shim_default_dispatch_to_glibc does not exist in the prod version of base - # `base_base` since this only compiles for android and bionic is used. Bionic is the equivalent - # of glibc but for android. - module.target['glibc'].srcs.add(allocation_shim) + allocation_shim = "base/allocator/partition_allocator/shim/allocator_shim.cc" + allocator_shim_files = { + allocation_shim, + "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc", + } + module.srcs -= allocator_shim_files + for arch in module.target.values(): + arch.srcs -= allocator_shim_files + module.target['android'].srcs.add(allocation_shim) + if gn_utils.TESTING_SUFFIX in module.name: + # allocator_shim_default_dispatch_to_glibc is only added to the __testing version of base + # since base_base__testing is compiled for host. When compiling for host. Soong compiles + # using glibc or musl(experimental). We currently only support compiling for glibc. + module.target['glibc'].srcs.update(allocator_shim_files) + else: + # allocator_shim_default_dispatch_to_glibc does not exist in the prod version of base + # `base_base` since this only compiles for android and bionic is used. Bionic is the equivalent + # of glibc but for android. + module.target['glibc'].srcs.add(allocation_shim) def create_cc_defaults_module(): - defaults = Module('cc_defaults', cc_defaults_module, '//gn:default_deps') - defaults.cflags = [ - # TODO: this list is brittle and painful to maintain. We are too easily - # broken by changes to Chromium cflags, e.g. https://crbug.com/406704769. - # Ideally this list should be deduced from GN cflags. - '-DGOOGLE_PROTOBUF_NO_RTTI', - '-Wno-error=return-type', - '-Wno-non-virtual-dtor', - '-Wno-macro-redefined', - '-Wno-missing-field-initializers', - '-Wno-sign-compare', - '-Wno-sign-promo', - '-Wno-unused-parameter', - '-Wno-null-pointer-subtraction', # Needed to libevent - '-Wno-ambiguous-reversed-operator', # needed for icui18n - '-Wno-unreachable-code-loop-increment', # needed for icui18n - '-fPIC', - '-Wno-c++11-narrowing', - # Needed for address_space_randomization.h on riscv - # Can be removed after 125.0.6375.0 is imported - '-Wno-invalid-constexpr', - # b/330508686 disable coverage profiling for files or function in this list. - '-fprofile-list=external/cronet/exclude_coverage.list', - # https://crrev.com/c/6396655/7/build/config/compiler/BUILD.gn - # https://crbug.com/406704769 - '-Wno-nullability-completeness', - # Stops warning about unknown options. This usually happens when - # Chromium uses a newer version of Clang that supports a flag which - # Android's clang does not know about. - '-Wno-unknown-warning-option', - # Required to correctly compile quiche tests. - # TODO(crbug.com/433273929): Remove once fixed. - "-Wno-nonnull", - ] - defaults.build_file_path = "" - defaults.include_build_directory = False - defaults.whole_program_vtables = True - defaults.c_std = 'gnu11' - # Chromium builds do not add a dependency for headers found inside the - # sysroot, so they are added globally via defaults. - defaults.target['android'].header_libs = [ - 'jni_headers', - ] - defaults.target['android'].shared_libs = ['libmediandk'] - defaults.target['host'].cflags = [ - # -DANDROID is added by default but target.defines contain -DANDROID if - # it's required. So adding -UANDROID to cancel default -DANDROID if it's - # not specified. - # Note: -DANDROID is not consistently applied across the chromium code - # base, so it is removed unconditionally for host targets. - '-UANDROID', - ] - # Don't build 32-bit binaries for the host - otherwise - # cronet_aml_base_base__testing fails to build on aosp_cheetah due to - # partition_alloc failing on a static assertion that pointers are 64-bit. - defaults.target['host'].compile_multilib = '64' - defaults.stl = 'none' - defaults.cpp_std = CPP_VERSION - defaults.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP - defaults.apex_available.add(tethering_apex) - return defaults + defaults = Module('cc_defaults', cc_defaults_module, '//gn:default_deps') + defaults.cflags = [ + # TODO: this list is brittle and painful to maintain. We are too easily + # broken by changes to Chromium cflags, e.g. https://crbug.com/406704769. + # Ideally this list should be deduced from GN cflags. + '-DGOOGLE_PROTOBUF_NO_RTTI', + '-Wno-error=return-type', + '-Wno-non-virtual-dtor', + '-Wno-macro-redefined', + '-Wno-missing-field-initializers', + '-Wno-sign-compare', + '-Wno-sign-promo', + '-Wno-unused-parameter', + '-Wno-null-pointer-subtraction', # Needed to libevent + '-Wno-ambiguous-reversed-operator', # needed for icui18n + '-Wno-unreachable-code-loop-increment', # needed for icui18n + '-fPIC', + '-Wno-c++11-narrowing', + # Needed for address_space_randomization.h on riscv + # Can be removed after 125.0.6375.0 is imported + '-Wno-invalid-constexpr', + # b/330508686 disable coverage profiling for files or function in this list. + '-fprofile-list=external/cronet/exclude_coverage.list', + # https://crrev.com/c/6396655/7/build/config/compiler/BUILD.gn + # https://crbug.com/406704769 + '-Wno-nullability-completeness', + # Stops warning about unknown options. This usually happens when + # Chromium uses a newer version of Clang that supports a flag which + # Android's clang does not know about. + '-Wno-unknown-warning-option', + # Required to correctly compile quiche tests. + # TODO(crbug.com/433273929): Remove once fixed. + "-Wno-nonnull", + ] + defaults.build_file_path = "" + defaults.include_build_directory = False + defaults.whole_program_vtables = True + defaults.c_std = 'gnu11' + # Chromium builds do not add a dependency for headers found inside the + # sysroot, so they are added globally via defaults. + defaults.target['android'].header_libs = [ + 'jni_headers', + ] + defaults.target['android'].shared_libs = ['libmediandk'] + defaults.target['host'].cflags = [ + # -DANDROID is added by default but target.defines contain -DANDROID if + # it's required. So adding -UANDROID to cancel default -DANDROID if it's + # not specified. + # Note: -DANDROID is not consistently applied across the chromium code + # base, so it is removed unconditionally for host targets. + '-UANDROID', + ] + # Don't build 32-bit binaries for the host - otherwise + # cronet_aml_base_base__testing fails to build on aosp_cheetah due to + # partition_alloc failing on a static assertion that pointers are 64-bit. + defaults.target['host'].compile_multilib = '64' + defaults.stl = 'none' + defaults.cpp_std = CPP_VERSION + defaults.min_sdk_version = cronet_utils.MIN_SDK_VERSION_FOR_AOSP + defaults.apex_available.add(tethering_apex) + return defaults def apply_post_processing(module): - for key, add_val in additional_args.get(module.name, []): - curr = getattr(module, key) - if add_val and isinstance(add_val, set) and isinstance(curr, set): - curr.update(add_val) - elif isinstance(curr, list): - curr.extend(add_val) - elif isinstance(add_val, str) and (not curr or isinstance(curr, str)): - setattr(module, key, add_val) - elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)): - setattr(module, key, add_val) - elif isinstance(add_val, dict) and isinstance(curr, dict): - curr.update(add_val) - elif add_val is None: - setattr(module, key, None) - elif isinstance(add_val[1], dict) and isinstance(curr[add_val[0]], - Module.Target): - curr[add_val[0]].__dict__.update(add_val[1]) - elif isinstance(curr, dict): - curr[add_val[0]] = add_val[1] - else: - raise Exception('Unimplemented type %r of additional_args: %r' % - (type(add_val), key)) + for key, add_val in additional_args.get(module.name, []): + curr = getattr(module, key) + if add_val and isinstance(add_val, set) and isinstance(curr, set): + curr.update(add_val) + elif isinstance(curr, list): + curr.extend(add_val) + elif isinstance(add_val, str) and (not curr or isinstance(curr, str)): + setattr(module, key, add_val) + elif isinstance(add_val, bool) and (not curr + or isinstance(curr, bool)): + setattr(module, key, add_val) + elif isinstance(add_val, dict) and isinstance(curr, dict): + curr.update(add_val) + elif add_val is None: + setattr(module, key, None) + elif isinstance(add_val[1], dict) and isinstance( + curr[add_val[0]], Module.Target): + curr[add_val[0]].__dict__.update(add_val[1]) + elif isinstance(curr, dict): + curr[add_val[0]] = add_val[1] + else: + raise Exception('Unimplemented type %r of additional_args: %r' % + (type(add_val), key)) def make_cc_defaults_from_boringssl(boringssl_module: Module) -> Module: - module_name = boringssl_module.name + "__flags" - cc_default_flags_module = Module( - "cc_defaults", module_name, - "Flags auto-extracted from BoringSSL GN rules, to be used in manually maintained BoringSSL Android.bp rules" - ) + module_name = boringssl_module.name + "__flags" + cc_default_flags_module = Module( + "cc_defaults", module_name, + "Flags auto-extracted from BoringSSL GN rules, to be used in manually maintained BoringSSL Android.bp rules" + ) - libcrypto_cc_defaults_flags_module = Module( - "cc_defaults", f'{module_name}_libcrypto', - f"""This cc_defaults inherits the same flags from {module_name} except some flags that breaks FIPS compliance.""" - ) + libcrypto_cc_defaults_flags_module = Module( + "cc_defaults", f'{module_name}_libcrypto', + f"""This cc_defaults inherits the same flags from {module_name} except some flags that breaks FIPS compliance.""" + ) - def _get_libcrypto_cflags(cflags): - return [ - cflag for cflag in cflags - if all(not cflag.startswith(denied_prefix) for denied_prefix in [ - # Breaks FIPS compliance as this is used by the linker's `--gc-sections` to remove - # unused sections which breaks the hash. `-fno-*-sections` is intentionally not - # added here as those flags are used to build all of boringSSL, while only - # boringCrypto(libcrypto) requires it. - "-ffunction-sections", - "-fdata-sections", - # Causes 'tot_cronet_bcm_object.o:(.text): multiple relocation sections to one section are not supported' - # during linking stage. - "-fno-unique-section-names", - ]) - ] + def _get_libcrypto_cflags(cflags): + return [ + cflag for cflag in cflags + if all(not cflag.startswith(denied_prefix) for denied_prefix in [ + # Breaks FIPS compliance as this is used by the linker's `--gc-sections` to remove + # unused sections which breaks the hash. `-fno-*-sections` is intentionally not + # added here as those flags are used to build all of boringSSL, while only + # boringCrypto(libcrypto) requires it. + "-ffunction-sections", + "-fdata-sections", + # Causes 'tot_cronet_bcm_object.o:(.text): multiple relocation sections to one section are not supported' + # during linking stage. + "-fno-unique-section-names", + ]) + ] - def _get_libcrypto_ldflags(ldflags): - return [ - ldflag for ldflag in ldflags - if all(not ldflag.startswith(denied_prefix) for denied_prefix in [ - # -Wl, --gc-sections is effectively useless when '-ffunctions-sections' and - # '-fdata-sections' are not used. Hence remove it. - "-Wl,--gc-sections", - ]) - ] + def _get_libcrypto_ldflags(ldflags): + return [ + ldflag for ldflag in ldflags + if all(not ldflag.startswith(denied_prefix) for denied_prefix in [ + # -Wl, --gc-sections is effectively useless when '-ffunctions-sections' and + # '-fdata-sections' are not used. Hence remove it. + "-Wl,--gc-sections", + ]) + ] - cc_default_flags_module.cflags = boringssl_module.cflags - cc_default_flags_module.ldflags = boringssl_module.ldflags - libcrypto_cc_defaults_flags_module.cflags = _get_libcrypto_cflags( - boringssl_module.cflags) - libcrypto_cc_defaults_flags_module.ldflags = _get_libcrypto_ldflags( - boringssl_module.ldflags) - for arch, variant in boringssl_module.target.items(): - cc_default_flags_module.target[arch].cflags = variant.cflags - cc_default_flags_module.target[arch].ldflags = variant.ldflags - libcrypto_cc_defaults_flags_module.target[ - arch].cflags = _get_libcrypto_cflags(variant.cflags) - libcrypto_cc_defaults_flags_module.target[ - arch].ldflags = _get_libcrypto_ldflags(variant.ldflags) + cc_default_flags_module.cflags = boringssl_module.cflags + cc_default_flags_module.ldflags = boringssl_module.ldflags + libcrypto_cc_defaults_flags_module.cflags = _get_libcrypto_cflags( + boringssl_module.cflags) + libcrypto_cc_defaults_flags_module.ldflags = _get_libcrypto_ldflags( + boringssl_module.ldflags) + for arch, variant in boringssl_module.target.items(): + cc_default_flags_module.target[arch].cflags = variant.cflags + cc_default_flags_module.target[arch].ldflags = variant.ldflags + libcrypto_cc_defaults_flags_module.target[ + arch].cflags = _get_libcrypto_cflags(variant.cflags) + libcrypto_cc_defaults_flags_module.target[ + arch].ldflags = _get_libcrypto_ldflags(variant.ldflags) - cc_default_flags_module.build_file_path = "" - libcrypto_cc_defaults_flags_module.build_file_path = "" - cc_default_flags_module.defaults = [cc_defaults_module] - libcrypto_cc_defaults_flags_module.defaults = [cc_defaults_module] - return (cc_default_flags_module, libcrypto_cc_defaults_flags_module) + cc_default_flags_module.build_file_path = "" + libcrypto_cc_defaults_flags_module.build_file_path = "" + cc_default_flags_module.defaults = [cc_defaults_module] + libcrypto_cc_defaults_flags_module.defaults = [cc_defaults_module] + return (cc_default_flags_module, libcrypto_cc_defaults_flags_module) def setup_libcrypto_stripping(blueprint): - # Two-step build process for libhttpengine.so: - # 1. Build against the full libcrypto to identify required symbols and - # generate a stripped variant of libcrypto. - # 2. Rebuild against that stripped libcrypto to guarantee no undefined symbols. - cronet_shared_library_module = blueprint.modules[ - f"{MODULE_PREFIX}components_cronet_android_cronet"] - cronet_shared_library_copy = copy.deepcopy(cronet_shared_library_module) - cronet_shared_library_copy.name += "_against_unstripped_libcrypto" - cronet_shared_library_module.shared_libs.remove( - f"{MODULE_PREFIX}{LIBCRYPTO_UNSTRIPPED}") - cronet_shared_library_module.shared_libs.add( - f"{MODULE_PREFIX}{LIBCRYPTO_STRIPPED}") - blueprint.add_module(cronet_shared_library_copy) + # Two-step build process for libhttpengine.so: + # 1. Build against the full libcrypto to identify required symbols and + # generate a stripped variant of libcrypto. + # 2. Rebuild against that stripped libcrypto to guarantee no undefined symbols. + cronet_shared_library_module = blueprint.modules[ + f"{MODULE_PREFIX}components_cronet_android_cronet"] + cronet_shared_library_copy = copy.deepcopy(cronet_shared_library_module) + cronet_shared_library_copy.name += "_against_unstripped_libcrypto" + cronet_shared_library_module.shared_libs.remove( + f"{MODULE_PREFIX}{LIBCRYPTO_UNSTRIPPED}") + cronet_shared_library_module.shared_libs.add( + f"{MODULE_PREFIX}{LIBCRYPTO_STRIPPED}") + blueprint.add_module(cronet_shared_library_copy) + def create_blueprint_for_targets(gn, targets, test_targets): - """Generate a blueprint for a list of GN targets.""" - blueprint = Blueprint() + """Generate a blueprint for a list of GN targets.""" + blueprint = Blueprint() - # Default settings used by all modules. - blueprint.add_module(create_cc_defaults_module()) + # Default settings used by all modules. + blueprint.add_module(create_cc_defaults_module()) - for target in targets: - modules = create_modules_from_target(blueprint, - gn, - target, - parent_gn_type=None, - is_test_target=False) - for module in modules: - module.visibility.update(root_modules_visibility) + for target in targets: + modules = create_modules_from_target(blueprint, + gn, + target, + parent_gn_type=None, + is_test_target=False) + for module in modules: + module.visibility.update(root_modules_visibility) - for test_target in test_targets: - modules = create_modules_from_target(blueprint, - gn, - test_target + gn_utils.TESTING_SUFFIX, - parent_gn_type=None, - is_test_target=True) - for module in modules: - module.visibility.update(root_modules_visibility) + for test_target in test_targets: + modules = create_modules_from_target(blueprint, + gn, + test_target + + gn_utils.TESTING_SUFFIX, + parent_gn_type=None, + is_test_target=True) + for module in modules: + module.visibility.update(root_modules_visibility) - # Merge in additional hardcoded arguments. - for module in blueprint.modules.values(): - apply_post_processing(module) + # Merge in additional hardcoded arguments. + for module in blueprint.modules.values(): + apply_post_processing(module) - for module in make_cc_defaults_from_boringssl(blueprint.modules[ - label_to_module_name("//third_party/boringssl:boringssl")]): - blueprint.add_module(module) + for module in make_cc_defaults_from_boringssl(blueprint.modules[ + label_to_module_name("//third_party/boringssl:boringssl")]): + blueprint.add_module(module) - setup_libcrypto_stripping(blueprint) - return blueprint + setup_libcrypto_stripping(blueprint) + return blueprint def _rebase_file(filepath: str, blueprint_path: str) -> Union[str, None]: - """ + """ Rebases a single file, this method delegates to _rebase_files :param filepath: a single string representing filepath. :param blueprint_path: Path for which the srcs will be rebased relative to. :returns The rebased filepaths or None. """ - rebased_file = _rebase_files([filepath], blueprint_path) - if rebased_file: - return list(rebased_file)[0] - return None + rebased_file = _rebase_files([filepath], blueprint_path) + if rebased_file: + return list(rebased_file)[0] + return None def _rebase_files(filepaths, parent_prefix): - """ + """ Rebase a list of filepaths according to the provided path. This assumes that the |filepaths| are subdirectories of the |parent|. If the assumption is violated then None is returned. @@ -3712,27 +3793,27 @@ :param parent_prefix: Path for which the srcs will be rebased relative to. :returns The rebased filepaths or None. """ - if not parent_prefix: - return filepaths + if not parent_prefix: + return filepaths - rebased_srcs = set() - for src in filepaths: - if src.startswith(":"): - # This is a reference to another Android.bp module, add as-is. - rebased_srcs.add(src) - continue + rebased_srcs = set() + for src in filepaths: + if src.startswith(":"): + # This is a reference to another Android.bp module, add as-is. + rebased_srcs.add(src) + continue - if not src.startswith(parent_prefix): - # This module depends on a source file that is not in its subpackage. - return None - # Remove the BUILD file path to make it relative. - rebased_srcs.add(src[len(parent_prefix) + 1:]) - return rebased_srcs + if not src.startswith(parent_prefix): + # This module depends on a source file that is not in its subpackage. + return None + # Remove the BUILD file path to make it relative. + rebased_srcs.add(src[len(parent_prefix) + 1:]) + return rebased_srcs # TODO: Move to Module's class. def _rebase_module(module: Module, blueprint_path: str) -> Union[Module, None]: - """ + """ Rebases the module specified on top of the blueprint_path if possible. If the rebase operation has failed, None is returned to indicate that the module should stay as a top-level module. @@ -3743,98 +3824,99 @@ :returns A new module based on the provided one but rebased or None. """ - module_copy = copy.deepcopy(module) - # TODO: Find a better way to rebase attribute and verify if all rebase operations - # have succeeded or not. - if module_copy.crate_root: - module_copy.crate_root = _rebase_file(module_copy.crate_root, - blueprint_path) - if module_copy.crate_root is None: - return None + module_copy = copy.deepcopy(module) + # TODO: Find a better way to rebase attribute and verify if all rebase operations + # have succeeded or not. + if module_copy.crate_root: + module_copy.crate_root = _rebase_file(module_copy.crate_root, + blueprint_path) + if module_copy.crate_root is None: + return None - if module_copy.path: - module_copy.path = _rebase_file(module_copy.path, blueprint_path) - if module_copy.path is None: - return None + if module_copy.path: + module_copy.path = _rebase_file(module_copy.path, blueprint_path) + if module_copy.path is None: + return None - if module_copy.wrapper_src: - module_copy.wrapper_src = _rebase_file(module_copy.wrapper_src, - blueprint_path) - if module_copy.wrapper_src is None: - return None + if module_copy.wrapper_src: + module_copy.wrapper_src = _rebase_file(module_copy.wrapper_src, + blueprint_path) + if module_copy.wrapper_src is None: + return None - if module_copy.srcs: - module_copy.srcs = _rebase_files(module_copy.srcs, blueprint_path) - if module_copy.srcs is None: - return None + if module_copy.srcs: + module_copy.srcs = _rebase_files(module_copy.srcs, blueprint_path) + if module_copy.srcs is None: + return None - if module_copy.jars: - module_copy.jars = _rebase_files(module_copy.jars, blueprint_path) - if module_copy.jars is None: - return None + if module_copy.jars: + module_copy.jars = _rebase_files(module_copy.jars, blueprint_path) + if module_copy.jars is None: + return None - for (arch_name, _) in module_copy.target.items(): - module_copy.target[arch_name].srcs = (_rebase_files( - module_copy.target[arch_name].srcs, blueprint_path)) - if module_copy.target[arch_name].srcs is None: - return None + for (arch_name, _) in module_copy.target.items(): + module_copy.target[arch_name].srcs = (_rebase_files( + module_copy.target[arch_name].srcs, blueprint_path)) + if module_copy.target[arch_name].srcs is None: + return None - return module_copy + return module_copy def _path_to_name(path: str) -> str: - path = path.replace("/", "_").lower() - return f"{MODULE_PREFIX}{path}_license" + path = path.replace("/", "_").lower() + return f"{MODULE_PREFIX}{path}_license" def _maybe_create_license_module(path: str) -> Union[Module, None]: - """ + """ Creates a module license if a README.chromium exists in the path provided otherwise just returns None. :param path: Path to check for README.chromium :return: Module or None. """ - readme_relative_path = os.path.join(path, "README.chromium") - readme_chromium_file = Path( - os.path.join(REPOSITORY_ROOT, path, "README.chromium")) - if (not readme_chromium_file.exists() - or license_utils.is_ignored_readme_chromium(readme_relative_path)): - return None + readme_relative_path = os.path.join(path, "README.chromium") + readme_chromium_file = Path( + os.path.join(REPOSITORY_ROOT, path, "README.chromium")) + if (not readme_chromium_file.exists() + or license_utils.is_ignored_readme_chromium(readme_relative_path)): + return None - license_module = Module("license", _path_to_name(path), "License-Artificial") - license_module.visibility = {":__subpackages__"} - # Assume that a LICENSE file always exist as we run the - # create_android_metadata_license.py script each time we run GN2BP. - license_module.license_text = {"LICENSE"} - metadata = license_utils.parse_chromium_readme_file( - str(readme_chromium_file), - license_constants.POST_PROCESS_OPERATION.get(readme_relative_path, - lambda _metadata: _metadata)) - for license_name in metadata.get_licenses(): - license_module.license_kinds.add( - license_utils.get_license_bp_name(license_name)) - return license_module + license_module = Module("license", _path_to_name(path), + "License-Artificial") + license_module.visibility = {":__subpackages__"} + # Assume that a LICENSE file always exist as we run the + # create_android_metadata_license.py script each time we run GN2BP. + license_module.license_text = {"LICENSE"} + metadata = license_utils.parse_chromium_readme_file( + str(readme_chromium_file), + license_constants.POST_PROCESS_OPERATION.get( + readme_relative_path, lambda _metadata: _metadata)) + for license_name in metadata.get_licenses(): + license_module.license_kinds.add( + license_utils.get_license_bp_name(license_name)) + return license_module def _get_longest_matching_blueprint( - current_blueprint_path: str, - all_blueprints: Dict[str, Blueprint]) -> Union[Blueprint, None]: - longest_path_matching = None - for (blueprint_path, search_blueprint) in all_blueprints.items(): - if (search_blueprint.get_license_module() - and current_blueprint_path.startswith(blueprint_path) - and (longest_path_matching is None - or len(blueprint_path) > len(longest_path_matching))): - longest_path_matching = blueprint_path + current_blueprint_path: str, + all_blueprints: Dict[str, Blueprint]) -> Union[Blueprint, None]: + longest_path_matching = None + for (blueprint_path, search_blueprint) in all_blueprints.items(): + if (search_blueprint.get_license_module() + and current_blueprint_path.startswith(blueprint_path) + and (longest_path_matching is None + or len(blueprint_path) > len(longest_path_matching))): + longest_path_matching = blueprint_path - if longest_path_matching: - return all_blueprints[longest_path_matching] - return None + if longest_path_matching: + return all_blueprints[longest_path_matching] + return None def finalize_package_modules(blueprints: Dict[str, Blueprint]): - """ + """ Adds a package module to every blueprint passed in |blueprints|. A package module is just a reference to a license module, the approach here is that the package module will point to the nearest ancestor's license module, the @@ -3843,35 +3925,35 @@ :param blueprints: Dictionary of (path, blueprint) to be populated with """ - for (current_path, blueprint) in blueprints.items(): - if current_path == "": - # Don't add a package module for the top-level Android.bp, this is handled - # manually in Android.extras.bp. - continue + for (current_path, blueprint) in blueprints.items(): + if current_path == "": + # Don't add a package module for the top-level Android.bp, this is handled + # manually in Android.extras.bp. + continue - package_module = Module("package", None, "Package-Artificial") - if blueprint.get_license_module(): - package_module.default_applicable_licenses.add( - blueprint.get_license_module().name) - else: # Search for closest ancestor with a license module - ancestor_blueprint = _get_longest_matching_blueprint( - current_path, blueprints) - if ancestor_blueprint: - # We found an ancestor, make a reference to its license module - package_module.default_applicable_licenses.add( - ancestor_blueprint.get_license_module().name) - else: - # No ancestor with a license found, this is most likely a non-third - # license, just point at Chromium's license in Android.extras.bp. - package_module.default_applicable_licenses.add( - "external_cronet_license") + package_module = Module("package", None, "Package-Artificial") + if blueprint.get_license_module(): + package_module.default_applicable_licenses.add( + blueprint.get_license_module().name) + else: # Search for closest ancestor with a license module + ancestor_blueprint = _get_longest_matching_blueprint( + current_path, blueprints) + if ancestor_blueprint: + # We found an ancestor, make a reference to its license module + package_module.default_applicable_licenses.add( + ancestor_blueprint.get_license_module().name) + else: + # No ancestor with a license found, this is most likely a non-third + # license, just point at Chromium's license in Android.extras.bp. + package_module.default_applicable_licenses.add( + "external_cronet_license") - blueprint.set_package_module(package_module) + blueprint.set_package_module(package_module) def create_license_modules( - blueprints: Dict[str, Blueprint]) -> Dict[str, Module]: - """ + blueprints: Dict[str, Blueprint]) -> Dict[str, Module]: + """ Creates license module (if possible) for each blueprint passed, a license module will be created if a README.chromium exists in the same directory as the BUILD.gn which created that blueprint. @@ -3882,48 +3964,48 @@ :param blueprints: List of paths for all possible blueprints. :return: Dictionary of (path, license_module). """ - license_modules = {} - for blueprint_path, blueprint in blueprints.items(): - if not blueprint.get_readme_location(): - # Don't generate a license for the top-level Android.bp as this is handled - # manually in Android.extras.bp - continue + license_modules = {} + for blueprint_path, blueprint in blueprints.items(): + if not blueprint.get_readme_location(): + # Don't generate a license for the top-level Android.bp as this is handled + # manually in Android.extras.bp + continue - license_module = _maybe_create_license_module( - blueprint.get_readme_location()) - if license_module: - license_modules[blueprint_path] = license_module - return license_modules + license_module = _maybe_create_license_module( + blueprint.get_readme_location()) + if license_module: + license_modules[blueprint_path] = license_module + return license_modules def _get_rust_crate_root_directory_from_crate_root(crate_root: str) -> str: - if crate_root and crate_root.startswith( - "third_party/rust/chromium_crates_io/vendor"): - # Return the first 5 directories (a/b/c/d/e) - crate_root_dir = crate_root.split("/")[:5] - return "/".join(crate_root_dir) - return None + if crate_root and crate_root.startswith( + "third_party/rust/chromium_crates_io/vendor"): + # Return the first 5 directories (a/b/c/d/e) + crate_root_dir = crate_root.split("/")[:5] + return "/".join(crate_root_dir) + return None def _locate_android_bp_destination(module: Module) -> str: - """Returns the appropriate location of the generated Android.bp for the + """Returns the appropriate location of the generated Android.bp for the specified module. Sometimes it is favourable to relocate the Android.bp to a different location other than next to BUILD.gn (eg: rust's BUILD.gn are defined in a different directory than the source code). :returns the appropriate location for the blueprint """ - crate_root_dir = _get_rust_crate_root_directory_from_crate_root( - module.crate_root) - if module.build_file_path in BLUEPRINTS_MAPPING: - return BLUEPRINTS_MAPPING[module.build_file_path] - if crate_root_dir: - return crate_root_dir - return module.build_file_path + crate_root_dir = _get_rust_crate_root_directory_from_crate_root( + module.crate_root) + if module.build_file_path in BLUEPRINTS_MAPPING: + return BLUEPRINTS_MAPPING[module.build_file_path] + if crate_root_dir: + return crate_root_dir + return module.build_file_path def _break_down_blueprint(top_level_blueprint: Blueprint): - """ + """ This breaks down the top-level blueprint into smaller blueprints in different directory. The goal here is to break down the huge Android.bp into smaller ones for compliance with SBOM. At the moment, not all targets @@ -3933,137 +4015,139 @@ :returns A dictionary of path -> Blueprint, the path is relative to repository root. """ - blueprints = {"": Blueprint()} - for (module_name, module) in top_level_blueprint.modules.items(): - if module.type in [ - "package", "genrule", "cc_genrule", "java_genrule", - "cc_preprocess_no_configuration" - ] and not module.allow_rebasing: - # Exclude the genrules from the rebasing as there is no support for them. - # cc_preprocess_no_configuration is created only for the sake of genrules as an intermediate - # target. - blueprints[""].add_module(module) - continue + blueprints = {"": Blueprint()} + for (module_name, module) in top_level_blueprint.modules.items(): + if module.type in [ + "package", "genrule", "cc_genrule", "java_genrule", + "cc_preprocess_no_configuration" + ] and not module.allow_rebasing: + # Exclude the genrules from the rebasing as there is no support for them. + # cc_preprocess_no_configuration is created only for the sake of genrules as an intermediate + # target. + blueprints[""].add_module(module) + continue - android_bp_path = _locate_android_bp_destination(module) - # third_party/android_deps is not imported which means that copybara will not - # pick up the Android.bp in there. Instead direct the modules to the top-level - # Android.bp - if android_bp_path.startswith("third_party/android_deps"): - blueprints[""].add_module(module) - continue - if android_bp_path is None: - # Raise an exception if the module does not specify a BUILD file path. - raise Exception(f"Found module {module_name} without a build file path.") + android_bp_path = _locate_android_bp_destination(module) + # third_party/android_deps is not imported which means that copybara will not + # pick up the Android.bp in there. Instead direct the modules to the top-level + # Android.bp + if android_bp_path.startswith("third_party/android_deps"): + blueprints[""].add_module(module) + continue + if android_bp_path is None: + # Raise an exception if the module does not specify a BUILD file path. + raise Exception( + f"Found module {module_name} without a build file path.") - rebased_module = _rebase_module(module, android_bp_path) - if rebased_module: - if android_bp_path not in blueprints.keys(): - blueprints[android_bp_path] = Blueprint(module.build_file_path) - blueprints[android_bp_path].add_module(rebased_module) - else: - # Append to the top-level blueprint. - blueprints[""].add_module(module) + rebased_module = _rebase_module(module, android_bp_path) + if rebased_module: + if android_bp_path not in blueprints.keys(): + blueprints[android_bp_path] = Blueprint(module.build_file_path) + blueprints[android_bp_path].add_module(rebased_module) + else: + # Append to the top-level blueprint. + blueprints[""].add_module(module) - for blueprint in blueprints.values(): - if blueprint.get_buildgn_location() in gn2bp_targets.README_MAPPING: - blueprint.set_readme_location( - gn2bp_targets.README_MAPPING[blueprint.get_buildgn_location()]) - return blueprints + for blueprint in blueprints.values(): + if blueprint.get_buildgn_location() in gn2bp_targets.README_MAPPING: + blueprint.set_readme_location( + gn2bp_targets.README_MAPPING[blueprint.get_buildgn_location()]) + return blueprints def main(): - parser = argparse.ArgumentParser( - description='Generate Android.bp from a GN description.') - parser.add_argument( - '--desc', - help='GN description (e.g., gn desc out --format=json "//*".' + - 'You can specify multiple --desc options for different target_cpu', - required=True, - action='append') - parser.add_argument('--repo_root', - required=True, - help='Path to the root of the repistory') - parser.add_argument( - '--build_script_output', - help='JSON-formatted file containing output of build scripts broken down' - + 'by architecture.', - required=True) - parser.add_argument( - '--extras', - help='Extra targets to include at the end of the Blueprint file', - default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'), - ) - parser.add_argument( - '--output', - help='Blueprint file to create', - default=os.path.join(gn_utils.repo_root(), 'Android.bp'), - ) - parser.add_argument( - '-v', - '--verbose', - help='Print debug logs.', - action='store_true', - ) - parser.add_argument( - 'targets', - nargs=argparse.REMAINDER, - help='Targets to include in the blueprint (e.g., "//:perfetto_tests")') - parser.add_argument( - '--suffix', - help='The suffix to the Android.bp filename. Pass "" if no suffix.', - default='.gn2bp') - parser.add_argument( - '--channel', - help='The channel this Android.bp generation is being performed for.', - type=str, - choices=['tot', 'stable'], - default='tot') - group = parser.add_mutually_exclusive_group() - group.add_argument( - '--license', - help='Generate license.', - dest='license', - action='store_true', - ) - group.add_argument( - '--no-license', - help='Do not generate license.', - dest='license', - action='store_false', - ) - parser.set_defaults(license=True) - args = parser.parse_args() + parser = argparse.ArgumentParser( + description='Generate Android.bp from a GN description.') + parser.add_argument( + '--desc', + help='GN description (e.g., gn desc out --format=json "//*".' + + 'You can specify multiple --desc options for different target_cpu', + required=True, + action='append') + parser.add_argument('--repo_root', + required=True, + help='Path to the root of the repistory') + parser.add_argument( + '--build_script_output', + help= + 'JSON-formatted file containing output of build scripts broken down' + + 'by architecture.', + required=True) + parser.add_argument( + '--extras', + help='Extra targets to include at the end of the Blueprint file', + default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'), + ) + parser.add_argument( + '--output', + help='Blueprint file to create', + default=os.path.join(gn_utils.repo_root(), 'Android.bp'), + ) + parser.add_argument( + '-v', + '--verbose', + help='Print debug logs.', + action='store_true', + ) + parser.add_argument( + 'targets', + nargs=argparse.REMAINDER, + help='Targets to include in the blueprint (e.g., "//:perfetto_tests")') + parser.add_argument( + '--suffix', + help='The suffix to the Android.bp filename. Pass "" if no suffix.', + default='.gn2bp') + parser.add_argument( + '--channel', + help='The channel this Android.bp generation is being performed for.', + type=str, + choices=['tot', 'stable'], + default='tot') + group = parser.add_mutually_exclusive_group() + group.add_argument( + '--license', + help='Generate license.', + dest='license', + action='store_true', + ) + group.add_argument( + '--no-license', + help='Do not generate license.', + dest='license', + action='store_false', + ) + parser.set_defaults(license=True) + args = parser.parse_args() - if args.verbose: - log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', - level=log.DEBUG) + if args.verbose: + log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', + level=log.DEBUG) - initialize_globals(args.channel) - targets = args.targets or gn2bp_targets.DEFAULT_TARGETS - build_scripts_output = None - with open(args.build_script_output) as f: - build_scripts_output = json.load(f) - gn = gn_utils.GnParser(builtin_deps, build_scripts_output) - for desc_file in args.desc: - with open(desc_file) as f: - desc = json.load(f) - for target in targets: - gn.parse_gn_desc(desc, target) - for test_target in gn2bp_targets.DEFAULT_TESTS: - gn.parse_gn_desc(desc, test_target, is_test_target=True) - top_level_blueprint = create_blueprint_for_targets( - gn, targets, gn2bp_targets.DEFAULT_TESTS) + initialize_globals(args.channel) + targets = args.targets or gn2bp_targets.DEFAULT_TARGETS + build_scripts_output = None + with open(args.build_script_output) as f: + build_scripts_output = json.load(f) + gn = gn_utils.GnParser(builtin_deps, build_scripts_output) + for desc_file in args.desc: + with open(desc_file) as f: + desc = json.load(f) + for target in targets: + gn.parse_gn_desc(desc, target) + for test_target in gn2bp_targets.DEFAULT_TESTS: + gn.parse_gn_desc(desc, test_target, is_test_target=True) + top_level_blueprint = create_blueprint_for_targets( + gn, targets, gn2bp_targets.DEFAULT_TESTS) - final_blueprints = _break_down_blueprint(top_level_blueprint) - if args.license: - license_modules = create_license_modules(final_blueprints) - for (path, module) in license_modules.items(): - final_blueprints[path].set_license_module(module) + final_blueprints = _break_down_blueprint(top_level_blueprint) + if args.license: + license_modules = create_license_modules(final_blueprints) + for (path, module) in license_modules.items(): + final_blueprints[path].set_license_module(module) - finalize_package_modules(final_blueprints) + finalize_package_modules(final_blueprints) - header = """// Copyright (C) 2022 The Android Open Source Project + header = """// Copyright (C) 2022 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -4080,16 +4164,16 @@ // This file is automatically generated by %s. Do not edit. """ % (Path(__file__).name) - for (path, blueprint) in final_blueprints.items(): - # Copybara only includes the Android.bp files generated with .gn2bp suffix - filename = "Android.bp" + args.suffix - android_bp_file = Path(os.path.join(args.repo_root, path, filename)) - android_bp_file.write_text( - "\n".join([header] + BLUEPRINTS_EXTRAS.get(path, []) + - blueprint.to_string())) + for (path, blueprint) in final_blueprints.items(): + # Copybara only includes the Android.bp files generated with .gn2bp suffix + filename = "Android.bp" + args.suffix + android_bp_file = Path(os.path.join(args.repo_root, path, filename)) + android_bp_file.write_text( + "\n".join([header] + BLUEPRINTS_EXTRAS.get(path, []) + + blueprint.to_string())) - return 0 + return 0 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/gn2bp/generate_build_scripts_output.py b/components/cronet/gn2bp/generate_build_scripts_output.py index 748cf3e..1ef7a5f 100644 --- a/components/cronet/gn2bp/generate_build_scripts_output.py +++ b/components/cronet/gn2bp/generate_build_scripts_output.py
@@ -44,7 +44,7 @@ def _extract_crate_path(args: List[str]) -> str: - """Extracts the path where the crate actually exist from the args of a build script + """Extracts the path where the crate actually exist from the args of a build script action. Args: @@ -53,53 +53,54 @@ Returns: The path to the rust crate relative to Chromium repository. """ - return args[args.index("--src-dir") + 1].replace("../../", "") + return args[args.index("--src-dir") + 1].replace("../../", "") def _get_toolchain_name(toolchain_label: str) -> str: - return toolchain_label[toolchain_label.find(":") + 1:] + return toolchain_label[toolchain_label.find(":") + 1:] def _get_toolchain_label_from_label(target_label: str) -> str: - return target_label[target_label.find('(') + 1:-1] + return target_label[target_label.find('(') + 1:-1] def _get_label_name_without_toolchain(target_label: str) -> str: - return target_label[:target_label.find("(")] + return target_label[:target_label.find("(")] def _build_rust_build_script_actions( - out_path: str, host_variant: bool) -> Dict[str, Dict[str, any]]: - targets_data = json.loads( - cronet_utils.run_and_get_stdout([ - cronet_utils.GN_PATH, "desc", out_path, "//*", "--format=json", - "--type=action" - ])) - possible_candidates = {} - for target_name, target_data in targets_data.items(): - if gn2bp_common.is_rust_build_script(target_data.get("script", "")): - # "clang_x64" assumes that the host uses clang to compile for the host machine. - # which is mostly true, unless gcc is used which is a case that we don't care about. - if host_variant and _get_toolchain_name( - target_data['toolchain']).startswith("clang_x64"): - possible_candidates[target_name[2:]] = target_data - elif not host_variant and _get_toolchain_name( - target_data['toolchain']).startswith("android_clang_"): - possible_candidates[target_name[2:]] = target_data + out_path: str, host_variant: bool) -> Dict[str, Dict[str, any]]: + targets_data = json.loads( + cronet_utils.run_and_get_stdout([ + cronet_utils.GN_PATH, "desc", out_path, "//*", "--format=json", + "--type=action" + ])) + possible_candidates = {} + for target_name, target_data in targets_data.items(): + if gn2bp_common.is_rust_build_script(target_data.get("script", "")): + # "clang_x64" assumes that the host uses clang to compile for the host machine. + # which is mostly true, unless gcc is used which is a case that we don't care about. + if host_variant and _get_toolchain_name( + target_data['toolchain']).startswith("clang_x64"): + possible_candidates[target_name[2:]] = target_data + elif not host_variant and _get_toolchain_name( + target_data['toolchain']).startswith("android_clang_"): + possible_candidates[target_name[2:]] = target_data - # The generated Ninja targets do not support the "//...(//toolchain)` syntax. So to workaround - # this limitation, build the phony target which matches the host-toolchain target. A target whose - # name is "A/B/C:D(//path/to/toolchain:toolchain_name)" will be mapped to - # "toolchain_name/phony/A/B/C/D". - cronet_utils.build_targets_list_chunking(out_path, [ - name if not host_variant else - f'{_get_toolchain_name(_get_toolchain_label_from_label(name))}/phony/{_get_label_name_without_toolchain(name.replace(":", "/"))}' - for name in possible_candidates - ]) - return possible_candidates + # The generated Ninja targets do not support the "//...(//toolchain)` syntax. So to workaround + # this limitation, build the phony target which matches the host-toolchain target. A target whose + # name is "A/B/C:D(//path/to/toolchain:toolchain_name)" will be mapped to + # "toolchain_name/phony/A/B/C/D". + cronet_utils.build_targets_list_chunking(out_path, [ + name if not host_variant else + f'{_get_toolchain_name(_get_toolchain_label_from_label(name))}/phony/{_get_label_name_without_toolchain(name.replace(":", "/"))}' + for name in possible_candidates + ]) + return possible_candidates + def _get_target_name_from_file(file_name: str) -> str: - """Extracts the target name which generated the file from + """Extracts the target name which generated the file from the directory path. The file_name format is "[X]/gen/path/to/BUILDGN/target_name/file_name" @@ -114,94 +115,97 @@ Returns: GN target label that generated the file. """ - # Remove everything before gen/ directory - file_name = re.sub(".*gen\/", "", file_name) - dirs = file_name.split("/") - build_gn_path = "/".join(dirs[0:-2]) - target_name = dirs[-2] - return f"//{build_gn_path}:{target_name}" + # Remove everything before gen/ directory + file_name = re.sub(".*gen\/", "", file_name) + dirs = file_name.split("/") + build_gn_path = "/".join(dirs[0:-2]) + target_name = dirs[-2] + return f"//{build_gn_path}:{target_name}" def _generate_and_copy_build_script_outputs_for_host() -> Dict[str, any]: - return _generate_and_copy_build_script_outputs_for_arch(arch="x64", - host_variant=True) + return _generate_and_copy_build_script_outputs_for_arch(arch="x64", + host_variant=True) def _generate_and_copy_build_script_outputs_for_arch(arch: str, host_variant: bool = False ) -> Dict[str, any]: - # gn desc behaves completely differently when the output - # directory is outside of chromium/src, some paths will - # stop having // in the beginning of their labels - # eg (//A/B will become A/B), this mostly apply to files - # that are generated through actions and not targets. - # This is why the temporary directory has to be generated - # beneath the repository root until gn2bp is tweaked to - # deal with this small differences. - target_name_to_build_script_output = {} - with tempfile.TemporaryDirectory(dir=_OUT_DIR) as gn_out_dir: - cronet_utils.gn(gn_out_dir, - ' '.join(cronet_utils.get_gn_args_for_aosp(arch))) - candidate_targets = _build_rust_build_script_actions( - gn_out_dir, host_variant=host_variant) - for target_data in candidate_targets.values(): - output_files = [ - '/'.join(file_name.removeprefix("//").split("/")[2:]) - for file_name in target_data["outputs"] if file_name.endswith(".rs") - ] + # gn desc behaves completely differently when the output + # directory is outside of chromium/src, some paths will + # stop having // in the beginning of their labels + # eg (//A/B will become A/B), this mostly apply to files + # that are generated through actions and not targets. + # This is why the temporary directory has to be generated + # beneath the repository root until gn2bp is tweaked to + # deal with this small differences. + target_name_to_build_script_output = {} + with tempfile.TemporaryDirectory(dir=_OUT_DIR) as gn_out_dir: + cronet_utils.gn(gn_out_dir, + ' '.join(cronet_utils.get_gn_args_for_aosp(arch))) + candidate_targets = _build_rust_build_script_actions( + gn_out_dir, host_variant=host_variant) + for target_data in candidate_targets.values(): + output_files = [ + '/'.join(file_name.removeprefix("//").split("/")[2:]) + for file_name in target_data["outputs"] + if file_name.endswith(".rs") + ] - cargo_rs = next(file for file in output_files - if file.endswith("cargo_flags.rs")) - target_name_to_build_script_output[_get_target_name_from_file( - cargo_rs)] = cronet_utils.read_file(os.path.join( - gn_out_dir, cargo_rs)).rstrip("\n").split("\n") - for output_file in output_files: - output_path = os.path.join( - REPOSITORY_ROOT, - _extract_crate_path(target_data['args']), - "gn2bp_rust_build_script_outputs", - (arch if not host_variant else "host"), - # TODO(crbug.com/448059753): Support outputs nested in a sub-directory. - output_file.split("/")[-1]) - os.makedirs(os.path.dirname(output_path), exist_ok=True) - shutil.copy(os.path.join(gn_out_dir, output_file), output_path) - return target_name_to_build_script_output + cargo_rs = next(file for file in output_files + if file.endswith("cargo_flags.rs")) + target_name_to_build_script_output[_get_target_name_from_file( + cargo_rs)] = cronet_utils.read_file( + os.path.join(gn_out_dir, + cargo_rs)).rstrip("\n").split("\n") + for output_file in output_files: + output_path = os.path.join( + REPOSITORY_ROOT, + _extract_crate_path(target_data['args']), + "gn2bp_rust_build_script_outputs", + (arch if not host_variant else "host"), + # TODO(crbug.com/448059753): Support outputs nested in a sub-directory. + output_file.split("/")[-1]) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + shutil.copy(os.path.join(gn_out_dir, output_file), output_path) + return target_name_to_build_script_output def _generate_build_scripts_outputs( - archs: List[str], - targets: List[str] = None) -> Dict[str, Dict[str, List[str]]]: - build_scripts_output_per_arch = {} - with multiprocessing.dummy.Pool(len(archs)) as pool: - results = [ - (arch, - pool.apply_async(_generate_and_copy_build_script_outputs_for_arch, - (arch, False))) for arch in archs - ] - for (arch, result) in results: - build_script_output = result.get() - for (target_name, output) in build_script_output.items(): - if targets and target_name not in targets: - continue - if target_name not in build_scripts_output_per_arch: - build_scripts_output_per_arch[target_name] = {} - build_scripts_output_per_arch[target_name][arch] = output + archs: List[str], + targets: List[str] = None) -> Dict[str, Dict[str, List[str]]]: + build_scripts_output_per_arch = {} + with multiprocessing.dummy.Pool(len(archs)) as pool: + results = [ + (arch, + pool.apply_async(_generate_and_copy_build_script_outputs_for_arch, + (arch, False))) for arch in archs + ] + for (arch, result) in results: + build_script_output = result.get() + for (target_name, output) in build_script_output.items(): + if targets and target_name not in targets: + continue + if target_name not in build_scripts_output_per_arch: + build_scripts_output_per_arch[target_name] = {} + build_scripts_output_per_arch[target_name][arch] = output - # Generate host-specific build script outputs - build_script_output = _generate_and_copy_build_script_outputs_for_host() - for (target_name, output) in build_script_output.items(): - if targets and target_name not in targets: - continue - if target_name not in build_scripts_output_per_arch: - build_scripts_output_per_arch[target_name] = {} - build_scripts_output_per_arch[target_name]["host"] = output - return build_scripts_output_per_arch + # Generate host-specific build script outputs + build_script_output = _generate_and_copy_build_script_outputs_for_host() + for (target_name, output) in build_script_output.items(): + if targets and target_name not in targets: + continue + if target_name not in build_scripts_output_per_arch: + build_scripts_output_per_arch[target_name] = {} + build_scripts_output_per_arch[target_name]["host"] = output + return build_scripts_output_per_arch + def dump_build_scripts_outputs_to_file( - output_file_path: str, - archs: List[str], - targets_to_build: List[str] = None) -> None: - """Dumps a JSON formatted string that maps from target + output_file_path: str, + archs: List[str], + targets_to_build: List[str] = None) -> None: + """Dumps a JSON formatted string that maps from target name to build scripts output. Args: @@ -210,25 +214,27 @@ targets_to_build: If specified, only those targets build_script will be present in the final output. Otherwise, everything will be available. """ - with open(output_file_path, "w") as output_file: - output_file.write( - json.dumps(_generate_build_scripts_outputs(archs, targets_to_build), - indent=2, - sort_keys=True)) + with open(output_file_path, "w") as output_file: + output_file.write( + json.dumps(_generate_build_scripts_outputs(archs, + targets_to_build), + indent=2, + sort_keys=True)) + def main(): - parser = argparse.ArgumentParser( - description= - 'Generates a JSON dictionary containing the mapping between GN target labels to Rust build script output' - ) - parser.add_argument( - '--output', - type=str, - help='Path to file for which the output will be written to', - required=True) - args = parser.parse_args() - dump_build_scripts_outputs_to_file(args.output, _ARCHS) + parser = argparse.ArgumentParser( + description= + 'Generates a JSON dictionary containing the mapping between GN target labels to Rust build script output' + ) + parser.add_argument( + '--output', + type=str, + help='Path to file for which the output will be written to', + required=True) + args = parser.parse_args() + dump_build_scripts_outputs_to_file(args.output, _ARCHS) if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/gn2bp/gn_utils.py b/components/cronet/gn2bp/gn_utils.py index 407359ec4..e284365 100644 --- a/components/cronet/gn2bp/gn_utils.py +++ b/components/cronet/gn2bp/gn_utils.py
@@ -20,7 +20,8 @@ TOOLCHAIN_SUFFIX = "__toolchain_" HOST_TOOLCHAIN_TO_SUFFIX = { - '//build/toolchain/linux:clang_x64': 'clang', + '//build/toolchain/linux:clang_x64': + 'clang', '//build/toolchain/linux:clang_x64_for_rust_host_build_tools': 'clang_for_rust', } @@ -39,105 +40,111 @@ def repo_root(): - """Returns an absolute path to the repository root.""" - return os.path.join(os.path.realpath(os.path.dirname(__file__)), - os.path.pardir) + """Returns an absolute path to the repository root.""" + return os.path.join(os.path.realpath(os.path.dirname(__file__)), + os.path.pardir) def _get_build_path_from_label(target_name: str) -> str: - """Returns the path to the BUILD file for which this target was declared.""" - return target_name[2:].split(":")[0] + """Returns the path to the BUILD file for which this target was declared.""" + return target_name[2:].split(":")[0] def _clean_string(string): - return string.replace('\\', '').replace('../../', '').replace('"', '').strip() + return string.replace('\\', '').replace('../../', '').replace('"', + '').strip() def _extract_rust_package_version(env_args): - for arg in env_args: - is_match = re.match(r'CARGO_PKG_VERSION=(.+)', arg) - if is_match: - return is_match.group(1) - return None + for arg in env_args: + is_match = re.match(r'CARGO_PKG_VERSION=(.+)', arg) + if is_match: + return is_match.group(1) + return None + def _extract_includes_from_aidl_args(args): - for arg in args: - is_match = re.match(AIDL_INCLUDE_DIRS_REGEX, arg) - if is_match: - local_includes = is_match.group(1).split(",") - return [_clean_string(local_include) for local_include in local_includes] - return [] + for arg in args: + is_match = re.match(AIDL_INCLUDE_DIRS_REGEX, arg) + if is_match: + local_includes = is_match.group(1).split(",") + return [ + _clean_string(local_include) + for local_include in local_includes + ] + return [] def _get_jni_registration_deps(gn_target_name, gn_desc): - # the dependencies are stored within another target with the same name - # and a __java_sources suffix, see - # https://source.chromium.org/chromium/chromium/src/+/main:third_party/jni_zero/jni_zero.gni;l=117;drc=78e8e27142ed3fddf04fbcd122507517a87cb9ad - # for the auto-generated target name. - jni_registration_java_target = f'{gn_target_name}__java_sources' - if jni_registration_java_target in gn_desc.keys(): - return gn_desc[jni_registration_java_target]["deps"] - return set() + # the dependencies are stored within another target with the same name + # and a __java_sources suffix, see + # https://source.chromium.org/chromium/chromium/src/+/main:third_party/jni_zero/jni_zero.gni;l=117;drc=78e8e27142ed3fddf04fbcd122507517a87cb9ad + # for the auto-generated target name. + jni_registration_java_target = f'{gn_target_name}__java_sources' + if jni_registration_java_target in gn_desc.keys(): + return gn_desc[jni_registration_java_target]["deps"] + return set() def label_to_path(label): - """Turn a GN output label (e.g., //some_dir/file.cc) into a path.""" - assert label.startswith('//') - return label[2:] or "" + """Turn a GN output label (e.g., //some_dir/file.cc) into a path.""" + assert label.startswith('//') + return label[2:] or "" def label_without_toolchain(label): - """Strips the toolchain from a GN label. + """Strips the toolchain from a GN label. Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain: gcc_like_host) without the parenthesised toolchain part. """ - return label.split('(')[0] + return label.split('(')[0] def _is_java_source(src): - return os.path.splitext(src)[1] == '.java' and not src.startswith("//out/") + return os.path.splitext(src)[1] == '.java' and not src.startswith("//out/") def _remove_out_prefix(label): - return re.sub('^//out/.+?/(gen|obj)/', '', label) + return re.sub('^//out/.+?/(gen|obj)/', '', label) def _filter_cflags(cflags): - cflags_processed = [] - # This is set to true when we have combined two consecutive indices into a single - # string and want to skip the next iteration. - skip_next = False - for i in range(len(cflags)): - if skip_next: - skip_next = False - continue + cflags_processed = [] + # This is set to true when we have combined two consecutive indices into a single + # string and want to skip the next iteration. + skip_next = False + for i in range(len(cflags)): + if skip_next: + skip_next = False + continue - if cflags[i].startswith("-Xclang"): - # We don't care about -Xclang values as they're solely for the front-end to - # enable some static analysis on the code. It's important to disable this as - # there exists code that is implemented in Chromium, but built only in AOSP. - # We don't want those to stop building due to some statical analysis error. - skip_next = True - else: - cflags_processed.append(cflags[i]) - return cflags_processed + if cflags[i].startswith("-Xclang"): + # We don't care about -Xclang values as they're solely for the front-end to + # enable some static analysis on the code. It's important to disable this as + # there exists code that is implemented in Chromium, but built only in AOSP. + # We don't want those to stop building due to some statical analysis error. + skip_next = True + else: + cflags_processed.append(cflags[i]) + return cflags_processed + def _filter_defines(defines): - # These C++ defines are not actually used in code; Chromium only uses them to - # force rebuilds on rolls of certain dependencies. They don't hurt, per se, - # but they do create annoying diff noise on Android.bp files, so we drop them - # for aesthetic/convenience reasons. - EXCLUDED_DEFINES = { - "CR_CLANG_REVISION", "CR_LIBCXX_REVISION", "ANDROID_NDK_VERSION_ROLL" - } - return (define for define in defines if not any( - define.startswith(f"{excluded_define}=") - for excluded_define in EXCLUDED_DEFINES)) + # These C++ defines are not actually used in code; Chromium only uses them to + # force rebuilds on rolls of certain dependencies. They don't hurt, per se, + # but they do create annoying diff noise on Android.bp files, so we drop them + # for aesthetic/convenience reasons. + EXCLUDED_DEFINES = { + "CR_CLANG_REVISION", "CR_LIBCXX_REVISION", "ANDROID_NDK_VERSION_ROLL" + } + return (define for define in defines if not any( + define.startswith(f"{excluded_define}=") + for excluded_define in EXCLUDED_DEFINES)) class GnParser: - """A parser with some cleverness for GN json desc files + """A parser with some cleverness for GN json desc files The main goals of this parser are: 1) Deal with the fact that other build systems don't have an equivalent @@ -153,652 +160,671 @@ being used. """ - class Target: - """Reperesents A GN target. + class Target: + """Reperesents A GN target. Maked properties are propagated up the dependency chain when a source_set dependency is encountered. """ - class Arch(): - """Architecture-dependent properties + class Arch(): + """Architecture-dependent properties """ - def __init__(self): - self.sources = set() - self.cflags = [] - self.defines = set() - self.include_dirs = set() - self.deps = set() - self.transitive_static_libs_deps = set() - self.ldflags = [] - # These are valid only for type == 'action' - self.inputs = set() - self.outputs = set() - self.args = [] - self.response_file_contents = '' - self.rust_flags = list() - self.libs = set() + def __init__(self): + self.sources = set() + self.cflags = [] + self.defines = set() + self.include_dirs = set() + self.deps = set() + self.transitive_static_libs_deps = set() + self.ldflags = [] + # These are valid only for type == 'action' + self.inputs = set() + self.outputs = set() + self.args = [] + self.response_file_contents = '' + self.rust_flags = list() + self.libs = set() - def __init__(self, name, gn_type): - self.name = name # e.g. //src/ipc:ipc + def __init__(self, name, gn_type): + self.name = name # e.g. //src/ipc:ipc - VALID_TYPES = ('static_library', 'shared_library', 'executable', 'group', - 'action', 'source_set', 'proto_library', 'copy', - 'action_foreach', 'generated_file', "rust_library", - "rust_proc_macro") - assert ( - gn_type - in VALID_TYPES), f"Unable to parse target {name} with type {gn_type}." - self.type = gn_type - self.testonly = False - self.toolchain = None + VALID_TYPES = ('static_library', 'shared_library', 'executable', + 'group', 'action', 'source_set', 'proto_library', + 'copy', 'action_foreach', 'generated_file', + "rust_library", "rust_proc_macro") + assert (gn_type in VALID_TYPES + ), f"Unable to parse target {name} with type {gn_type}." + self.type = gn_type + self.testonly = False + self.toolchain = None - # These are valid only for type == proto_library. - self.proto_paths = set() - self.proto_exports = set() - self.proto_in_dir = "" + # These are valid only for type == proto_library. + self.proto_paths = set() + self.proto_exports = set() + self.proto_in_dir = "" - # TODO(primiano): consider whether the public section should be part of - # bubbled-up sources. - self.public_headers = set() # 'public' + # TODO(primiano): consider whether the public section should be part of + # bubbled-up sources. + self.public_headers = set() # 'public' - # These are valid only for gn_type == 'action' - self.script = '' + # These are valid only for gn_type == 'action' + self.script = '' - self.proto_deps = set() - self.rtti = False + self.proto_deps = set() + self.rtti = False - # TODO: come up with a better way to only run this once. - # is_finalized tracks whether finalize() was called on this target. - self.is_finalized = False - # 'common' is a pseudo-architecture used to store common architecture dependent properties (to - # make handling of common vs architecture-specific arguments more consistent). - self.arch = {'common': self.Arch()} + # TODO: come up with a better way to only run this once. + # is_finalized tracks whether finalize() was called on this target. + self.is_finalized = False + # 'common' is a pseudo-architecture used to store common architecture dependent properties (to + # make handling of common vs architecture-specific arguments more consistent). + self.arch = {'common': self.Arch()} - # This is used to get the name/version of libcronet - self.output_name = None - # Local Includes used for AIDL - self.aidl_includes = set() - # Each java_target will contain the transitive java sources found - # in generate_jni gn_type target. - self.transitive_jni_java_sources = set() - # Deps for JNI Registration. Those are not added to deps so that - # the generated module would not depend on those deps. - self.jni_registration_java_deps = set() - self.sdk_version = "" - self.build_file_path = "" - self.crate_name = None - self.crate_root = None + # This is used to get the name/version of libcronet + self.output_name = None + # Local Includes used for AIDL + self.aidl_includes = set() + # Each java_target will contain the transitive java sources found + # in generate_jni gn_type target. + self.transitive_jni_java_sources = set() + # Deps for JNI Registration. Those are not added to deps so that + # the generated module would not depend on those deps. + self.jni_registration_java_deps = set() + self.sdk_version = "" + self.build_file_path = "" + self.crate_name = None + self.crate_root = None - self.java_jar_excluded_patterns = [] - self.java_jar_included_patterns = [] - # This is only populated for build script actions. It refers to the directory for which - # the original source files are. - self.rust_source_dir = None - self.rust_package_version = None + self.java_jar_excluded_patterns = [] + self.java_jar_included_patterns = [] + # This is only populated for build script actions. It refers to the directory for which + # the original source files are. + self.rust_source_dir = None + self.rust_package_version = None - # Properties to forward access to common arch. - # TODO: delete these after the transition has been completed. - @property - def sources(self): - return self.arch['common'].sources + # Properties to forward access to common arch. + # TODO: delete these after the transition has been completed. + @property + def sources(self): + return self.arch['common'].sources - @sources.setter - def sources(self, val): - self.arch['common'].sources = val + @sources.setter + def sources(self, val): + self.arch['common'].sources = val - @property - def inputs(self): - return self.arch['common'].inputs + @property + def inputs(self): + return self.arch['common'].inputs - @inputs.setter - def inputs(self, val): - self.arch['common'].inputs = val + @inputs.setter + def inputs(self, val): + self.arch['common'].inputs = val - @property - def outputs(self): - return self.arch['common'].outputs + @property + def outputs(self): + return self.arch['common'].outputs - @property - def libs(self): - return self.arch['common'].libs + @property + def libs(self): + return self.arch['common'].libs - @outputs.setter - def outputs(self, val): - self.arch['common'].outputs = val + @outputs.setter + def outputs(self, val): + self.arch['common'].outputs = val - @property - def args(self): - return self.arch['common'].args + @property + def args(self): + return self.arch['common'].args - @args.setter - def args(self, val): - self.arch['common'].args = val + @args.setter + def args(self, val): + self.arch['common'].args = val - @property - def response_file_contents(self): - return self.arch['common'].response_file_contents + @property + def response_file_contents(self): + return self.arch['common'].response_file_contents - @response_file_contents.setter - def response_file_contents(self, val): - self.arch['common'].response_file_contents = val + @response_file_contents.setter + def response_file_contents(self, val): + self.arch['common'].response_file_contents = val - @property - def cflags(self): - return self.arch['common'].cflags + @property + def cflags(self): + return self.arch['common'].cflags - @cflags.setter - def cflags(self, val): - self.arch['common'].cflags = val + @cflags.setter + def cflags(self, val): + self.arch['common'].cflags = val - @property - def defines(self): - return self.arch['common'].defines + @property + def defines(self): + return self.arch['common'].defines - @property - def deps(self): - return self.arch['common'].deps + @property + def deps(self): + return self.arch['common'].deps - @deps.setter - def deps(self, val): - self.arch['common'].deps = val + @deps.setter + def deps(self, val): + self.arch['common'].deps = val - @property - def rust_flags(self): - return self.arch['common'].rust_flags + @property + def rust_flags(self): + return self.arch['common'].rust_flags - @rust_flags.setter - def rust_flags(self, val): - self.arch['common'].rust_flags = val + @rust_flags.setter + def rust_flags(self, val): + self.arch['common'].rust_flags = val - @property - def include_dirs(self): - return self.arch['common'].include_dirs + @property + def include_dirs(self): + return self.arch['common'].include_dirs - @property - def ldflags(self): - return self.arch['common'].ldflags + @property + def ldflags(self): + return self.arch['common'].ldflags - @ldflags.setter - def ldflags(self, val): - self.arch['common'].ldflags = val + @ldflags.setter + def ldflags(self, val): + self.arch['common'].ldflags = val - def host_supported(self): - return 'host' in self.arch + def host_supported(self): + return 'host' in self.arch - def device_supported(self): - return any(name.startswith('android') for name in self.arch) + def device_supported(self): + return any(name.startswith('android') for name in self.arch) - def is_linker_unit_type(self): - return self.type in LINKER_UNIT_TYPES + def is_linker_unit_type(self): + return self.type in LINKER_UNIT_TYPES - def __lt__(self, other): - if isinstance(other, self.__class__): - return self.name < other.name - raise TypeError( - '\'<\' not supported between instances of \'%s\' and \'%s\'' % - (type(self).__name__, type(other).__name__)) + def __lt__(self, other): + if isinstance(other, self.__class__): + return self.name < other.name + raise TypeError( + '\'<\' not supported between instances of \'%s\' and \'%s\'' % + (type(self).__name__, type(other).__name__)) - def __repr__(self): - return json.dumps( - { - k: (list(sorted(v)) if isinstance(v, set) else v) - for (k, v) in self.__dict__.items() - }, - indent=4, - sort_keys=True) + def __repr__(self): + return json.dumps( + { + k: (list(sorted(v)) if isinstance(v, set) else v) + for (k, v) in self.__dict__.items() + }, + indent=4, + sort_keys=True) - def update(self, other, arch): - for key in ('cflags', 'defines', 'deps', 'include_dirs', 'ldflags', - 'proto_deps', 'proto_paths'): - val = getattr(self, key) - if isinstance(val, set): - # The pylint is confused as it does not understand that this line is protected - # behind a type-check via `isinstance` - # pylint: disable=no-member - val.update(getattr(other, key, ())) - elif isinstance(val, list): - val.extend(getattr(other, key, [])) + def update(self, other, arch): + for key in ('cflags', 'defines', 'deps', 'include_dirs', 'ldflags', + 'proto_deps', 'proto_paths'): + val = getattr(self, key) + if isinstance(val, set): + # The pylint is confused as it does not understand that this line is protected + # behind a type-check via `isinstance` + # pylint: disable=no-member + val.update(getattr(other, key, ())) + elif isinstance(val, list): + val.extend(getattr(other, key, [])) - for key_in_arch in ('cflags', 'defines', 'include_dirs', 'deps', - 'ldflags', 'libs'): - val = getattr(self.arch[arch], key_in_arch) - if isinstance(val, set): - val.update(getattr(other.arch[arch], key_in_arch, [])) - elif isinstance(val, list): - val.extend(getattr(other.arch[arch], key_in_arch, [])) + for key_in_arch in ('cflags', 'defines', 'include_dirs', 'deps', + 'ldflags', 'libs'): + val = getattr(self.arch[arch], key_in_arch) + if isinstance(val, set): + val.update(getattr(other.arch[arch], key_in_arch, [])) + elif isinstance(val, list): + val.extend(getattr(other.arch[arch], key_in_arch, [])) + def get_archs(self): + """ Returns a dict of archs without the common arch """ + return { + arch: val + for arch, val in self.arch.items() if arch != 'common' + } - def get_archs(self): - """ Returns a dict of archs without the common arch """ - return {arch: val for arch, val in self.arch.items() if arch != 'common'} + def _finalize_set_attribute(self, key): + # Target contains the intersection of arch-dependent properties + getattr(self, key).update( + set.intersection( + * + [getattr(arch, key) + for arch in self.get_archs().values()])) - def _finalize_set_attribute(self, key): - # Target contains the intersection of arch-dependent properties - getattr(self, key).update( - set.intersection( - *[getattr(arch, key) for arch in self.get_archs().values()])) + # Deduplicate arch-dependent properties + for arch in self.get_archs().values(): + getattr(arch, key).difference_update(getattr(self, key)) - # Deduplicate arch-dependent properties - for arch in self.get_archs().values(): - getattr(arch, key).difference_update(getattr(self, key)) + def _finalize_non_set_attribute(self, key): + # Only when all the arch has the same non empty value, move the value to the target common + val = getattr(list(self.get_archs().values())[0], key) + if val and all(val == getattr(arch, key) + for arch in self.get_archs().values()): + setattr(self, key, copy.deepcopy(val)) - def _finalize_non_set_attribute(self, key): - # Only when all the arch has the same non empty value, move the value to the target common - val = getattr(list(self.get_archs().values())[0], key) - if val and all(val == getattr(arch, key) - for arch in self.get_archs().values()): - setattr(self, key, copy.deepcopy(val)) + def _finalize_attribute(self, key): + val = getattr(self, key) + if isinstance(val, set): + self._finalize_set_attribute(key) + elif isinstance(val, (list, str)): + self._finalize_non_set_attribute(key) + else: + raise TypeError(f'Unsupported type: {type(val)}') - def _finalize_attribute(self, key): - val = getattr(self, key) - if isinstance(val, set): - self._finalize_set_attribute(key) - elif isinstance(val, (list, str)): - self._finalize_non_set_attribute(key) - else: - raise TypeError(f'Unsupported type: {type(val)}') - - def finalize(self): - """Move common properties out of arch-dependent subobjects to Target object. + def finalize(self): + """Move common properties out of arch-dependent subobjects to Target object. TODO: find a better name for this function. """ - if self.is_finalized: - return - self.is_finalized = True + if self.is_finalized: + return + self.is_finalized = True - if len(self.arch) == 1: - return + if len(self.arch) == 1: + return - for key in ('sources', 'cflags', 'defines', 'include_dirs', 'deps', - 'inputs', 'outputs', 'args', 'response_file_contents', - 'ldflags', 'rust_flags', 'libs'): - self._finalize_attribute(key) + for key in ('sources', 'cflags', 'defines', 'include_dirs', 'deps', + 'inputs', 'outputs', 'args', 'response_file_contents', + 'ldflags', 'rust_flags', 'libs'): + self._finalize_attribute(key) - def get_target_name(self): - return self.name[self.name.find(":") + 1:] + def get_target_name(self): + return self.name[self.name.find(":") + 1:] - def __init__(self, builtin_deps, build_script_outputs): - self.builtin_deps = builtin_deps - self.build_script_outputs = build_script_outputs - self.all_targets = {} - self.jni_java_sources = set() + def __init__(self, builtin_deps, build_script_outputs): + self.builtin_deps = builtin_deps + self.build_script_outputs = build_script_outputs + self.all_targets = {} + self.jni_java_sources = set() - def _get_response_file_contents(self, action_desc): - # GN response_file_contents docs state "The response file contents will - # always be quoted and escaped according to Unix shell rules". Reproduce - # GN's behavior. - return ' '.join( - shlex.quote(arg) - for arg in action_desc.get('response_file_contents', [])) + def _get_response_file_contents(self, action_desc): + # GN response_file_contents docs state "The response file contents will + # always be quoted and escaped according to Unix shell rules". Reproduce + # GN's behavior. + return ' '.join( + shlex.quote(arg) + for arg in action_desc.get('response_file_contents', [])) - def _is_java_group(self, type_, target_name): - # Per https://chromium.googlesource.com/chromium/src/build/+/HEAD/android/docs/java_toolchain.md - # java target names must end in "_java". - # TODO: There are some other possible variations we might need to support. - return type_ == 'group' and target_name.endswith('_java') + def _is_java_group(self, type_, target_name): + # Per https://chromium.googlesource.com/chromium/src/build/+/HEAD/android/docs/java_toolchain.md + # java target names must end in "_java". + # TODO: There are some other possible variations we might need to support. + return type_ == 'group' and target_name.endswith('_java') - def _get_arch(self, toolchain): - if toolchain == '//build/toolchain/android:android_clang_x86': - return 'android_x86', 'x86' - if toolchain == '//build/toolchain/android:android_clang_x64': - return 'android_x86_64', 'x64' - if toolchain == '//build/toolchain/android:android_clang_arm': - return 'android_arm', 'arm' - if toolchain == '//build/toolchain/android:android_clang_arm64': - return 'android_arm64', 'arm64' - if toolchain == '//build/toolchain/android:android_clang_riscv64': - return 'android_riscv64', 'riscv64' - if toolchain in HOST_TOOLCHAIN_TO_SUFFIX.keys(): - return 'host', 'host' - raise TypeError(f"Unknown toolchain found: {toolchain}") + def _get_arch(self, toolchain): + if toolchain == '//build/toolchain/android:android_clang_x86': + return 'android_x86', 'x86' + if toolchain == '//build/toolchain/android:android_clang_x64': + return 'android_x86_64', 'x64' + if toolchain == '//build/toolchain/android:android_clang_arm': + return 'android_arm', 'arm' + if toolchain == '//build/toolchain/android:android_clang_arm64': + return 'android_arm64', 'arm64' + if toolchain == '//build/toolchain/android:android_clang_riscv64': + return 'android_riscv64', 'riscv64' + if toolchain in HOST_TOOLCHAIN_TO_SUFFIX.keys(): + return 'host', 'host' + raise TypeError(f"Unknown toolchain found: {toolchain}") - def get_target(self, gn_target_name): - """Returns a Target object from the fully qualified GN target name. + def get_target(self, gn_target_name): + """Returns a Target object from the fully qualified GN target name. get_target() requires that parse_gn_desc() has already been called. """ - # Run this every time as parse_gn_desc can be called at any time. - for target in self.all_targets.values(): - target.finalize() + # Run this every time as parse_gn_desc can be called at any time. + for target in self.all_targets.values(): + target.finalize() - return self.all_targets[label_without_toolchain(gn_target_name)] + return self.all_targets[label_without_toolchain(gn_target_name)] - def parse_gn_desc(self, - gn_desc, - gn_target_name, - is_test_target=False, - custom_processor=None, - override_deps=None): - """Parses a gn desc tree and resolves all target dependencies. + def parse_gn_desc(self, + gn_desc, + gn_target_name, + is_test_target=False, + custom_processor=None, + override_deps=None): + """Parses a gn desc tree and resolves all target dependencies. It bubbles up variables from source_set dependencies as described in the class-level comments. """ - # Use name without toolchain for targets to support targets built for - # multiple archs. - target_name = label_without_toolchain(gn_target_name) - desc = gn_desc[gn_target_name] - type_ = desc['type'] - arch, chromium_arch = self._get_arch(desc['toolchain']) - metadata = desc.get("metadata", {}) + # Use name without toolchain for targets to support targets built for + # multiple archs. + target_name = label_without_toolchain(gn_target_name) + desc = gn_desc[gn_target_name] + type_ = desc['type'] + arch, chromium_arch = self._get_arch(desc['toolchain']) + metadata = desc.get("metadata", {}) - if arch == 'host': - # Add a custom suffix to the name. The goal here is to have host architecture - # as its own independent targets because there could be more than a single - # toolchain for the host. - target_name += f"{TOOLCHAIN_SUFFIX}{HOST_TOOLCHAIN_TO_SUFFIX[desc['toolchain']]}" + if arch == 'host': + # Add a custom suffix to the name. The goal here is to have host architecture + # as its own independent targets because there could be more than a single + # toolchain for the host. + target_name += f"{TOOLCHAIN_SUFFIX}{HOST_TOOLCHAIN_TO_SUFFIX[desc['toolchain']]}" - if is_test_target: - target_name += TESTING_SUFFIX + if is_test_target: + target_name += TESTING_SUFFIX - target = self.all_targets.get(target_name) - if target is None: - target = GnParser.Target(target_name, type_) - self.all_targets[target_name] = target + target = self.all_targets.get(target_name) + if target is None: + target = GnParser.Target(target_name, type_) + self.all_targets[target_name] = target - if arch not in target.arch: - target.arch[arch] = GnParser.Target.Arch() - else: - return target # Target already processed. - - def turn_into_java_library(java_target): - java_target.type = 'java_library' - java_target.sdk_version = 'current' - # Assume the target is unfiltered by default. This may be reassigned - # later. - java_target.unfiltered_java_target = java_target - - # In GN, java libraries are actually a hierarchy of targets with a `group` - # target at the root. We surface the target as a `java_library` for clarity. - # See below for more details on how we handle java_library. - # - # The reason why we do this now and not alongside the java_library logic is - # so that, if this target is a builtin (see below), it is still returned as - # a java_library, not as a group (which would just get ignored). - if any(metadata_key in metadata - for metadata_key in ("java_library_deps", "java_library_sources")): - turn_into_java_library(target) - - if target.name in self.builtin_deps: - # return early, no need to parse any further as the module is a builtin. - return target - - if target_name.startswith("//build/rust/std"): - # We intentionally don't parse build/rust/std as we use AOSP's stdlib. - return target - - target.testonly = desc.get('testonly', False) - if target.type == "executable" and desc.get("crate_root", None): - # Find a more decisive way to figure out that this is a rust executable. - # TODO: Add a metadata to the executable from Chromium side. - target.type = "rust_executable" - deps = desc.get("deps", []) - build_only_deps = [] - if custom_processor is not None: - custom_processor(target, desc, deps, build_only_deps) - elif desc.get("script", "") == "//tools/protoc_wrapper/protoc_wrapper.py": - target.type = 'proto_library' - target.proto_paths.update(self.get_proto_paths(desc)) - target.proto_exports.update(self.get_proto_exports(desc)) - target.proto_in_dir = self.get_proto_in_dir(desc) - target.arch[arch].sources.update(desc.get('sources', [])) - target.arch[arch].inputs.update(desc.get('inputs', [])) - target.arch[arch].outputs.update( - _remove_out_prefix(output) for output in desc['outputs']) - target.arch[arch].args = desc['args'] - elif target.type == 'source_set': - target.arch[arch].sources.update(source - for source in desc.get('sources', []) - if not source.startswith("//out")) - elif target.is_linker_unit_type(): - target.arch[arch].sources.update(source - for source in desc.get('sources', []) - if not source.startswith("//out")) - elif target.script == "//build/android/gyp/aidl.py": - target.type = "aidl_interface" - # It's assumed that all of AIDLs' attributes are not arch-specific. - target.sources.update(desc.get('sources', {})) - target.outputs.update([_remove_out_prefix(x) for x in desc['outputs']]) - target.aidl_includes = _extract_includes_from_aidl_args( - desc.get('args', '')) - elif target.type == "java_library": - log.info('Found Java Target %s', target.name) - - # Java GN targets are... complicated. The relevant GN rules are in - # //build/config/android/internal_rules.gni, specifically - # java_library_impl. What makes this challenging is this GN rule is - # extremely flexible and has tons of extra features - it's not just - # running javac like Soong's java_library does. We don't support all of - # GN's java_library features, but hopefully we support the subset that - # matters to get things to work. - # - # The `java_library` GN rule generates not just one GN target, but a whole - # hierarchy of subtargets (`_java__compile_java`, `_java__dex`, etc.) - # behind a top-level `group` target. - # - # One approach could be to look at the various subtargets and piece - # together the information we need (i.e. the Java source file paths, the - # Java deps, the jar filtering rules, etc.). But that would require - # non-trivial business logic and runs the risk of breakage when changes - # are made to the internals of GN `java_library` rules (e.g. - # https://crbug.com/412984664). - # - # Another approach could be to closely replicate the subtarget structure - # in Soong (i.e. generate one module per subtarget), but that means we - # would basically end up generating genrules that indirectly call javac - # instead of generating `java_library` modules, which feels extremely - # awkward, impractical and unlikely to work. - # - # Instead, we rely entirely on GN target metadata that the `java_library` - # GN rule helpfully attaches to the top-level group target. This makes the - # analysis trivial and completely decouples this code from the internal - # structure of the `java_library` GN subtargets. - - inputs = metadata.get("java_library_inputs", []) - target.sources.update(input for input in inputs - if not input.startswith('//out/')) - target.inputs.update(_remove_out_prefix(input) for input in inputs) - - deps.clear() - deps.extend(metadata.get("java_library_deps", [])) - - target.java_jar_excluded_patterns = metadata.get( - "java_library_jar_excluded_patterns", []) - target.java_jar_included_patterns = metadata.get( - "java_library_jar_included_patterns", []) - - android_sdk_dep = metadata.get("java_library_android_sdk_dep", None) - if android_sdk_dep is not None: - assert len(android_sdk_dep) == 1, target.name - android_sdk_dep = android_sdk_dep[0] - if android_sdk_dep == "//third_party/android_sdk:android_sdk_java": - target.sdk_version = "current" - elif android_sdk_dep == "//third_party/android_sdk:public_framework_system_java": - target.sdk_version = "system_current" + if arch not in target.arch: + target.arch[arch] = GnParser.Target.Arch() else: - raise ValueError( - f"Unexpected android_sdk_dep: {android_sdk_dep} for target {target.name}" - ) - elif desc.get("script", "") == "//build/rust/gni_impl/run_bindgen.py": - # rust_bindgen is a supported module in Soong but GN depend on actions - # so we need to copy the action fields (sources, outputs and args) in - # order to correctly generate the `rust_bindgen` module. - target.sources.update(desc.get('sources', [])) - outs = [_remove_out_prefix(x) for x in desc['outputs']] - target.outputs.update(outs) - target.args = desc['args'] - target.type = "rust_bindgen" - elif (target.type in [ - 'action', 'action_foreach' - # GN's copy is translated to Soong by making it look like a GN's action - # with a special //cp script. This works well for its only usage: - # //base:build_date_header. As the list of supported copy target grows, we might - # need to revisit this decision. - ]) or (desc['type'] == 'copy' and target.name - in ['//base:build_date_header', '//base:build_date_header__testing']): - target.arch[arch].inputs.update(desc.get('inputs', [])) - target.arch[arch].sources.update(desc.get('sources', [])) - outs = [_remove_out_prefix(x) for x in desc['outputs']] - target.arch[arch].outputs.update(outs) - # We need to check desc['type'], not target.type: targets go through - # this code multiple times. If we checked for target.type, the second - # time we parsed a copy target, we would take the else branch. - if desc['type'] == 'copy': - target.type = 'action' - target.script = '//cp' - else: - # While the arguments might differ, an action should always use the same script for every - # architecture. (gen_android_bp's get_action_sanitizer actually relies on this fact. - target.script = desc['script'] - target.arch[arch].args = desc['args'] - target.arch[ - arch].response_file_contents = self._get_response_file_contents(desc) - # _get_jni_registration_deps will return the dependencies of a target if - # the target is of type `generate_jni_registration` otherwise it will - # return an empty set. - target.jni_registration_java_deps.update( - _get_jni_registration_deps(gn_target_name, gn_desc)) - # JNI java sources are embedded as metadata inside `jni_headers` targets. - # See https://source.chromium.org/chromium/chromium/src/+/main:third_party/jni_zero/jni_zero.gni;l=421;drc=78e8e27142ed3fddf04fbcd122507517a87cb9ad - # for more details - target.transitive_jni_java_sources.update( - metadata.get("jni_source_files", set())) - self.jni_java_sources.update(metadata.get("jni_source_files", set())) - if gn2bp_common.is_rust_build_script(target.script): + return target # Target already processed. - def _extract_crate_path(args): - return args[args.index("--src-dir") + 1].replace("../../", "") + def turn_into_java_library(java_target): + java_target.type = 'java_library' + java_target.sdk_version = 'current' + # Assume the target is unfiltered by default. This may be reassigned + # later. + java_target.unfiltered_java_target = java_target - target.rust_source_dir = _extract_crate_path(desc['args']) - # Don't continue the dependencies exploration. + # In GN, java libraries are actually a hierarchy of targets with a `group` + # target at the root. We surface the target as a `java_library` for clarity. + # See below for more details on how we handle java_library. + # + # The reason why we do this now and not alongside the java_library logic is + # so that, if this target is a builtin (see below), it is still returned as + # a java_library, not as a group (which would just get ignored). + if any(metadata_key in metadata + for metadata_key in ("java_library_deps", + "java_library_sources")): + turn_into_java_library(target) + + if target.name in self.builtin_deps: + # return early, no need to parse any further as the module is a builtin. + return target + + if target_name.startswith("//build/rust/std"): + # We intentionally don't parse build/rust/std as we use AOSP's stdlib. + return target + + target.testonly = desc.get('testonly', False) + if target.type == "executable" and desc.get("crate_root", None): + # Find a more decisive way to figure out that this is a rust executable. + # TODO: Add a metadata to the executable from Chromium side. + target.type = "rust_executable" + deps = desc.get("deps", []) + build_only_deps = [] + if custom_processor is not None: + custom_processor(target, desc, deps, build_only_deps) + elif desc.get("script", + "") == "//tools/protoc_wrapper/protoc_wrapper.py": + target.type = 'proto_library' + target.proto_paths.update(self.get_proto_paths(desc)) + target.proto_exports.update(self.get_proto_exports(desc)) + target.proto_in_dir = self.get_proto_in_dir(desc) + target.arch[arch].sources.update(desc.get('sources', [])) + target.arch[arch].inputs.update(desc.get('inputs', [])) + target.arch[arch].outputs.update( + _remove_out_prefix(output) for output in desc['outputs']) + target.arch[arch].args = desc['args'] + elif target.type == 'source_set': + target.arch[arch].sources.update( + source for source in desc.get('sources', []) + if not source.startswith("//out")) + elif target.is_linker_unit_type(): + target.arch[arch].sources.update( + source for source in desc.get('sources', []) + if not source.startswith("//out")) + elif target.script == "//build/android/gyp/aidl.py": + target.type = "aidl_interface" + # It's assumed that all of AIDLs' attributes are not arch-specific. + target.sources.update(desc.get('sources', {})) + target.outputs.update( + [_remove_out_prefix(x) for x in desc['outputs']]) + target.aidl_includes = _extract_includes_from_aidl_args( + desc.get('args', '')) + elif target.type == "java_library": + log.info('Found Java Target %s', target.name) + + # Java GN targets are... complicated. The relevant GN rules are in + # //build/config/android/internal_rules.gni, specifically + # java_library_impl. What makes this challenging is this GN rule is + # extremely flexible and has tons of extra features - it's not just + # running javac like Soong's java_library does. We don't support all of + # GN's java_library features, but hopefully we support the subset that + # matters to get things to work. + # + # The `java_library` GN rule generates not just one GN target, but a whole + # hierarchy of subtargets (`_java__compile_java`, `_java__dex`, etc.) + # behind a top-level `group` target. + # + # One approach could be to look at the various subtargets and piece + # together the information we need (i.e. the Java source file paths, the + # Java deps, the jar filtering rules, etc.). But that would require + # non-trivial business logic and runs the risk of breakage when changes + # are made to the internals of GN `java_library` rules (e.g. + # https://crbug.com/412984664). + # + # Another approach could be to closely replicate the subtarget structure + # in Soong (i.e. generate one module per subtarget), but that means we + # would basically end up generating genrules that indirectly call javac + # instead of generating `java_library` modules, which feels extremely + # awkward, impractical and unlikely to work. + # + # Instead, we rely entirely on GN target metadata that the `java_library` + # GN rule helpfully attaches to the top-level group target. This makes the + # analysis trivial and completely decouples this code from the internal + # structure of the `java_library` GN subtargets. + + inputs = metadata.get("java_library_inputs", []) + target.sources.update(input for input in inputs + if not input.startswith('//out/')) + target.inputs.update(_remove_out_prefix(input) for input in inputs) + + deps.clear() + deps.extend(metadata.get("java_library_deps", [])) + + target.java_jar_excluded_patterns = metadata.get( + "java_library_jar_excluded_patterns", []) + target.java_jar_included_patterns = metadata.get( + "java_library_jar_included_patterns", []) + + android_sdk_dep = metadata.get("java_library_android_sdk_dep", + None) + if android_sdk_dep is not None: + assert len(android_sdk_dep) == 1, target.name + android_sdk_dep = android_sdk_dep[0] + if android_sdk_dep == "//third_party/android_sdk:android_sdk_java": + target.sdk_version = "current" + elif android_sdk_dep == "//third_party/android_sdk:public_framework_system_java": + target.sdk_version = "system_current" + else: + raise ValueError( + f"Unexpected android_sdk_dep: {android_sdk_dep} for target {target.name}" + ) + elif desc.get("script", "") == "//build/rust/gni_impl/run_bindgen.py": + # rust_bindgen is a supported module in Soong but GN depend on actions + # so we need to copy the action fields (sources, outputs and args) in + # order to correctly generate the `rust_bindgen` module. + target.sources.update(desc.get('sources', [])) + outs = [_remove_out_prefix(x) for x in desc['outputs']] + target.outputs.update(outs) + target.args = desc['args'] + target.type = "rust_bindgen" + elif (target.type in [ + 'action', 'action_foreach' + # GN's copy is translated to Soong by making it look like a GN's action + # with a special //cp script. This works well for its only usage: + # //base:build_date_header. As the list of supported copy target grows, we might + # need to revisit this decision. + ]) or (desc['type'] == 'copy' and target.name in [ + '//base:build_date_header', '//base:build_date_header__testing' + ]): + target.arch[arch].inputs.update(desc.get('inputs', [])) + target.arch[arch].sources.update(desc.get('sources', [])) + outs = [_remove_out_prefix(x) for x in desc['outputs']] + target.arch[arch].outputs.update(outs) + # We need to check desc['type'], not target.type: targets go through + # this code multiple times. If we checked for target.type, the second + # time we parsed a copy target, we would take the else branch. + if desc['type'] == 'copy': + target.type = 'action' + target.script = '//cp' + else: + # While the arguments might differ, an action should always use the same script for every + # architecture. (gen_android_bp's get_action_sanitizer actually relies on this fact. + target.script = desc['script'] + target.arch[arch].args = desc['args'] + target.arch[ + arch].response_file_contents = self._get_response_file_contents( + desc) + # _get_jni_registration_deps will return the dependencies of a target if + # the target is of type `generate_jni_registration` otherwise it will + # return an empty set. + target.jni_registration_java_deps.update( + _get_jni_registration_deps(gn_target_name, gn_desc)) + # JNI java sources are embedded as metadata inside `jni_headers` targets. + # See https://source.chromium.org/chromium/chromium/src/+/main:third_party/jni_zero/jni_zero.gni;l=421;drc=78e8e27142ed3fddf04fbcd122507517a87cb9ad + # for more details + target.transitive_jni_java_sources.update( + metadata.get("jni_source_files", set())) + self.jni_java_sources.update( + metadata.get("jni_source_files", set())) + if gn2bp_common.is_rust_build_script(target.script): + + def _extract_crate_path(args): + return args[args.index("--src-dir") + 1].replace( + "../../", "") + + target.rust_source_dir = _extract_crate_path(desc['args']) + # Don't continue the dependencies exploration. + return target + elif target.type == 'group': + # Group targets are bubbled upward without creating an equivalent GN target. + pass + elif target.type == 'copy': + # Copy targets, except for a few exception (see handling of action + # targets above), are bubbled upward without creating an equivalent + # GN target. + pass + elif target.type in [ + "rust_library", "rust_proc_macro", "rust_executable" + ]: + target.arch[arch].sources.update( + source for source in desc.get('sources', []) + if not source.startswith("//out")) + target.rust_package_version = _extract_rust_package_version( + desc['rustenv']) + else: + raise Exception( + f"Encountered GN target with unknown type\nCulprit target: {gn_target_name}\ntype: {target.type}" + ) + + # Default for 'public' is //* - all headers in 'sources' are public. + # TODO(primiano): if a 'public' section is specified (even if empty), then + # the rest of 'sources' is considered inaccessible by gn. Consider + # emulating that, so that generated build files don't end up with overly + # accessible headers. + public_headers = [x for x in desc.get('public', []) if x != '*'] + target.public_headers.update(public_headers) + target.build_file_path = _get_build_path_from_label(target_name) + target.arch[arch].cflags.extend( + _filter_cflags(desc.get('cflags', []) + desc.get('cflags_cc', []))) + target.arch[arch].libs.update(desc.get('libs', [])) + target.arch[arch].ldflags.extend(desc.get('ldflags', [])) + target.arch[arch].defines.update( + _filter_defines(desc.get('defines', []))) + target.arch[arch].include_dirs.update(desc.get('include_dirs', [])) + target.output_name = desc.get('output_name', None) + target.crate_name = desc.get("crate_name", None) + target.crate_root = desc.get("crate_root", None) + target.arch[arch].rust_flags = desc.get("rustflags", list()) + target.arch[arch].rust_flags.extend( + self.build_script_outputs.get( + label_without_toolchain(gn_target_name), + {}).get(chromium_arch, list())) + + if "-frtti" in target.arch[arch].cflags: + target.rtti = True + + for gn_dep_name in set(target.jni_registration_java_deps): + dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_test_target) + target.transitive_jni_java_sources.update( + dep.transitive_jni_java_sources) + + if override_deps is not None: + deps = override_deps + + # Recurse in dependencies. + for gn_dep_name in sorted(set(deps) | set(build_only_deps)): + dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_test_target) + + if dep.type == 'proto_library': + target.proto_deps.add(dep.name) + elif dep.type == 'copy': + target.update(dep, arch) + elif dep.type == 'group': + target.update(dep, + arch) # Bubble up groups's cflags/ldflags etc. + target.transitive_jni_java_sources.update( + dep.transitive_jni_java_sources) + elif dep.type in ['action', 'action_foreach']: + target.arch[arch].deps.add(dep.name) + target.transitive_jni_java_sources.update( + dep.transitive_jni_java_sources) + elif dep.is_linker_unit_type(): + target.arch[arch].deps.add(dep.name) + elif dep.type == 'aidl_interface': + target.arch[arch].deps.add(dep.name) + elif dep.type == "rust_executable": + target.arch[arch].deps.add(dep.name) + elif dep.type == 'java_library': + if gn_dep_name in build_only_deps: + # Chromium builds Java code against the unfiltered dependencies + # (_java__header). This reproduces this behavior. + target.build_only_deps.add(dep.unfiltered_java_target.name) + else: + target.deps.add(dep.name) + target.transitive_jni_java_sources.update( + dep.transitive_jni_java_sources) + elif dep.type in [ + 'rust_binary', "rust_library", "rust_proc_macro", + "rust_bindgen" + ]: + target.arch[arch].deps.add(dep.name) + if dep.type in ['static_library', 'source_set', 'rust_library']: + # Bubble up static_libs and source_set. Necessary, since soong does not propagate + # static_libs up the build tree. + # Source sets are later translated to static_libraries, so it makes sense + # to reuse transitive_static_libs_deps. + target.arch[arch].transitive_static_libs_deps.add(dep.name) + + # rust_proc_macro must never propagate their dependency upward the tree. proc_macros are only used + # during compilations on host as they allow extending the compiler with custom macros, their dependency should + # be used to build the proc macro, then abandoned. Propagating it upward means that we'll be linking + # against code that is effectively dead, and can cause issues. + # Don't bubble up transitive dependencies of executables as they've already been linked. + # A dependency on the executable means that the output of the target (the executable) should + # be used, rather than its dependency. + if arch in dep.arch and dep.type not in [ + 'rust_proc_macro', 'rust_executable', 'executable' + ]: + target.arch[arch].transitive_static_libs_deps.update( + dep.arch[arch].transitive_static_libs_deps) + target.arch[arch].deps.update( + target.arch[arch].transitive_static_libs_deps) + return target - elif target.type == 'group': - # Group targets are bubbled upward without creating an equivalent GN target. - pass - elif target.type == 'copy': - # Copy targets, except for a few exception (see handling of action - # targets above), are bubbled upward without creating an equivalent - # GN target. - pass - elif target.type in ["rust_library", "rust_proc_macro", "rust_executable"]: - target.arch[arch].sources.update(source - for source in desc.get('sources', []) - if not source.startswith("//out")) - target.rust_package_version = _extract_rust_package_version( - desc['rustenv']) - else: - raise Exception( - f"Encountered GN target with unknown type\nCulprit target: {gn_target_name}\ntype: {target.type}" - ) - # Default for 'public' is //* - all headers in 'sources' are public. - # TODO(primiano): if a 'public' section is specified (even if empty), then - # the rest of 'sources' is considered inaccessible by gn. Consider - # emulating that, so that generated build files don't end up with overly - # accessible headers. - public_headers = [x for x in desc.get('public', []) if x != '*'] - target.public_headers.update(public_headers) - target.build_file_path = _get_build_path_from_label(target_name) - target.arch[arch].cflags.extend( - _filter_cflags(desc.get('cflags', []) + desc.get('cflags_cc', []))) - target.arch[arch].libs.update(desc.get('libs', [])) - target.arch[arch].ldflags.extend(desc.get('ldflags', [])) - target.arch[arch].defines.update(_filter_defines(desc.get('defines', []))) - target.arch[arch].include_dirs.update(desc.get('include_dirs', [])) - target.output_name = desc.get('output_name', None) - target.crate_name = desc.get("crate_name", None) - target.crate_root = desc.get("crate_root", None) - target.arch[arch].rust_flags = desc.get("rustflags", list()) - target.arch[arch].rust_flags.extend( - self.build_script_outputs.get(label_without_toolchain(gn_target_name), - {}).get(chromium_arch, list())) + def get_proto_exports(self, proto_desc): + # exports in metadata will be available for source_set targets. + metadata = proto_desc.get('metadata', {}) + return metadata.get('exports', []) - if "-frtti" in target.arch[arch].cflags: - target.rtti = True + def get_proto_paths(self, proto_desc): + args = proto_desc.get('args') + proto_paths = set() + for arg in args: + is_match = re.match(PROTO_IMPORT_DIRS_REGEX, arg) + if is_match: + proto_paths.add(re.sub('^\.\./\.\./', '', is_match.group(1))) + return proto_paths - for gn_dep_name in set(target.jni_registration_java_deps): - dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_test_target) - target.transitive_jni_java_sources.update(dep.transitive_jni_java_sources) - - if override_deps is not None: - deps = override_deps - - # Recurse in dependencies. - for gn_dep_name in sorted(set(deps) | set(build_only_deps)): - dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_test_target) - - if dep.type == 'proto_library': - target.proto_deps.add(dep.name) - elif dep.type == 'copy': - target.update(dep, arch) - elif dep.type == 'group': - target.update(dep, arch) # Bubble up groups's cflags/ldflags etc. - target.transitive_jni_java_sources.update( - dep.transitive_jni_java_sources) - elif dep.type in ['action', 'action_foreach']: - target.arch[arch].deps.add(dep.name) - target.transitive_jni_java_sources.update( - dep.transitive_jni_java_sources) - elif dep.is_linker_unit_type(): - target.arch[arch].deps.add(dep.name) - elif dep.type == 'aidl_interface': - target.arch[arch].deps.add(dep.name) - elif dep.type == "rust_executable": - target.arch[arch].deps.add(dep.name) - elif dep.type == 'java_library': - if gn_dep_name in build_only_deps: - # Chromium builds Java code against the unfiltered dependencies - # (_java__header). This reproduces this behavior. - target.build_only_deps.add(dep.unfiltered_java_target.name) - else: - target.deps.add(dep.name) - target.transitive_jni_java_sources.update( - dep.transitive_jni_java_sources) - elif dep.type in [ - 'rust_binary', "rust_library", "rust_proc_macro", "rust_bindgen" - ]: - target.arch[arch].deps.add(dep.name) - if dep.type in ['static_library', 'source_set', 'rust_library']: - # Bubble up static_libs and source_set. Necessary, since soong does not propagate - # static_libs up the build tree. - # Source sets are later translated to static_libraries, so it makes sense - # to reuse transitive_static_libs_deps. - target.arch[arch].transitive_static_libs_deps.add(dep.name) - - # rust_proc_macro must never propagate their dependency upward the tree. proc_macros are only used - # during compilations on host as they allow extending the compiler with custom macros, their dependency should - # be used to build the proc macro, then abandoned. Propagating it upward means that we'll be linking - # against code that is effectively dead, and can cause issues. - # Don't bubble up transitive dependencies of executables as they've already been linked. - # A dependency on the executable means that the output of the target (the executable) should - # be used, rather than its dependency. - if arch in dep.arch and dep.type not in [ - 'rust_proc_macro', 'rust_executable', 'executable' - ]: - target.arch[arch].transitive_static_libs_deps.update( - dep.arch[arch].transitive_static_libs_deps) - target.arch[arch].deps.update( - target.arch[arch].transitive_static_libs_deps) - - return target - - def get_proto_exports(self, proto_desc): - # exports in metadata will be available for source_set targets. - metadata = proto_desc.get('metadata', {}) - return metadata.get('exports', []) - - def get_proto_paths(self, proto_desc): - args = proto_desc.get('args') - proto_paths = set() - for arg in args: - is_match = re.match(PROTO_IMPORT_DIRS_REGEX, arg) - if is_match: - proto_paths.add(re.sub('^\.\./\.\./', '', is_match.group(1))) - return proto_paths - - def get_proto_in_dir(self, proto_desc): - args = proto_desc.get('args') - return re.sub('^\.\./\.\./', '', args[args.index('--proto-in-dir') + 1]) + def get_proto_in_dir(self, proto_desc): + args = proto_desc.get('args') + return re.sub('^\.\./\.\./', '', + args[args.index('--proto-in-dir') + 1])
diff --git a/components/cronet/gn2bp/run_gn2bp.py b/components/cronet/gn2bp/run_gn2bp.py index 77593b3..4e5238b 100755 --- a/components/cronet/gn2bp/run_gn2bp.py +++ b/components/cronet/gn2bp/run_gn2bp.py
@@ -47,7 +47,8 @@ _COPYBARA_PATH = os.path.join(REPOSITORY_ROOT, 'tools/copybara/copybara/copybara_deploy.jar') _GENERATE_BUILD_SCRIPT_PATH = os.path.join( - REPOSITORY_ROOT, 'components/cronet/gn2bp/generate_build_scripts_output.py') + REPOSITORY_ROOT, + 'components/cronet/gn2bp/generate_build_scripts_output.py') _GENERATE_LICENSE_SCRIPT_PATH = os.path.join( REPOSITORY_ROOT, 'components/cronet/license/create_android_metadata_license.py') @@ -63,194 +64,205 @@ # months will be collected and checked against the breakages.json _MONTHS_OF_CHANGELIST = 6 + class _OptionalExit(contextlib.AbstractContextManager): - """A context manager wrapper that optionally skips the exit phase of its + """A context manager wrapper that optionally skips the exit phase of its inner context manager.""" - _inner_context_manager: contextlib.AbstractContextManager - _exit: bool + _inner_context_manager: contextlib.AbstractContextManager + _exit: bool - def __init__(self, inner_context_manager: contextlib.AbstractContextManager, - do_exit: bool): - self._inner_context_manager = inner_context_manager - self._exit = do_exit + def __init__(self, + inner_context_manager: contextlib.AbstractContextManager, + do_exit: bool): + self._inner_context_manager = inner_context_manager + self._exit = do_exit - def __enter__(self): - return self._inner_context_manager.__enter__() + def __enter__(self): + return self._inner_context_manager.__enter__() - def __exit__(self, exc_type, exc_val, exc_tb): - if self._exit: - return self._inner_context_manager.__exit__(exc_type, exc_val, exc_tb) - return None + def __exit__(self, exc_type, exc_val, exc_tb): + if self._exit: + return self._inner_context_manager.__exit__( + exc_type, exc_val, exc_tb) + return None def _get_version_string() -> str: - version = '' - chrome_version_file_path = os.path.join(REPOSITORY_ROOT, 'chrome', 'VERSION') - for version_component in cronet_utils.read_file( - chrome_version_file_path).split('\n'): - if not version_component: - # Ignore empty lines - continue - if version: - # Only subsequent version components should be split by dots - version += '.' - version += version_component.split('=')[1] - return version + version = '' + chrome_version_file_path = os.path.join(REPOSITORY_ROOT, 'chrome', + 'VERSION') + for version_component in cronet_utils.read_file( + chrome_version_file_path).split('\n'): + if not version_component: + # Ignore empty lines + continue + if version: + # Only subsequent version components should be split by dots + version += '.' + version += version_component.split('=')[1] + return version def _run_license_generation(): - cronet_utils.run(["python3", _GENERATE_LICENSE_SCRIPT_PATH]) + cronet_utils.run(["python3", _GENERATE_LICENSE_SCRIPT_PATH]) def _is_trybot(): - return os.environ.get('SWARMING_BOT_ID', '').startswith('luci-chrome-try-') + return os.environ.get('SWARMING_BOT_ID', '').startswith('luci-chrome-try-') + def _run_gn2bp(desc_files: Set[tempfile.NamedTemporaryFile], skip_build_scripts: bool, delete_temporary_files: bool, channel: str) -> int: - """Run gen_android_bp.py to generate Android.bp.gn2bp files.""" - with tempfile.NamedTemporaryFile( - mode='w+', encoding='utf-8', - delete=delete_temporary_files) as build_script_output: + """Run gen_android_bp.py to generate Android.bp.gn2bp files.""" + with tempfile.NamedTemporaryFile( + mode='w+', encoding='utf-8', + delete=delete_temporary_files) as build_script_output: - if skip_build_scripts: - pathlib.Path(build_script_output.name).write_text('{}') - else: - _run_generate_build_scripts(build_script_output.name) + if skip_build_scripts: + pathlib.Path(build_script_output.name).write_text('{}') + else: + _run_generate_build_scripts(build_script_output.name) - base_cmd = [ - sys.executable, _GN2BP_SCRIPT_PATH, '--repo_root', REPOSITORY_ROOT, - '--build_script_output', build_script_output.name - ] - for desc_file in desc_files: - # desc_file.name represents the absolute path. - base_cmd += ['--desc', desc_file.name] + base_cmd = [ + sys.executable, _GN2BP_SCRIPT_PATH, '--repo_root', REPOSITORY_ROOT, + '--build_script_output', build_script_output.name + ] + for desc_file in desc_files: + # desc_file.name represents the absolute path. + base_cmd += ['--desc', desc_file.name] - base_cmd += ["--license"] - base_cmd += ["--channel", channel] - cronet_utils.run(base_cmd) + base_cmd += ["--license"] + base_cmd += ["--channel", channel] + cronet_utils.run(base_cmd) def _run_generate_build_scripts(output_path: str): - """Run generate_build_scripts_output.py. + """Run generate_build_scripts_output.py. Args: output_path: Path of the file that will contain the output. """ - cronet_utils.run([ - sys.executable, - _GENERATE_BUILD_SCRIPT_PATH, - '--output', - output_path, - ]) + cronet_utils.run([ + sys.executable, + _GENERATE_BUILD_SCRIPT_PATH, + '--output', + output_path, + ]) def _write_desc_json(gn_out_dir: str, temp_file: tempfile.NamedTemporaryFile): - """Generate desc json files needed by gen_android_bp.py.""" - cronet_utils.run( - [cronet_utils.GN_PATH, 'desc', gn_out_dir, '--format=json', '//*'], - stdout=temp_file) + """Generate desc json files needed by gen_android_bp.py.""" + cronet_utils.run( + [cronet_utils.GN_PATH, 'desc', gn_out_dir, '--format=json', '//*'], + stdout=temp_file) def _gen_extras_bp(import_channel: str): - """Generate Android.extras.bp.""" - extras_androidbp_template_path = os.path.join(REPOSITORY_ROOT, 'components', - 'cronet', 'gn2bp', 'templates', - 'Android.extras.bp.template') - extras_androidbp_template_contents = cronet_utils.read_file( - extras_androidbp_template_path) - extras_androidbp_path = os.path.join(REPOSITORY_ROOT, - 'Android.extras.bp.gn2bp') - cronet_utils.write_file( - extras_androidbp_path, - string.Template(extras_androidbp_template_contents).substitute( - GN2BP_MODULE_PREFIX=f'{import_channel}_cronet_')) + """Generate Android.extras.bp.""" + extras_androidbp_template_path = os.path.join( + REPOSITORY_ROOT, 'components', 'cronet', 'gn2bp', 'templates', + 'Android.extras.bp.template') + extras_androidbp_template_contents = cronet_utils.read_file( + extras_androidbp_template_path) + extras_androidbp_path = os.path.join(REPOSITORY_ROOT, + 'Android.extras.bp.gn2bp') + cronet_utils.write_file( + extras_androidbp_path, + string.Template(extras_androidbp_template_contents).substitute( + GN2BP_MODULE_PREFIX=f'{import_channel}_cronet_')) def _gen_androidtest_xml(import_channel: str): - """Generate AndroidTest.xml, required to run test in Android.""" - module_prefix = f'{import_channel}_cronet_' - androidtest_xml_template_path = os.path.join(REPOSITORY_ROOT, 'components', - 'cronet', 'gn2bp', 'templates', - 'AndroidTest.xml.template') - androidtest_xml_template_contents = cronet_utils.read_file( - androidtest_xml_template_path) - androidtest_xml_path = os.path.join(REPOSITORY_ROOT, 'AndroidTest.xml') - cronet_utils.write_file( - androidtest_xml_path, - string.Template(androidtest_xml_template_contents).substitute( - GN2BP_MODULE_PREFIX=module_prefix)) + """Generate AndroidTest.xml, required to run test in Android.""" + module_prefix = f'{import_channel}_cronet_' + androidtest_xml_template_path = os.path.join(REPOSITORY_ROOT, 'components', + 'cronet', 'gn2bp', + 'templates', + 'AndroidTest.xml.template') + androidtest_xml_template_contents = cronet_utils.read_file( + androidtest_xml_template_path) + androidtest_xml_path = os.path.join(REPOSITORY_ROOT, 'AndroidTest.xml') + cronet_utils.write_file( + androidtest_xml_path, + string.Template(androidtest_xml_template_contents).substitute( + GN2BP_MODULE_PREFIX=module_prefix)) + def _gen_boringssl(import_channel: str): - """Generate boringssl Android build files.""" - module_prefix = f'{import_channel}_cronet_' - boringssl_androidbp_template_path = os.path.join( - REPOSITORY_ROOT, 'components', 'cronet', 'gn2bp', 'templates', - 'boringssl_Android.bp.template') - boringssl_androidbp_template_contents = cronet_utils.read_file( - boringssl_androidbp_template_path) - boringssl_androidbp_path = os.path.join(_BORINGSSL_PATH, 'Android.bp.gn2bp') - cronet_utils.write_file( - boringssl_androidbp_path, - string.Template(boringssl_androidbp_template_contents).substitute( - GN2BP_IMPORT_CHANNEL=import_channel, - GN2BP_MODULE_PREFIX=module_prefix)) - cmd = f'cd {_BORINGSSL_PATH} && python3 {_BORINGSSL_SCRIPT} --target-prefix={module_prefix} android' - cronet_utils.run(cmd, shell=True) + """Generate boringssl Android build files.""" + module_prefix = f'{import_channel}_cronet_' + boringssl_androidbp_template_path = os.path.join( + REPOSITORY_ROOT, 'components', 'cronet', 'gn2bp', 'templates', + 'boringssl_Android.bp.template') + boringssl_androidbp_template_contents = cronet_utils.read_file( + boringssl_androidbp_template_path) + boringssl_androidbp_path = os.path.join(_BORINGSSL_PATH, + 'Android.bp.gn2bp') + cronet_utils.write_file( + boringssl_androidbp_path, + string.Template(boringssl_androidbp_template_contents).substitute( + GN2BP_IMPORT_CHANNEL=import_channel, + GN2BP_MODULE_PREFIX=module_prefix)) + cmd = f'cd {_BORINGSSL_PATH} && python3 {_BORINGSSL_SCRIPT} --target-prefix={module_prefix} android' + cronet_utils.run(cmd, shell=True) def _wait_and_fail_if_not_presubmit_verified(change_id: str): - while True: - with tempfile.NamedTemporaryFile(mode="w+", encoding='utf-8', - delete=True) as gerrit_change_labels_file: - cronet_utils.run([ - _GERRIT_CLIENT_PATH, 'changes', - '--host=https://googleplex-android-review.googlesource.com', - '--project=platform/external/cronet', f'--query={change_id}', '-o', - 'LABELS', f'--json={gerrit_change_labels_file.name}' - ]) - cronet_change_labels = json.loads( - cronet_utils.read_file(gerrit_change_labels_file.name)) - presubmit_verified_entries = cronet_change_labels[0]['labels'][ - 'Presubmit-Verified'] - for key in presubmit_verified_entries: - if key in ('rejected', 'disliked'): - raise RuntimeError( - 'Presubmit failed, check the Android CL for more info') - if key in ('approved', 'recommended'): - return - print( - f'Still waiting for Presubmit-Verified: {presubmit_verified_entries}') - time.sleep(60 * 5) # 5 mins + while True: + with tempfile.NamedTemporaryFile( + mode="w+", encoding='utf-8', + delete=True) as gerrit_change_labels_file: + cronet_utils.run([ + _GERRIT_CLIENT_PATH, 'changes', + '--host=https://googleplex-android-review.googlesource.com', + '--project=platform/external/cronet', f'--query={change_id}', + '-o', 'LABELS', f'--json={gerrit_change_labels_file.name}' + ]) + cronet_change_labels = json.loads( + cronet_utils.read_file(gerrit_change_labels_file.name)) + presubmit_verified_entries = cronet_change_labels[0]['labels'][ + 'Presubmit-Verified'] + for key in presubmit_verified_entries: + if key in ('rejected', 'disliked'): + raise RuntimeError( + 'Presubmit failed, check the Android CL for more info') + if key in ('approved', 'recommended'): + return + print( + f'Still waiting for Presubmit-Verified: {presubmit_verified_entries}' + ) + time.sleep(60 * 5) # 5 mins def _is_bot_environment(): - return os.environ.get('SWARMING_BOT_ID', None) is not None + return os.environ.get('SWARMING_BOT_ID', None) is not None def _is_ci_bot(): - return os.environ.get('SWARMING_BOT_ID', - '').startswith('luci-chrome-trusted-') + return os.environ.get('SWARMING_BOT_ID', + '').startswith('luci-chrome-trusted-') + def _run_copybara_to_aosp(config: str, copybara_binary: str, git_url_and_branch: Optional[Tuple[str, str]], regenerate_consistency_file: bool, import_channel: str, wait_for_presubmit_verified: bool): - """Run Copybara CLI to generate an AOSP Gerrit CL with the generated files. + """Run Copybara CLI to generate an AOSP Gerrit CL with the generated files. Get the commit hash of AOSP `external/cronet` tip of tree to merge into. It will print the generated Gerrit url to stdout. """ - msg = f'gn2bp{time.time_ns()}' - change_id = f'I{hashlib.sha1(msg.encode()).hexdigest()}' - print(f'Generated {change_id=}') + msg = f'gn2bp{time.time_ns()}' + change_id = f'I{hashlib.sha1(msg.encode()).hexdigest()}' + print(f'Generated {change_id=}') - version = _get_version_string() - commit_hash = cronet_utils.run_and_get_stdout(['git', 'rev-parse', 'HEAD']) - commit_date = cronet_utils.run_and_get_stdout( - ['git', 'show', '--pretty=format:%ci', '--no-patch']) - swarming_task_id = os.environ.get('SWARMING_TASK_ID') - commit_message = textwrap.dedent(f"""\ + version = _get_version_string() + commit_hash = cronet_utils.run_and_get_stdout(['git', 'rev-parse', 'HEAD']) + commit_date = cronet_utils.run_and_get_stdout( + ['git', 'show', '--pretty=format:%ci', '--no-patch']) + swarming_task_id = os.environ.get('SWARMING_TASK_ID') + commit_message = textwrap.dedent(f"""\ Import Cronet {commit_hash[:8]} ({version}) into {import_channel} Chromium commit hash: {commit_hash} @@ -258,31 +270,31 @@ Chromium version: {version} """) - if not _is_ci_bot(): - # This is not ideal, but we don't have a better signal to tell if gn2bp is - # running in CI or somewhere else. - # - # Chromium CQ checks for this string in code, so this must be split to land - # the change. - prefix = 'DO NOT ' + 'SUBMIT' - commit_message += textwrap.dedent(f"""\ + if not _is_ci_bot(): + # This is not ideal, but we don't have a better signal to tell if gn2bp is + # running in CI or somewhere else. + # + # Chromium CQ checks for this string in code, so this must be split to land + # the change. + prefix = 'DO NOT ' + 'SUBMIT' + commit_message += textwrap.dedent(f"""\ {prefix}: This import was not generated by Chromium's CI, as such it might contain unreviewed changes on top of the aforementioned commit. """) - if import_channel == 'stable' and not _is_currently_on_latest_stable(): - prefix = 'DO NOT ' + 'SUBMIT' - commit_message += textwrap.dedent(f"""\ + if import_channel == 'stable' and not _is_currently_on_latest_stable(): + prefix = 'DO NOT ' + 'SUBMIT' + commit_message += textwrap.dedent(f"""\ {prefix}: This import targets the stable channel but was generated from a Chromium branch that is not stable. """) - if swarming_task_id: - commit_message += textwrap.dedent(f"""\ + if swarming_task_id: + commit_message += textwrap.dedent(f"""\ This CL was autogenerated by the following Chromium bot run: https://luci-milo.appspot.com/swarming/task/{swarming_task_id}?server=chrome-swarming.appspot.com """) - commit_message += textwrap.dedent(f"""\ + commit_message += textwrap.dedent(f"""\ This CL can be reproduced by running the following command: gclient config --spec 'solutions = [ {{ @@ -304,129 +316,129 @@ NO_IFTTT=Imported from Chromium. """) - additional_parameters = [ - '--ignore-noop', - '--force-message', - commit_message, - ] + additional_parameters = [ + '--ignore-noop', + '--force-message', + commit_message, + ] - target_workflow = None - after_upload_comment = None - if git_url_and_branch: - target_workflow = f'{import_channel}_import_cronet_to_git_branch' - additional_parameters.extend([ - '--git-destination-url', - git_url_and_branch[0], - '--git-destination-push', - git_url_and_branch[1], - ]) - else: - target_workflow = f'{import_channel}_import_cronet_to_aosp_gerrit' - if import_channel == 'stable' and not _is_currently_on_latest_stable(): - # If we're importing to stable and the current branch is not a stable - # branch, then don't auto-submit. - after_upload_comment = 'Importing to the stable channel from a non-stable Chromium branch. The workflow will be set to non-autosubmittable. This is an unresolved comment to prevent accidental auto-submit' - target_workflow += '_no_autosubmit' + target_workflow = None + after_upload_comment = None + if git_url_and_branch: + target_workflow = f'{import_channel}_import_cronet_to_git_branch' + additional_parameters.extend([ + '--git-destination-url', + git_url_and_branch[0], + '--git-destination-push', + git_url_and_branch[1], + ]) else: - target_workflow += '_autosubmit' + target_workflow = f'{import_channel}_import_cronet_to_aosp_gerrit' + if import_channel == 'stable' and not _is_currently_on_latest_stable(): + # If we're importing to stable and the current branch is not a stable + # branch, then don't auto-submit. + after_upload_comment = 'Importing to the stable channel from a non-stable Chromium branch. The workflow will be set to non-autosubmittable. This is an unresolved comment to prevent accidental auto-submit' + target_workflow += '_no_autosubmit' + else: + target_workflow += '_autosubmit' - additional_parameters.extend([ - '--git-push-option', - 'nokeycheck', - '--git-push-option', - 'uploadvalidator~skip', - '--gerrit-change-id', - change_id, - ]) - if regenerate_consistency_file: - # We can't use the copybara `regenerate` subcommand because it doesn't - # support folder origins. See https://crbug.com/391331930. - additional_parameters.extend([ - '--disable-consistency-merge-import', - 'true', - '--baseline-for-merge-import', - REPOSITORY_ROOT, - ]) + additional_parameters.extend([ + '--git-push-option', + 'nokeycheck', + '--git-push-option', + 'uploadvalidator~skip', + '--gerrit-change-id', + change_id, + ]) + if regenerate_consistency_file: + # We can't use the copybara `regenerate` subcommand because it doesn't + # support folder origins. See https://crbug.com/391331930. + additional_parameters.extend([ + '--disable-consistency-merge-import', + 'true', + '--baseline-for-merge-import', + REPOSITORY_ROOT, + ]) - cronet_utils.run([ - _JAVA_PATH, '-jar', copybara_binary, config, target_workflow, - REPOSITORY_ROOT - ] + additional_parameters) - - if after_upload_comment and not git_url_and_branch: cronet_utils.run([ - _GERRIT_CLIENT_PATH, 'addpatchsetcomment', - '--host=https://googleplex-android-review.googlesource.com', - f'--change={change_id}', f'--message={after_upload_comment}', - '--unresolved' - ]) + _JAVA_PATH, '-jar', copybara_binary, config, target_workflow, + REPOSITORY_ROOT + ] + additional_parameters) - if wait_for_presubmit_verified and not git_url_and_branch: - _wait_and_fail_if_not_presubmit_verified(change_id) + if after_upload_comment and not git_url_and_branch: + cronet_utils.run([ + _GERRIT_CLIENT_PATH, 'addpatchsetcomment', + '--host=https://googleplex-android-review.googlesource.com', + f'--change={change_id}', f'--message={after_upload_comment}', + '--unresolved' + ]) + + if wait_for_presubmit_verified and not git_url_and_branch: + _wait_and_fail_if_not_presubmit_verified(change_id) def _fill_desc_file_for_arch(arch, desc_file, delete_temporary_files): - # gn desc behaves completely differently when the output - # directory is outside of chromium/src, some paths will - # stop having // in the beginning of their labels - # eg (//A/B will become A/B), this mostly apply to files - # that are generated through actions and not targets. - # - # This is why the temporary directory has to be generated - # beneath the repository root until gn2bp is tweaked to - # deal with this small differences. - with _OptionalExit(tempfile.TemporaryDirectory(dir=_OUT_DIR), - do_exit=delete_temporary_files) as gn_out_dir: - cronet_utils.gn(gn_out_dir, - ' '.join(cronet_utils.get_gn_args_for_aosp(arch))) - _write_desc_json(gn_out_dir, desc_file) + # gn desc behaves completely differently when the output + # directory is outside of chromium/src, some paths will + # stop having // in the beginning of their labels + # eg (//A/B will become A/B), this mostly apply to files + # that are generated through actions and not targets. + # + # This is why the temporary directory has to be generated + # beneath the repository root until gn2bp is tweaked to + # deal with this small differences. + with _OptionalExit(tempfile.TemporaryDirectory(dir=_OUT_DIR), + do_exit=delete_temporary_files) as gn_out_dir: + cronet_utils.gn(gn_out_dir, + ' '.join(cronet_utils.get_gn_args_for_aosp(arch))) + _write_desc_json(gn_out_dir, desc_file) # TODO(crbug.com/481701970): Create an abstraction for versions and move this there. def compare_versions(version1: str, version2: str) -> int: - return int(version1.split('.')[2]) - int(version2.split('.')[2]) + return int(version1.split('.')[2]) - int(version2.split('.')[2]) # TODO(crbug.com/481701970): Create an abstraction for versions and move this there. def sort_versions(versions: List[str]) -> List[str]: - return sorted(versions, key=cmp_to_key(compare_versions)) + return sorted(versions, key=cmp_to_key(compare_versions)) def _is_currently_on_latest_stable(): - """Verifies that the current checkout is on the latest stable milestone.""" - print('Fetching latest stable version from chromiumdash...') - with urllib.request.urlopen( - # Chromiumdash lists releases by date. Because of LTS backports, an older - # milestone is often released more recently than the newest major version. - # We fetch a large batch (e.g., 50) and select the highest branch number - # to ensure we identify the actual latest stable branch. - 'https://chromiumdash.appspot.com/fetch_releases?num=50&platform=Android&channel=Stable' - ) as url: - data = json.loads(url.read().decode()) - current_version = _get_version_string() - print(f'Current checkout version is {current_version}') - latest_stable = sort_versions( - [release_json['version'] for release_json in data])[-1] - print(f'Latest stable version is {latest_stable}') + """Verifies that the current checkout is on the latest stable milestone.""" + print('Fetching latest stable version from chromiumdash...') + with urllib.request.urlopen( + # Chromiumdash lists releases by date. Because of LTS backports, an older + # milestone is often released more recently than the newest major version. + # We fetch a large batch (e.g., 50) and select the highest branch number + # to ensure we identify the actual latest stable branch. + 'https://chromiumdash.appspot.com/fetch_releases?num=50&platform=Android&channel=Stable' + ) as url: + data = json.loads(url.read().decode()) + current_version = _get_version_string() + print(f'Current checkout version is {current_version}') + latest_stable = sort_versions( + [release_json['version'] for release_json in data])[-1] + print(f'Latest stable version is {latest_stable}') - # Version is major.minor.build.patch - latest_build = latest_stable.split('.')[2] - current_build = current_version.split('.')[2] + # Version is major.minor.build.patch + latest_build = latest_stable.split('.')[2] + current_build = current_version.split('.')[2] - if latest_build != current_build: - # There are two main approaches: either exit cleanly (indicating success - # without an import) or exit with an error. Exiting cleanly might falsely - # suggest a successful import, while exiting with an error could obscure - # genuine pipeline failures. We opt for the safer approach of exiting - # with an error until this logic is integrated into a LUCI recipe. - print(f'Note: The current branch ({current_build}) is not on the ' - f'latest stable milestone branch ({latest_build}).') - return False - return True + if latest_build != current_build: + # There are two main approaches: either exit cleanly (indicating success + # without an import) or exit with an error. Exiting cleanly might falsely + # suggest a successful import, while exiting with an error could obscure + # genuine pipeline failures. We opt for the safer approach of exiting + # with an error until this logic is integrated into a LUCI recipe. + print(f'Note: The current branch ({current_build}) is not on the ' + f'latest stable milestone branch ({latest_build}).') + return False + return True def _get_chromium_last_change() -> str: - """Returns the LASTCHANGE string from build/util/LASTCHANGE. + """Returns the LASTCHANGE string from build/util/LASTCHANGE. The LASTCHANGE file is generated by `gclient sync`. It reflects the `Cr-Commit-Position` of the last commit, which indicates the branch where @@ -434,251 +446,260 @@ the last commit consistently includes this field. However, it may not work correctly for local checkouts with cherry-picked commits. """ - lastchange_path = os.path.join(REPOSITORY_ROOT, 'build', 'util', 'LASTCHANGE') - if not os.path.exists(lastchange_path): - raise FileNotFoundError(f'Could not find {lastchange_path}') + lastchange_path = os.path.join(REPOSITORY_ROOT, 'build', 'util', + 'LASTCHANGE') + if not os.path.exists(lastchange_path): + raise FileNotFoundError(f'Could not find {lastchange_path}') - for line in cronet_utils.read_file(lastchange_path).splitlines(): - if line.startswith('LASTCHANGE='): - return line.split('=')[1] - raise ValueError(f'Could not find LASTCHANGE in {lastchange_path}') + for line in cronet_utils.read_file(lastchange_path).splitlines(): + if line.startswith('LASTCHANGE='): + return line.split('=')[1] + raise ValueError(f'Could not find LASTCHANGE in {lastchange_path}') def _fetch_breakages() -> list[dict[str, str]]: - print(f"Fetching breakages.json from {_BREAKAGES_FILE_URL}") - with urllib.request.urlopen(_BREAKAGES_FILE_URL) as url: - return json.loads(base64.b64decode(url.read().decode()))["breakages"] - raise ValueError("Failed to fetch breakages") + print(f"Fetching breakages.json from {_BREAKAGES_FILE_URL}") + with urllib.request.urlopen(_BREAKAGES_FILE_URL) as url: + return json.loads(base64.b64decode(url.read().decode()))["breakages"] + raise ValueError("Failed to fetch breakages") def _get_change_ids_from_head(months: int) -> dict[str, int]: - """Returns a dictionary of Change-ID to index for commits since the last {months}.""" - # Run git log with the specific trailer format - cmd = [ - 'git', 'log', f'--since={months} months ago', - '--format=%(trailers:key=Change-Id,valueonly)' - ] - output = cronet_utils.run_and_get_stdout(cmd) - change_ids = [line.strip() for line in output.splitlines() if line.strip()] - change_ids_dictionary = {} - for i, change_id in enumerate(change_ids): - change_ids_dictionary[change_id] = i - return change_ids_dictionary + """Returns a dictionary of Change-ID to index for commits since the last {months}.""" + # Run git log with the specific trailer format + cmd = [ + 'git', 'log', f'--since={months} months ago', + '--format=%(trailers:key=Change-Id,valueonly)' + ] + output = cronet_utils.run_and_get_stdout(cmd) + change_ids = [line.strip() for line in output.splitlines() if line.strip()] + change_ids_dictionary = {} + for i, change_id in enumerate(change_ids): + change_ids_dictionary[change_id] = i + return change_ids_dictionary def validate_release(breakages: list[dict[str, any]], changelist: dict[str, int]) -> None: - print("Validating the current release against breakages.json") - for breakage in breakages: - bad_change_id = breakage.get(breakages_constants.BAD_CHANGE_ID_TXT) + print("Validating the current release against breakages.json") + for breakage in breakages: + bad_change_id = breakage.get(breakages_constants.BAD_CHANGE_ID_TXT) - good_change_ids = breakage.get(breakages_constants.GOOD_CHANGE_IDS_TXT, []) - if not isinstance(good_change_ids, list): - raise ValueError( - f'The type of `{breakages_constants.GOOD_CHANGE_IDS_TXT}` must be a list. {breakage=}' - ) + good_change_ids = breakage.get(breakages_constants.GOOD_CHANGE_IDS_TXT, + []) + if not isinstance(good_change_ids, list): + raise ValueError( + f'The type of `{breakages_constants.GOOD_CHANGE_IDS_TXT}` must be a list. {breakage=}' + ) - if not good_change_ids: - raise RuntimeError( - f'Stopping the import: there is a breakage that has not been fixed yet. {breakage=}' - ) + if not good_change_ids: + raise RuntimeError( + f'Stopping the import: there is a breakage that has not been fixed yet. {breakage=}' + ) - if bad_change_id not in changelist: - continue + if bad_change_id not in changelist: + continue - good_change_ids_in_history = [ - good_change_id for good_change_id in good_change_ids - if good_change_id in changelist - ] - if not good_change_ids_in_history: - raise RuntimeError( - f'Stopping the import: the current checkout includes a breaking change, but not its fix. {breakage=}' - ) + good_change_ids_in_history = [ + good_change_id for good_change_id in good_change_ids + if good_change_id in changelist + ] + if not good_change_ids_in_history: + raise RuntimeError( + f'Stopping the import: the current checkout includes a breaking change, but not its fix. {breakage=}' + ) - if len(good_change_ids_in_history) >= 2: - raise RuntimeError( - 'Stopping the import: there might be a problem with the local checkout, multiple ' - 'good change IDs, for the same breakage, have been found in the history. Multiple ' - 'good change IDs are only necessary when a fix has to be cherry-picked into a release ' - 'branch, where it might end up with a different change ID than the original fix. ' - f'{breakage=}') - good_change_id_index = changelist[good_change_ids_in_history[0]] - if good_change_id_index >= changelist[bad_change_id]: - raise RuntimeError( - f'Stopping the import: there might be a problem with the local checkout, ' - f'the local history shows a bad change ID that is more recent than its fix. ' - f'{breakage=}') + if len(good_change_ids_in_history) >= 2: + raise RuntimeError( + 'Stopping the import: there might be a problem with the local checkout, multiple ' + 'good change IDs, for the same breakage, have been found in the history. Multiple ' + 'good change IDs are only necessary when a fix has to be cherry-picked into a release ' + 'branch, where it might end up with a different change ID than the original fix. ' + f'{breakage=}') + good_change_id_index = changelist[good_change_ids_in_history[0]] + if good_change_id_index >= changelist[bad_change_id]: + raise RuntimeError( + f'Stopping the import: there might be a problem with the local checkout, ' + f'the local history shows a bad change ID that is more recent than its fix. ' + f'{breakage=}') def _pick_target_channel_for_bot_environment(): - """Picks the most appropriate channel depending on whether the current chromium + """Picks the most appropriate channel depending on whether the current chromium checkout is a release branch or not.""" - if not _is_bot_environment(): - raise RuntimeError('This is only supported when running within a builder ' - 'environment') + if not _is_bot_environment(): + raise RuntimeError( + 'This is only supported when running within a builder ' + 'environment') - print('Running automatic channel selection logic.') - # We check the build/util/LASTCHANGE file to see if we are on a release branch or not. - last_change = _get_chromium_last_change() + print('Running automatic channel selection logic.') + # We check the build/util/LASTCHANGE file to see if we are on a release branch or not. + last_change = _get_chromium_last_change() - # Content is of the format: - # COMMIT_HASH-refs/heads/main@{#COMMIT_NUMBER} - if 'refs/heads/main' in last_change: - return 'tot' + # Content is of the format: + # COMMIT_HASH-refs/heads/main@{#COMMIT_NUMBER} + if 'refs/heads/main' in last_change: + return 'tot' - # COMMIT_HASH-refs/branch-heads/branch_number@{#COMMIT_NUMBER} - branch_number = _get_version_string().split('.')[2] - if f'refs/branch-heads/{branch_number}' in last_change: - return 'stable' - raise ValueError( - f'Could not automatically determine the appropriate channel. LASTCHANGE value is {last_change}' - ) + # COMMIT_HASH-refs/branch-heads/branch_number@{#COMMIT_NUMBER} + branch_number = _get_version_string().split('.')[2] + if f'refs/branch-heads/{branch_number}' in last_change: + return 'stable' + raise ValueError( + f'Could not automatically determine the appropriate channel. LASTCHANGE value is {last_change}' + ) def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--stamp', type=str, help='Path to touch on success') - parser.add_argument('--config', - type=str, - help='Copy.bara.sky file path to run Copybara on', - default=_COPYBARA_CONFIG_PATH, - required=False) - parser.add_argument('--copybara', - type=str, - help=('Path to copybara executable binary downloaded ' - 'from CIPD'), - default=_COPYBARA_PATH, - required=False) - parser.add_argument('--skip_build_scripts', - type=bool, - help=('Skips building the build_scripts output, ' - 'this should be only used for testing.')) - parser.add_argument('--skip-copybara', - action='store_true', - help=("Only generate the build files - do not run " - "copybara afterwards. This is useful if you only " - "want to take a look at the generated files " - "without doing an actual import.")) - parser.add_argument('--git-url-and-branch', - type=str, - help=("Git URL and branch to push to. If not specified, " - "creates an AOSP Gerrit CL. This option is useful " - "to push to a local git repo for manual testing, " - "for example: " - "file:////home/foo/aosp/external/cronet mybranch"), - nargs=2) - parser.add_argument('--keep-temporary-files', - action='store_true', - help=("Don't clean up temporary files. Useful for " - "troubleshooting.")) - parser.add_argument('--regenerate-consistency-file', - action='store_true', - help=("Ask copybara to ignore the existing merge import " - "consistency file and generate a new one. Note for " - "this to work the script must be run from the same " - "origin as the one that was used for the last " - "import into the destination; in other words, you " - "must re-import the exact same Cronet version that " - "is currently in the destination.")) - parser.add_argument('--channel', - help='The channel this execution of gn2bp is targeting. ' - 'This must not be used when running the script within a ' - 'CQ/CI bot. In that scenario the channel being targeted ' - 'is defined based on the environment we are running in ' - '(see _pick_target_channel_for_bot_environment).', - type=str, - choices=['tot', 'stable'], - default=None) - parser.add_argument( - '--wait-for-presubmit-verified', - help= - 'Whether the script should wait for presubmit verified after uploading a CL to Android', - action='store_true') - parser.add_argument( - '--skip-release-validation', - help= - 'Validates the current Git history against the remote breakages.json file to ensure no known breakages are present.', - default=False, - action='store_true') - args = parser.parse_args() + parser = argparse.ArgumentParser() + parser.add_argument('--stamp', type=str, help='Path to touch on success') + parser.add_argument('--config', + type=str, + help='Copy.bara.sky file path to run Copybara on', + default=_COPYBARA_CONFIG_PATH, + required=False) + parser.add_argument('--copybara', + type=str, + help=('Path to copybara executable binary downloaded ' + 'from CIPD'), + default=_COPYBARA_PATH, + required=False) + parser.add_argument('--skip_build_scripts', + type=bool, + help=('Skips building the build_scripts output, ' + 'this should be only used for testing.')) + parser.add_argument( + '--skip-copybara', + action='store_true', + help=("Only generate the build files - do not run " + "copybara afterwards. This is useful if you only " + "want to take a look at the generated files " + "without doing an actual import.")) + parser.add_argument( + '--git-url-and-branch', + type=str, + help=("Git URL and branch to push to. If not specified, " + "creates an AOSP Gerrit CL. This option is useful " + "to push to a local git repo for manual testing, " + "for example: " + "file:////home/foo/aosp/external/cronet mybranch"), + nargs=2) + parser.add_argument('--keep-temporary-files', + action='store_true', + help=("Don't clean up temporary files. Useful for " + "troubleshooting.")) + parser.add_argument( + '--regenerate-consistency-file', + action='store_true', + help=("Ask copybara to ignore the existing merge import " + "consistency file and generate a new one. Note for " + "this to work the script must be run from the same " + "origin as the one that was used for the last " + "import into the destination; in other words, you " + "must re-import the exact same Cronet version that " + "is currently in the destination.")) + parser.add_argument( + '--channel', + help='The channel this execution of gn2bp is targeting. ' + 'This must not be used when running the script within a ' + 'CQ/CI bot. In that scenario the channel being targeted ' + 'is defined based on the environment we are running in ' + '(see _pick_target_channel_for_bot_environment).', + type=str, + choices=['tot', 'stable'], + default=None) + parser.add_argument( + '--wait-for-presubmit-verified', + help= + 'Whether the script should wait for presubmit verified after uploading a CL to Android', + action='store_true') + parser.add_argument( + '--skip-release-validation', + help= + 'Validates the current Git history against the remote breakages.json file to ensure no known breakages are present.', + default=False, + action='store_true') + args = parser.parse_args() - if _is_bot_environment(): - if args.channel is not None: - raise RuntimeError('Automatic channel selection must be used in a bot ' - f'environment. However, found {args.channel}') - args.channel = _pick_target_channel_for_bot_environment() - print(f'Automatic selection logic has chosen `{args.channel}` track') + if _is_bot_environment(): + if args.channel is not None: + raise RuntimeError( + 'Automatic channel selection must be used in a bot ' + f'environment. However, found {args.channel}') + args.channel = _pick_target_channel_for_bot_environment() + print(f'Automatic selection logic has chosen `{args.channel}` track') - # Don't validate releases for trybots. Otherwise, in certain scenarios, this could prevent - # landing fixes. For example, whenever a breakage entry does not have a good change ID. - # In this case, validate_release will always fail in CQ: breakages.json is always fetched - # from HEAD, making validate_release believe that no fix has landed yet (and also preventing - # said fix from landing). - if not _is_trybot() and not args.skip_release_validation: - validate_release(_fetch_breakages(), - _get_change_ids_from_head(_MONTHS_OF_CHANGELIST)) - else: - print("Skipping release validation") + # Don't validate releases for trybots. Otherwise, in certain scenarios, this could prevent + # landing fixes. For example, whenever a breakage entry does not have a good change ID. + # In this case, validate_release will always fail in CQ: breakages.json is always fetched + # from HEAD, making validate_release believe that no fix has landed yet (and also preventing + # said fix from landing). + if not _is_trybot() and not args.skip_release_validation: + validate_release(_fetch_breakages(), + _get_change_ids_from_head(_MONTHS_OF_CHANGELIST)) + else: + print("Skipping release validation") - if args.channel not in ['tot', 'stable']: - raise ValueError('Invalid {args.channel=}') + if args.channel not in ['tot', 'stable']: + raise ValueError('Invalid {args.channel=}') - delete_temporary_files = not args.keep_temporary_files + delete_temporary_files = not args.keep_temporary_files - if not args.skip_copybara and os.listdir( - os.path.join(REPOSITORY_ROOT, 'clank')): - raise RuntimeError( - 'gn2bp should not be run with an internal code checkout, as copybara' - ' may end up leaking internal code to the destination') + if not args.skip_copybara and os.listdir( + os.path.join(REPOSITORY_ROOT, 'clank')): + raise RuntimeError( + 'gn2bp should not be run with an internal code checkout, as copybara' + ' may end up leaking internal code to the destination') - try: - arch_to_desc_file = { - arch: - tempfile.NamedTemporaryFile(mode="w+", - encoding='utf-8', - delete=delete_temporary_files) - for arch in cronet_utils.ARCHS - } - with multiprocessing.dummy.Pool(len(arch_to_desc_file.items())) as pool: - results = [ - pool.apply_async(_fill_desc_file_for_arch, - (arch, desc_file, delete_temporary_files)) - for (arch, desc_file) in arch_to_desc_file.items() - ] - for result in results: - # We don't care about result, since there isn't one. This is only - # needed to re-raises failures raised by _fill_desc_file_for_arch, - # if any. - result.get() + try: + arch_to_desc_file = { + arch: + tempfile.NamedTemporaryFile(mode="w+", + encoding='utf-8', + delete=delete_temporary_files) + for arch in cronet_utils.ARCHS + } + with multiprocessing.dummy.Pool(len( + arch_to_desc_file.items())) as pool: + results = [ + pool.apply_async(_fill_desc_file_for_arch, + (arch, desc_file, delete_temporary_files)) + for (arch, desc_file) in arch_to_desc_file.items() + ] + for result in results: + # We don't care about result, since there isn't one. This is only + # needed to re-raises failures raised by _fill_desc_file_for_arch, + # if any. + result.get() - _run_license_generation() - _run_gn2bp(desc_files=arch_to_desc_file.values(), - skip_build_scripts=args.skip_build_scripts, - delete_temporary_files=delete_temporary_files, - channel=args.channel) - _gen_boringssl(args.channel) - _gen_extras_bp(args.channel) - _gen_androidtest_xml(args.channel) + _run_license_generation() + _run_gn2bp(desc_files=arch_to_desc_file.values(), + skip_build_scripts=args.skip_build_scripts, + delete_temporary_files=delete_temporary_files, + channel=args.channel) + _gen_boringssl(args.channel) + _gen_extras_bp(args.channel) + _gen_androidtest_xml(args.channel) - if not args.skip_copybara: - _run_copybara_to_aosp( - config=args.config, - copybara_binary=args.copybara, - git_url_and_branch=args.git_url_and_branch, - regenerate_consistency_file=args.regenerate_consistency_file, - import_channel=args.channel, - wait_for_presubmit_verified=args.wait_for_presubmit_verified) + if not args.skip_copybara: + _run_copybara_to_aosp( + config=args.config, + copybara_binary=args.copybara, + git_url_and_branch=args.git_url_and_branch, + regenerate_consistency_file=args.regenerate_consistency_file, + import_channel=args.channel, + wait_for_presubmit_verified=args.wait_for_presubmit_verified) - finally: - for file in arch_to_desc_file.values(): - # Close the temporary files so they can be deleted. - file.close() + finally: + for file in arch_to_desc_file.values(): + # Close the temporary files so they can be deleted. + file.close() - if args.stamp is not None: - build_utils.Touch(args.stamp) - print('Success!') - return 0 + if args.stamp is not None: + build_utils.Touch(args.stamp) + print('Success!') + return 0 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/gn2bp/tests/run_gn2bp_unittest.py b/components/cronet/gn2bp/tests/run_gn2bp_unittest.py index e9b8fa0..93904ab 100755 --- a/components/cronet/gn2bp/tests/run_gn2bp_unittest.py +++ b/components/cronet/gn2bp/tests/run_gn2bp_unittest.py
@@ -18,96 +18,100 @@ class TestRunGN2BPUnitTest(unittest.TestCase): - def test_bad_change_id_no_good_change_id_should_throw(self): - self.assertRaisesRegex( - RuntimeError, - 'Stopping the import: there is a breakage that has not been fixed yet.', - run_gn2bp.validate_release, [{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: [] - }], {'foo': 0}) + def test_bad_change_id_no_good_change_id_should_throw(self): + self.assertRaisesRegex( + RuntimeError, + 'Stopping the import: there is a breakage that has not been fixed yet.', + run_gn2bp.validate_release, + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: [] + }], {'foo': 0}) - def test_bad_change_id_and_no_good_change_id_but_but_both_not_in_history_should_throw( - self): - self.assertRaisesRegex( - RuntimeError, - 'Stopping the import: there is a breakage that has not been fixed yet.', - run_gn2bp.validate_release, [{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: [] - }], {}) + def test_bad_change_id_and_no_good_change_id_but_but_both_not_in_history_should_throw( + self): + self.assertRaisesRegex( + RuntimeError, + 'Stopping the import: there is a breakage that has not been fixed yet.', + run_gn2bp.validate_release, + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: [] + }], {}) - def test_bad_change_id_and_good_change_id_but_but_both_not_in_history_should_not_throw( - self): - run_gn2bp.validate_release([{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] - }], {}) + def test_bad_change_id_and_good_change_id_but_but_both_not_in_history_should_not_throw( + self): + run_gn2bp.validate_release( + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] + }], {}) - def test_bad_change_id_and_good_change_id_but_but_bad_not_in_history_should_not_throw( - self): - run_gn2bp.validate_release([{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] - }], {'bar': 0}) + def test_bad_change_id_and_good_change_id_but_but_bad_not_in_history_should_not_throw( + self): + run_gn2bp.validate_release( + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] + }], {'bar': 0}) - def test_bad_change_id_and_good_change_id_but_not_in_history_should_throw( - self): - self.assertRaisesRegex( - RuntimeError, - 'Stopping the import: the current checkout includes a breaking change, but not its fix.', - run_gn2bp.validate_release, - [{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] - }], {'foo': 0}) + def test_bad_change_id_and_good_change_id_but_not_in_history_should_throw( + self): + self.assertRaisesRegex( + RuntimeError, + 'Stopping the import: the current checkout includes a breaking change, but not its fix.', + run_gn2bp.validate_release, + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] + }], {'foo': 0}) - def test_sort_versions(self): - self.assertEqual( - run_gn2bp.sort_versions(["123.0.1000.0", "123.0.999.0", - "123.0.1001.0"]), - ["123.0.999.0", "123.0.1000.0", "123.0.1001.0"]) + def test_sort_versions(self): + self.assertEqual( + run_gn2bp.sort_versions( + ["123.0.1000.0", "123.0.999.0", "123.0.1001.0"]), + ["123.0.999.0", "123.0.1000.0", "123.0.1001.0"]) + def test_bad_change_id_and_good_change_id_in_history_should_not_throw( + self): + run_gn2bp.validate_release( + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] + }], { + 'bar': 0, + 'foo': 1 + }) + def test_bad_change_id_and_good_change_id_but_before_bad_change_id_should_throw( + self): + self.assertRaisesRegex( + RuntimeError, + 'the local history shows a bad change ID that is more recent than its fix', + run_gn2bp.validate_release, + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] + }], { + 'bar': 1, + 'foo': 0 + }) - def test_bad_change_id_and_good_change_id_in_history_should_not_throw(self): - run_gn2bp.validate_release([{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] - }], { - 'bar': 0, - 'foo': 1 - }) - - def test_bad_change_id_and_good_change_id_but_before_bad_change_id_should_throw( - self): - self.assertRaisesRegex( - RuntimeError, - 'the local history shows a bad change ID that is more recent than its fix', - run_gn2bp.validate_release, - [{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar'] - }], { - 'bar': 1, - 'foo': 0 - }) - - def test_bad_change_id_but_two_good_change_ids_should_throw(self): - self.assertRaisesRegex( - RuntimeError, - 'Multiple good change IDs are only necessary when a fix has to be cherry-picked into a release', - run_gn2bp.validate_release, - [{ - breakages_constants.BAD_CHANGE_ID_TXT: 'foo', - breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar', 'bar_branch'] - }], { - 'bar': 0, - 'bar_branch': 1, - 'foo': 2 - }) + def test_bad_change_id_but_two_good_change_ids_should_throw(self): + self.assertRaisesRegex( + RuntimeError, + 'Multiple good change IDs are only necessary when a fix has to be cherry-picked into a release', + run_gn2bp.validate_release, + [{ + breakages_constants.BAD_CHANGE_ID_TXT: 'foo', + breakages_constants.GOOD_CHANGE_IDS_TXT: ['bar', 'bar_branch'] + }], { + 'bar': 0, + 'bar_branch': 1, + 'foo': 2 + }) if __name__ == '__main__': - # This allows you to run the file directly - unittest.main() + # This allows you to run the file directly + unittest.main()
diff --git a/components/cronet/license/constants.py b/components/cronet/license/constants.py index df4fc0aa..fc5eeac 100644 --- a/components/cronet/license/constants.py +++ b/components/cronet/license/constants.py
@@ -6,59 +6,55 @@ from mapper import Mapper from license_type import LicenseType -def create_license_post_processing(*args: Mapper) -> Callable: - def __update_metadata(metadata: Dict[str, Union[str, List[str]]]) -> Dict[ - str, Union[str, List[str]]]: - for mapper in args: - mapper.write(metadata) - return metadata - return __update_metadata +def create_license_post_processing(*args: Mapper) -> Callable: + + def __update_metadata( + metadata: Dict[str, Union[str, List[str]]] + ) -> Dict[str, Union[str, List[str]]]: + for mapper in args: + mapper.write(metadata) + return metadata + + return __update_metadata + RAW_LICENSE_TO_FORMATTED_DETAILS = { - "blessing": ("blessing", LicenseType.UNENCUMBERED, "SPDX-license-identifier-blessing"), + "blessing": + ("blessing", LicenseType.UNENCUMBERED, "SPDX-license-identifier-blessing"), "BSD": ("BSD", LicenseType.NOTICE, "SPDX-license-identifier-BSD"), - "BSD-2-Clause": ("BSD_2_CLAUSE", LicenseType.NOTICE, "SPDX-license-identifier-BSD-2-Clause"), - "BSD 3-Clause": ( - "BSD_3_CLAUSE", LicenseType.NOTICE, - "SPDX-license-identifier-BSD-3-Clause"), - "BSD-3-Clause": ( - "BSD_3_CLAUSE", LicenseType.NOTICE, - "SPDX-license-identifier-BSD-3-Clause"), - "Apache 2.0": ( - "APACHE_2_0", LicenseType.NOTICE, "SPDX-license-identifier-Apache-2.0"), + "BSD-2-Clause": ("BSD_2_CLAUSE", LicenseType.NOTICE, + "SPDX-license-identifier-BSD-2-Clause"), + "BSD 3-Clause": ("BSD_3_CLAUSE", LicenseType.NOTICE, + "SPDX-license-identifier-BSD-3-Clause"), + "BSD-3-Clause": ("BSD_3_CLAUSE", LicenseType.NOTICE, + "SPDX-license-identifier-BSD-3-Clause"), + "Apache 2.0": + ("APACHE_2_0", LicenseType.NOTICE, "SPDX-license-identifier-Apache-2.0"), # Different Apache 2.0 format used in Chromium. - "Apache-2.0": ( - "APACHE_2_0", LicenseType.NOTICE, "SPDX-license-identifier-Apache-2.0"), + "Apache-2.0": + ("APACHE_2_0", LicenseType.NOTICE, "SPDX-license-identifier-Apache-2.0"), "MIT": ("MIT", LicenseType.NOTICE, "SPDX-license-identifier-MIT"), - "Unicode-3.0": ( - "UNICODE_3_0", LicenseType.NOTICE, - "SPDX-license-identifier-Unicode-3.0"), - "Unicode-DFS-2016": ( - "UNICODE", LicenseType.NOTICE, - "SPDX-license-identifier-Unicode-DFS-2016"), - "ICU": ( - "ICU", LicenseType.NOTICE, - "SPDX-license-identifier-ICU"), - "Zlib": - ("ZLIB", LicenseType.RECIPROCAL, "SPDX-license-identifier-Zlib"), - "MPL 1.1": - ("MPL", LicenseType.RECIPROCAL, "SPDX-license-identifier-MPL-1.1"), - "MPL-1.1": - ("MPL", LicenseType.RECIPROCAL, "SPDX-license-identifier-MPL-1.1"), - "MPL 2.0": - ("MPL", LicenseType.RECIPROCAL, "SPDX-license-identifier-MPL-2.0"), - "MPL-2.0": - ("MPL", LicenseType.RECIPROCAL, "SPDX-license-identifier-MPL-2.0"), - "MPL-2": - ("MPL", LicenseType.RECIPROCAL, "SPDX-license-identifier-MPL-2.0"), - "NCSA": - ("NCSA", LicenseType.NOTICE, "SPDX-license-identifier-NCSA"), - "ISC": - ("ISC", LicenseType.NOTICE, "SPDX-license-identifier-ISC"), - "unencumbered": - ("UNENCUMBERED", LicenseType.UNENCUMBERED, - "SPDX-license-identifier-Unlicense"), + "Unicode-3.0": + ("UNICODE_3_0", LicenseType.NOTICE, "SPDX-license-identifier-Unicode-3.0"), + "Unicode-DFS-2016": ("UNICODE", LicenseType.NOTICE, + "SPDX-license-identifier-Unicode-DFS-2016"), + "ICU": ("ICU", LicenseType.NOTICE, "SPDX-license-identifier-ICU"), + "Zlib": ("ZLIB", LicenseType.RECIPROCAL, "SPDX-license-identifier-Zlib"), + "MPL 1.1": ("MPL", LicenseType.RECIPROCAL, + "SPDX-license-identifier-MPL-1.1"), + "MPL-1.1": ("MPL", LicenseType.RECIPROCAL, + "SPDX-license-identifier-MPL-1.1"), + "MPL 2.0": ("MPL", LicenseType.RECIPROCAL, + "SPDX-license-identifier-MPL-2.0"), + "MPL-2.0": ("MPL", LicenseType.RECIPROCAL, + "SPDX-license-identifier-MPL-2.0"), + "MPL-2": ("MPL", LicenseType.RECIPROCAL, + "SPDX-license-identifier-MPL-2.0"), + "NCSA": ("NCSA", LicenseType.NOTICE, "SPDX-license-identifier-NCSA"), + "ISC": ("ISC", LicenseType.NOTICE, "SPDX-license-identifier-ISC"), + "unencumbered": ("UNENCUMBERED", LicenseType.UNENCUMBERED, + "SPDX-license-identifier-Unlicense"), } # This is relative to the repo_directory passed in |update_license| @@ -68,36 +64,45 @@ # # The current structure is Mapper(dictionary_key, expected_value, value_to_write) POST_PROCESS_OPERATION = { - "third_party/apache-portable-runtime/README.chromium": create_license_post_processing( - Mapper("License", ['Apache-2.0', 'dso', 'Zlib', 'ISC', 'BSD-4-Clause-UC'], ["Apache 2.0"])), - "third_party/compiler-rt/README.chromium": create_license_post_processing( + "third_party/apache-portable-runtime/README.chromium": + create_license_post_processing( Mapper("License", - ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], + ['Apache-2.0', 'dso', 'Zlib', 'ISC', 'BSD-4-Clause-UC'], + ["Apache 2.0"])), + "third_party/compiler-rt/README.chromium": + create_license_post_processing( + Mapper("License", ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], ["MIT"])), - "third_party/libc++abi/README.chromium": create_license_post_processing( - Mapper("License", - ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], + "third_party/libc++abi/README.chromium": + create_license_post_processing( + Mapper("License", ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], ["MIT"])), - "third_party/libc++/README.chromium": create_license_post_processing( - Mapper("License", - ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], + "third_party/libc++/README.chromium": + create_license_post_processing( + Mapper("License", ['NCSA', 'Apache-with-LLVM-Exception', 'MIT'], ["MIT"])), - "third_party/boringssl/README.chromium": create_license_post_processing( - Mapper("License", ['MIT', 'BSD-3-Clause', 'OpenSSL', 'ISC', 'SSLeay'], ["BSD"]), + "third_party/boringssl/README.chromium": + create_license_post_processing( + Mapper("License", ['MIT', 'BSD-3-Clause', 'OpenSSL', 'ISC', 'SSLeay'], + ["BSD"]), # TODO(b/360316861): Fix upstream by setting an explicit version to boringssl. Mapper("Version", "git", None)), - "net/third_party/quiche/METADATA": create_license_post_processing( + "net/third_party/quiche/METADATA": + create_license_post_processing( # TODO(b/360316861): Fix upstream by setting an explicit version to QUICHE. Mapper("Version", "git", None)), # TODO(b/360316861): Fix this upstream in Chromium. - "third_party/quic_trace/README.chromium": create_license_post_processing( + "third_party/quic_trace/README.chromium": + create_license_post_processing( Mapper("Version", "git", "caa0a6eaba816ecb737f9a70782b7c80b8ac8dbc")), - "third_party/metrics_proto/README.chromium": create_license_post_processing( + "third_party/metrics_proto/README.chromium": + create_license_post_processing( Mapper("URL", "This is the canonical public repository", "Piper")), - "third_party/boringssl/src/pki/testdata/nist-pkits/README.chromium": create_license_post_processing( + "third_party/boringssl/src/pki/testdata/nist-pkits/README.chromium": + create_license_post_processing( Mapper("License", [ - 'Public Domain: United States Government Work under 17 U.S.C. 105'], - ["unencumbered"])), + 'Public Domain: United States Government Work under 17 U.S.C. 105' + ], ["unencumbered"])), } # This is relative to the repo_directory passed in |update_license| @@ -127,6 +132,6 @@ # because they don't have a corresponding BUILD.gn file. # TODO: http://crbug.com/389925432 - remove the need for this list. INCLUDED_README = { - "base/third_party/nspr/README.chromium", - "url/third_party/mozilla/README.chromium", + "base/third_party/nspr/README.chromium", + "url/third_party/mozilla/README.chromium", }
diff --git a/components/cronet/license/mapper.py b/components/cronet/license/mapper.py index 1b4e721..4f300da 100644 --- a/components/cronet/license/mapper.py +++ b/components/cronet/license/mapper.py
@@ -1,27 +1,17 @@ -# Copyright (C) 2024 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. from typing import Dict, List, Union class MapperException(Exception): - # Raised when the Mapper expectation equality fails. - pass + # Raised when the Mapper expectation equality fails. + pass class Mapper: - """ + """ This will overwrite the value of the metadata field whose key is |dictionary_key| with the value declared in |write_value|, before the overwrite happens, it will check if the expected_value matches the value in the metadata field. @@ -30,14 +20,13 @@ at all in the metadata. """ - def __init__(self, dictionary_key: str, expected_value, write_value): - self._key = dictionary_key - self._expected_value = expected_value - self._write_value = write_value + def __init__(self, dictionary_key: str, expected_value, write_value): + self._key = dictionary_key + self._expected_value = expected_value + self._write_value = write_value - def write(self, - metadata: Dict[str, Union[str, List[str]]]) -> None: - """ + def write(self, metadata: Dict[str, Union[str, List[str]]]) -> None: + """ Writes the value |write_value| which is passed in the constructor of the Mapper to the |metadata| provided. Before the write operation happens, a check will occur to make sure that the value being overwritten matches @@ -49,22 +38,22 @@ :param metadata: A dictionary whose field with key |key| will be overwritten. :raises: MapperException if the expectation check has failed. """ - if self._expected_value is None and self._key in metadata: - # We expected the key not to exist but it existed. - raise MapperException( - f"Expected absence of key `{self._key}` but " - f"found {metadata[self._key]}") + if self._expected_value is None and self._key in metadata: + # We expected the key not to exist but it existed. + raise MapperException(f"Expected absence of key `{self._key}` but " + f"found {metadata[self._key]}") - if self._key not in metadata.keys() and self._expected_value: - # We expected a value but the key didn't exist, throw! - raise MapperException(f"Expected presence of key {self._key} but was" - f"not found.") + if self._key not in metadata.keys() and self._expected_value: + # We expected a value but the key didn't exist, throw! + raise MapperException( + f"Expected presence of key {self._key} but was" + f"not found.") - if self._key in metadata.keys() and metadata[ - self._key] != self._expected_value: - # We expected the value to match but it didn't. - raise MapperException( - f"Expected \"{self._expected_value}\" but found" - f" {metadata[self._key]} in the README.chromium") + if self._key in metadata.keys() and metadata[ + self._key] != self._expected_value: + # We expected the value to match but it didn't. + raise MapperException( + f"Expected \"{self._expected_value}\" but found" + f" {metadata[self._key]} in the README.chromium") - metadata[self._key] = self._write_value + metadata[self._key] = self._write_value
diff --git a/components/cronet/license/metadata.py b/components/cronet/license/metadata.py index 2f13bc9..e0237c6 100644 --- a/components/cronet/license/metadata.py +++ b/components/cronet/license/metadata.py
@@ -88,7 +88,9 @@ cpe_prefix = self.metadata.get("CPEPrefix") if cpe_prefix is not None: - third_party_dict["security"] = security_dict = metadata_dictionary.MetadataDictionary("security") + third_party_dict[ + "security"] = security_dict = metadata_dictionary.MetadataDictionary( + "security") security_dict["tag"] = f"\"NVD-CPE2.3:{cpe_prefix}\"" return "\n".join(
diff --git a/components/cronet/testing/run_all_python_unittests.py b/components/cronet/testing/run_all_python_unittests.py index 59de489..de7c971 100755 --- a/components/cronet/testing/run_all_python_unittests.py +++ b/components/cronet/testing/run_all_python_unittests.py
@@ -13,9 +13,13 @@ # Help Python find typ in //third_party/catapult/third_party/typ/ sys.path.append( - os.path.join(REPOSITORY_ROOT, 'third_party', 'catapult', 'third_party', 'typ')) + os.path.join(REPOSITORY_ROOT, 'third_party', 'catapult', 'third_party', + 'typ')) import typ - if __name__ == '__main__': - sys.exit(typ.main(top_level_dirs=[os.path.join(REPOSITORY_ROOT, 'components', 'cronet')], skip=['tools.api_static_checks_unittest.*'])) \ No newline at end of file + sys.exit( + typ.main(top_level_dirs=[ + os.path.join(REPOSITORY_ROOT, 'components', 'cronet') + ], + skip=['tools.api_static_checks_unittest.*']))
diff --git a/components/cronet/tools/.style.yapf b/components/cronet/tools/.style.yapf deleted file mode 100644 index 03fd7153..0000000 --- a/components/cronet/tools/.style.yapf +++ /dev/null
@@ -1,7 +0,0 @@ -[style] -based_on_style = pep8 - -# New directories should use a .style.yapf that does not include the following: -column_limit = 80 -indent_width = 2 -
diff --git a/components/cronet/tools/android_rndis_forwarder.py b/components/cronet/tools/android_rndis_forwarder.py index 6ae06cc..3f066b1 100644 --- a/components/cronet/tools/android_rndis_forwarder.py +++ b/components/cronet/tools/android_rndis_forwarder.py
@@ -12,10 +12,11 @@ import sys # pylint: disable=wrong-import-position -REPOSITORY_ROOT = os.path.abspath(os.path.join( - os.path.dirname(__file__), '..', '..', '..')) +REPOSITORY_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..')) sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools', 'perf')) from core import path_util + sys.path.append(path_util.GetTelemetryDir()) from telemetry.core import platform @@ -32,86 +33,89 @@ class AndroidRndisForwarder(object): - """Forwards traffic using RNDIS. Assumes the device has root access.""" + """Forwards traffic using RNDIS. Assumes the device has root access.""" - def __init__(self, device, rndis_configurator): - self._device = device - self._rndis_configurator = rndis_configurator - self._device_iface = rndis_configurator.device_iface - self._host_ip = rndis_configurator.host_ip - self._original_dns = None, None, None - self._RedirectPorts() - # The netd commands fail on Lollipop and newer releases, but aren't - # necessary as DNS isn't used. - # self._OverrideDns() - self._OverrideDefaultGateway() - # Need to override routing policy again since call to setifdns - # sometimes resets policy table - self._rndis_configurator.OverrideRoutingPolicy() - atexit.register(self.Close) - # TODO(tonyg): Verify that each port can connect to host. + def __init__(self, device, rndis_configurator): + self._device = device + self._rndis_configurator = rndis_configurator + self._device_iface = rndis_configurator.device_iface + self._host_ip = rndis_configurator.host_ip + self._original_dns = None, None, None + self._RedirectPorts() + # The netd commands fail on Lollipop and newer releases, but aren't + # necessary as DNS isn't used. + # self._OverrideDns() + self._OverrideDefaultGateway() + # Need to override routing policy again since call to setifdns + # sometimes resets policy table + self._rndis_configurator.OverrideRoutingPolicy() + atexit.register(self.Close) + # TODO(tonyg): Verify that each port can connect to host. - @property - def host_ip(self): - return self._host_ip + @property + def host_ip(self): + return self._host_ip - def Close(self): - #if self._forwarding: - # self._rndis_configurator.RestoreRoutingPolicy() - # self._SetDns(*self._original_dns) - # self._RestoreDefaultGateway() - #super(AndroidRndisForwarder, self).Close() - pass + def Close(self): + #if self._forwarding: + # self._rndis_configurator.RestoreRoutingPolicy() + # self._SetDns(*self._original_dns) + # self._RestoreDefaultGateway() + #super(AndroidRndisForwarder, self).Close() + pass - def _RedirectPorts(self): - """Sets the local to remote pair mappings to use for RNDIS.""" - # Flush any old nat rules. - self._device.RunShellCommand( - ['iptables', '-F', '-t', 'nat'], check_return=True) + def _RedirectPorts(self): + """Sets the local to remote pair mappings to use for RNDIS.""" + # Flush any old nat rules. + self._device.RunShellCommand(['iptables', '-F', '-t', 'nat'], + check_return=True) - def _OverrideDns(self): - """Overrides DNS on device to point at the host.""" - self._original_dns = self._GetCurrentDns() - self._SetDns(self._device_iface, self.host_ip, self.host_ip) + def _OverrideDns(self): + """Overrides DNS on device to point at the host.""" + self._original_dns = self._GetCurrentDns() + self._SetDns(self._device_iface, self.host_ip, self.host_ip) - def _SetDns(self, iface, dns1, dns2): - """Overrides device's DNS configuration. + def _SetDns(self, iface, dns1, dns2): + """Overrides device's DNS configuration. Args: iface: name of the network interface to make default dns1, dns2: nameserver IP addresses """ - if not iface: - return # If there is no route, then nobody cares about DNS. - # DNS proxy in older versions of Android is configured via properties. - # TODO(szym): run via su -c if necessary. - self._device.SetProp('net.dns1', dns1) - self._device.SetProp('net.dns2', dns2) - dnschange = self._device.GetProp('net.dnschange') - if dnschange: - self._device.SetProp('net.dnschange', str(int(dnschange) + 1)) - # Since commit 8b47b3601f82f299bb8c135af0639b72b67230e6 to frameworks/base - # the net.dns1 properties have been replaced with explicit commands for netd - self._device.RunShellCommand( - ['netd', 'resolver', 'setifdns', iface, dns1, dns2], check_return=True) - # TODO(szym): if we know the package UID, we could setifaceforuidrange - self._device.RunShellCommand( - ['netd', 'resolver', 'setdefaultif', iface], check_return=True) + if not iface: + return # If there is no route, then nobody cares about DNS. + # DNS proxy in older versions of Android is configured via properties. + # TODO(szym): run via su -c if necessary. + self._device.SetProp('net.dns1', dns1) + self._device.SetProp('net.dns2', dns2) + dnschange = self._device.GetProp('net.dnschange') + if dnschange: + self._device.SetProp('net.dnschange', str(int(dnschange) + 1)) + # Since commit 8b47b3601f82f299bb8c135af0639b72b67230e6 to frameworks/base + # the net.dns1 properties have been replaced with explicit commands for netd + self._device.RunShellCommand( + ['netd', 'resolver', 'setifdns', iface, dns1, dns2], + check_return=True) + # TODO(szym): if we know the package UID, we could setifaceforuidrange + self._device.RunShellCommand( + ['netd', 'resolver', 'setdefaultif', iface], check_return=True) - def _GetCurrentDns(self): - """Returns current gateway, dns1, and dns2.""" - routes = self._device.RunShellCommand( - ['cat', '/proc/net/route'], check_return=True)[1:] - routes = [route.split() for route in routes] - default_routes = [route[0] for route in routes if route[1] == '00000000'] - return ( - default_routes[0] if default_routes else None, - self._device.GetProp('net.dns1'), - self._device.GetProp('net.dns2'), - ) + def _GetCurrentDns(self): + """Returns current gateway, dns1, and dns2.""" + routes = self._device.RunShellCommand(['cat', '/proc/net/route'], + check_return=True)[1:] + routes = [route.split() for route in routes] + default_routes = [ + route[0] for route in routes if route[1] == '00000000' + ] + return ( + default_routes[0] if default_routes else None, + self._device.GetProp('net.dns1'), + self._device.GetProp('net.dns2'), + ) - def _OverrideDefaultGateway(self): - """Force traffic to go through RNDIS interface. + def _OverrideDefaultGateway(self): + """Force traffic to go through RNDIS interface. Override any default gateway route. Without this traffic may go through the wrong interface. @@ -120,165 +124,182 @@ (e.g. Telemetry crashes). A power cycle or "adb reboot" is a simple workaround around in that case. """ - # NOTE(pauljensen): On Nougat this can produce a weird message and return - # a non-zero value, but routing still seems fine, so don't check_return. - self._device.RunShellCommand( - ['route', 'add', 'default', 'gw', self.host_ip, - 'dev', self._device_iface]) + # NOTE(pauljensen): On Nougat this can produce a weird message and return + # a non-zero value, but routing still seems fine, so don't check_return. + self._device.RunShellCommand([ + 'route', 'add', 'default', 'gw', self.host_ip, 'dev', + self._device_iface + ]) - def _RestoreDefaultGateway(self): - self._device.RunShellCommand( - ['netcfg', self._device_iface, 'down'], check_return=True) + def _RestoreDefaultGateway(self): + self._device.RunShellCommand(['netcfg', self._device_iface, 'down'], + check_return=True) class AndroidRndisConfigurator(object): - """Configures a linux host to connect to an android device via RNDIS. + """Configures a linux host to connect to an android device via RNDIS. Note that we intentionally leave RNDIS running on the device. This is because the setup is slow and potentially flaky and leaving it running doesn't seem to interfere with any other developer or bot use-cases. """ - _RNDIS_DEVICE = '/sys/class/android_usb/android0' - _NETWORK_INTERFACES = '/etc/network/interfaces' - _INTERFACES_INCLUDE = 'source /etc/network/interfaces.d/*.conf' - _TELEMETRY_INTERFACE_FILE = '/etc/network/interfaces.d/telemetry-{}.conf' - _DEVICE_IP_ADDRESS = '192.168.123.2' + _RNDIS_DEVICE = '/sys/class/android_usb/android0' + _NETWORK_INTERFACES = '/etc/network/interfaces' + _INTERFACES_INCLUDE = 'source /etc/network/interfaces.d/*.conf' + _TELEMETRY_INTERFACE_FILE = '/etc/network/interfaces.d/telemetry-{}.conf' + _DEVICE_IP_ADDRESS = '192.168.123.2' - def __init__(self, device): - self._device = device + def __init__(self, device): + self._device = device - try: - self._device.EnableRoot() - except device_errors.CommandFailedError: - logging.error('RNDIS forwarding requires a rooted device.') - raise + try: + self._device.EnableRoot() + except device_errors.CommandFailedError: + logging.error('RNDIS forwarding requires a rooted device.') + raise - self._device_ip = None - self._host_iface = None - self._host_ip = None - self.device_iface = None + self._device_ip = None + self._host_iface = None + self._host_ip = None + self.device_iface = None - if platform.GetHostPlatform().GetOSName() == 'mac': - self._InstallHorndis(platform.GetHostPlatform().GetArchName()) + if platform.GetHostPlatform().GetOSName() == 'mac': + self._InstallHorndis(platform.GetHostPlatform().GetArchName()) - assert self._IsRndisSupported(), 'Device does not support RNDIS.' - self._CheckConfigureNetwork() + assert self._IsRndisSupported(), 'Device does not support RNDIS.' + self._CheckConfigureNetwork() - @property - def host_ip(self): - return self._host_ip + @property + def host_ip(self): + return self._host_ip - def _IsRndisSupported(self): - """Checks that the device has RNDIS support in the kernel.""" - return self._device.FileExists('%s/f_rndis/device' % self._RNDIS_DEVICE) + def _IsRndisSupported(self): + """Checks that the device has RNDIS support in the kernel.""" + return self._device.FileExists('%s/f_rndis/device' % + self._RNDIS_DEVICE) - # pylint: disable=inconsistent-return-statements - def _FindDeviceRndisInterface(self): - """Returns the name of the RNDIS network interface if present.""" - config = self._device.RunShellCommand( - ['ip', '-o', 'link', 'show'], check_return=True) - interfaces = [line.split(':')[1].strip() for line in config] - candidates = [iface for iface in interfaces if re.match('rndis|usb', iface)] - if candidates: - candidates.sort() - if len(candidates) == 2 and candidates[0].startswith('rndis') and \ - candidates[1].startswith('usb'): - return candidates[0] - assert len(candidates) == 1, 'Found more than one rndis device!' - return candidates[0] - # pylint: enable=inconsistent-return-statements + # pylint: disable=inconsistent-return-statements + def _FindDeviceRndisInterface(self): + """Returns the name of the RNDIS network interface if present.""" + config = self._device.RunShellCommand(['ip', '-o', 'link', 'show'], + check_return=True) + interfaces = [line.split(':')[1].strip() for line in config] + candidates = [ + iface for iface in interfaces if re.match('rndis|usb', iface) + ] + if candidates: + candidates.sort() + if len(candidates) == 2 and candidates[0].startswith('rndis') and \ + candidates[1].startswith('usb'): + return candidates[0] + assert len(candidates) == 1, 'Found more than one rndis device!' + return candidates[0] - def _FindDeviceRndisMacAddress(self, interface): - """Returns the MAC address of the RNDIS network interface if present.""" - config = self._device.RunShellCommand( - ['ip', '-o', 'link', 'show', interface], check_return=True)[0] - return config.split('link/ether ')[1][:17] + # pylint: enable=inconsistent-return-statements - def _EnumerateHostInterfaces(self): - host_platform = platform.GetHostPlatform().GetOSName() - if host_platform == 'linux': - return subprocess.check_output(['ip', 'addr'], - encoding='utf8').splitlines() - if host_platform == 'mac': - return subprocess.check_output(['ifconfig'], encoding='utf8').splitlines() - raise NotImplementedError('Platform %s not supported!' % host_platform) + def _FindDeviceRndisMacAddress(self, interface): + """Returns the MAC address of the RNDIS network interface if present.""" + config = self._device.RunShellCommand( + ['ip', '-o', 'link', 'show', interface], check_return=True)[0] + return config.split('link/ether ')[1][:17] - # pylint: disable=inconsistent-return-statements - def _FindHostRndisInterface(self, device_mac_address): - """Returns the name of the host-side network interface.""" - interface_list = self._EnumerateHostInterfaces() - ether_address = self._device.ReadFile( - '%s/f_rndis/ethaddr' % self._RNDIS_DEVICE, - as_root=True, force_pull=True).strip() - interface_name = None - for line in interface_list: - if not line.startswith((' ', '\t')): - interface_name = line.split(':')[-2].strip() - # Attempt to ping device to trigger ARP for device. - with open(os.devnull, 'wb') as devnull: - subprocess.call(['ping', '-w1', '-c1', '-I', interface_name, - self._DEVICE_IP_ADDRESS], stdout=devnull, stderr=devnull) - # Check if ARP cache now has device in it. - arp = subprocess.check_output( - ['arp', '-i', interface_name, self._DEVICE_IP_ADDRESS], - encoding='utf8') - if device_mac_address in arp: - return interface_name - elif ether_address in line: - return interface_name - # NOTE(pauljensen): |ether_address| seems incorrect on Nougat devices, - # but just going by the host interface name seems safe enough. - elif interface_name == 'usb0': - return interface_name - # pylint: enable=inconsistent-return-statements + def _EnumerateHostInterfaces(self): + host_platform = platform.GetHostPlatform().GetOSName() + if host_platform == 'linux': + return subprocess.check_output(['ip', 'addr'], + encoding='utf8').splitlines() + if host_platform == 'mac': + return subprocess.check_output(['ifconfig'], + encoding='utf8').splitlines() + raise NotImplementedError('Platform %s not supported!' % host_platform) - def _WriteProtectedFile(self, file_path, contents): - subprocess.check_call( - ['/usr/bin/sudo', 'bash', '-c', - 'echo -e "%s" > %s' % (contents, file_path)]) + # pylint: disable=inconsistent-return-statements + def _FindHostRndisInterface(self, device_mac_address): + """Returns the name of the host-side network interface.""" + interface_list = self._EnumerateHostInterfaces() + ether_address = self._device.ReadFile('%s/f_rndis/ethaddr' % + self._RNDIS_DEVICE, + as_root=True, + force_pull=True).strip() + interface_name = None + for line in interface_list: + if not line.startswith((' ', '\t')): + interface_name = line.split(':')[-2].strip() + # Attempt to ping device to trigger ARP for device. + with open(os.devnull, 'wb') as devnull: + subprocess.call([ + 'ping', '-w1', '-c1', '-I', interface_name, + self._DEVICE_IP_ADDRESS + ], + stdout=devnull, + stderr=devnull) + # Check if ARP cache now has device in it. + arp = subprocess.check_output( + ['arp', '-i', interface_name, self._DEVICE_IP_ADDRESS], + encoding='utf8') + if device_mac_address in arp: + return interface_name + elif ether_address in line: + return interface_name + # NOTE(pauljensen): |ether_address| seems incorrect on Nougat devices, + # but just going by the host interface name seems safe enough. + elif interface_name == 'usb0': + return interface_name - def _LoadInstalledHoRNDIS(self): - """Attempt to load HoRNDIS if installed. + # pylint: enable=inconsistent-return-statements + + def _WriteProtectedFile(self, file_path, contents): + subprocess.check_call([ + '/usr/bin/sudo', 'bash', '-c', + 'echo -e "%s" > %s' % (contents, file_path) + ]) + + def _LoadInstalledHoRNDIS(self): + """Attempt to load HoRNDIS if installed. If kext could not be loaded or if HoRNDIS is not installed, return False. """ - if not os.path.isdir('/System/Library/Extensions/HoRNDIS.kext'): - logging.info('HoRNDIS not present on system.') - return False + if not os.path.isdir('/System/Library/Extensions/HoRNDIS.kext'): + logging.info('HoRNDIS not present on system.') + return False - def HoRNDISLoaded(): - return 'HoRNDIS' in subprocess.check_output(['kextstat'], encoding='utf8') + def HoRNDISLoaded(): + return 'HoRNDIS' in subprocess.check_output(['kextstat'], + encoding='utf8') - if HoRNDISLoaded(): - return True + if HoRNDISLoaded(): + return True - logging.info('HoRNDIS installed but not running, trying to load manually.') - subprocess.check_call( - ['/usr/bin/sudo', 'kextload', '-b', 'com.joshuawise.kexts.HoRNDIS']) + logging.info( + 'HoRNDIS installed but not running, trying to load manually.') + subprocess.check_call([ + '/usr/bin/sudo', 'kextload', '-b', 'com.joshuawise.kexts.HoRNDIS' + ]) - return HoRNDISLoaded() + return HoRNDISLoaded() - def _InstallHorndis(self, arch_name): - if self._LoadInstalledHoRNDIS(): - logging.info('HoRNDIS kext loaded successfully.') - return - logging.info('Installing HoRNDIS...') - pkg_path = binary_manager.FetchPath('horndis', 'mac', arch_name) - subprocess.check_call( - ['/usr/bin/sudo', 'installer', '-pkg', pkg_path, '-target', '/']) + def _InstallHorndis(self, arch_name): + if self._LoadInstalledHoRNDIS(): + logging.info('HoRNDIS kext loaded successfully.') + return + logging.info('Installing HoRNDIS...') + pkg_path = binary_manager.FetchPath('horndis', 'mac', arch_name) + subprocess.check_call( + ['/usr/bin/sudo', 'installer', '-pkg', pkg_path, '-target', '/']) - def _DisableRndis(self): - # Set expect_status=None as this will temporarily break the adb connection. - self._device.adb.Shell('setprop sys.usb.config adb', expect_status=None) - self._device.WaitUntilFullyBooted() + def _DisableRndis(self): + # Set expect_status=None as this will temporarily break the adb connection. + self._device.adb.Shell('setprop sys.usb.config adb', + expect_status=None) + self._device.WaitUntilFullyBooted() - def _EnableRndis(self): - """Enables the RNDIS network interface.""" - script_prefix = '/data/local/tmp/rndis' - # This could be accomplished via "svc usb setFunction rndis" but only on - # devices which have the "USB tethering" feature. - # Also, on some devices, it's necessary to go through "none" function. - script = """ + def _EnableRndis(self): + """Enables the RNDIS network interface.""" + script_prefix = '/data/local/tmp/rndis' + # This could be accomplished via "svc usb setFunction rndis" but only on + # devices which have the "USB tethering" feature. + # Also, on some devices, it's necessary to go through "none" function. + script = """ trap '' HUP trap '' TERM trap '' PIPE @@ -310,235 +331,261 @@ } doit & - """ % {'dev': self._RNDIS_DEVICE, - 'functions': 'rndis,adb', - 'prefix': script_prefix, - 'device_ip_address': self._DEVICE_IP_ADDRESS} - script_file = '%s.sh' % script_prefix - log_file = '%s.log' % script_prefix - self._device.WriteFile(script_file, script) - # TODO(szym): run via su -c if necessary. - self._device.RemovePath(log_file, force=True) - self._device.RunShellCommand(['.', script_file], check_return=True) - self._device.WaitUntilFullyBooted() - result = self._device.ReadFile(log_file).splitlines() - assert any('DONE' in line for line in result), 'RNDIS script did not run!' + """ % { + 'dev': self._RNDIS_DEVICE, + 'functions': 'rndis,adb', + 'prefix': script_prefix, + 'device_ip_address': self._DEVICE_IP_ADDRESS + } + script_file = '%s.sh' % script_prefix + log_file = '%s.log' % script_prefix + self._device.WriteFile(script_file, script) + # TODO(szym): run via su -c if necessary. + self._device.RemovePath(log_file, force=True) + self._device.RunShellCommand(['.', script_file], check_return=True) + self._device.WaitUntilFullyBooted() + result = self._device.ReadFile(log_file).splitlines() + assert any('DONE' in line + for line in result), 'RNDIS script did not run!' - def _CheckEnableRndis(self, force): - """Enables the RNDIS network interface, retrying if necessary. + def _CheckEnableRndis(self, force): + """Enables the RNDIS network interface, retrying if necessary. Args: force: Disable RNDIS first, even if it appears already enabled. Returns: device_iface: RNDIS interface name on the device host_iface: corresponding interface name on the host """ - for _ in range(3): - if not force: - device_iface = self._FindDeviceRndisInterface() - if device_iface: - device_mac_address = self._FindDeviceRndisMacAddress(device_iface) - host_iface = self._FindHostRndisInterface(device_mac_address) - if host_iface: - return device_iface, host_iface - self._DisableRndis() - self._EnableRndis() - force = False - raise Exception('Could not enable RNDIS, giving up.') + for _ in range(3): + if not force: + device_iface = self._FindDeviceRndisInterface() + if device_iface: + device_mac_address = self._FindDeviceRndisMacAddress( + device_iface) + host_iface = self._FindHostRndisInterface( + device_mac_address) + if host_iface: + return device_iface, host_iface + self._DisableRndis() + self._EnableRndis() + force = False + raise Exception('Could not enable RNDIS, giving up.') - def _Ip2Long(self, addr): - return struct.unpack('!L', socket.inet_aton(addr))[0] + def _Ip2Long(self, addr): + return struct.unpack('!L', socket.inet_aton(addr))[0] - def _IpPrefix2AddressMask(self, addr): - def _Length2Mask(length): - return 0xFFFFFFFF & ~((1 << (32 - length)) - 1) + def _IpPrefix2AddressMask(self, addr): - addr, masklen = addr.split('/') - return self._Ip2Long(addr), _Length2Mask(int(masklen)) + def _Length2Mask(length): + return 0xFFFFFFFF & ~((1 << (32 - length)) - 1) - def _GetHostAddresses(self, iface): - """Returns the IP addresses on host's interfaces, breaking out |iface|.""" - interface_list = self._EnumerateHostInterfaces() - addresses = [] - iface_address = None - found_iface = False - for line in interface_list: - if not line.startswith((' ', '\t')): - found_iface = iface in line - match = re.search(r'(?<=inet )\S+', line) - if match: - address = match.group(0) - if '/' in address: - address = self._IpPrefix2AddressMask(address) - else: - match = re.search(r'(?<=netmask )\S+', line) - address = self._Ip2Long(address), int(match.group(0), 16) - if found_iface: - assert not iface_address, ( - 'Found %s twice when parsing host interfaces.' % iface) - iface_address = address - else: - addresses.append(address) - return addresses, iface_address + addr, masklen = addr.split('/') + return self._Ip2Long(addr), _Length2Mask(int(masklen)) - def _GetDeviceAddresses(self, excluded_iface): - """Returns the IP addresses on all connected devices. + def _GetHostAddresses(self, iface): + """Returns the IP addresses on host's interfaces, breaking out |iface|.""" + interface_list = self._EnumerateHostInterfaces() + addresses = [] + iface_address = None + found_iface = False + for line in interface_list: + if not line.startswith((' ', '\t')): + found_iface = iface in line + match = re.search(r'(?<=inet )\S+', line) + if match: + address = match.group(0) + if '/' in address: + address = self._IpPrefix2AddressMask(address) + else: + match = re.search(r'(?<=netmask )\S+', line) + address = self._Ip2Long(address), int(match.group(0), 16) + if found_iface: + assert not iface_address, ( + 'Found %s twice when parsing host interfaces.' % iface) + iface_address = address + else: + addresses.append(address) + return addresses, iface_address + + def _GetDeviceAddresses(self, excluded_iface): + """Returns the IP addresses on all connected devices. Excludes interface |excluded_iface| on the selected device. """ - my_device = str(self._device) - addresses = [] - for device_serial in android_device.GetDeviceSerials(None): - try: - device = device_utils.DeviceUtils(device_serial) - if device_serial == my_device: - excluded = excluded_iface - else: - excluded = 'no interfaces excluded on other devices' - output = device.RunShellCommand( - ['ip', '-o', '-4', 'addr'], check_return=True) - addresses += [ - line.split()[3] for line in output if excluded not in line] - except device_errors.CommandFailedError: - logging.warning('Unable to determine IP addresses for %s', - device_serial) - return addresses + my_device = str(self._device) + addresses = [] + for device_serial in android_device.GetDeviceSerials(None): + try: + device = device_utils.DeviceUtils(device_serial) + if device_serial == my_device: + excluded = excluded_iface + else: + excluded = 'no interfaces excluded on other devices' + output = device.RunShellCommand(['ip', '-o', '-4', 'addr'], + check_return=True) + addresses += [ + line.split()[3] for line in output if excluded not in line + ] + except device_errors.CommandFailedError: + logging.warning('Unable to determine IP addresses for %s', + device_serial) + return addresses - def _ConfigureNetwork(self, device_iface, host_iface): - """Configures the |device_iface| to be on the same network as |host_iface|. + def _ConfigureNetwork(self, device_iface, host_iface): + """Configures the |device_iface| to be on the same network as |host_iface|. """ - def _Long2Ip(value): - return socket.inet_ntoa(struct.pack('!L', value)) - def _IsNetworkUnique(network, addresses): - return all((addr & mask != network & mask) for addr, mask in addresses) + def _Long2Ip(value): + return socket.inet_ntoa(struct.pack('!L', value)) - # pylint: disable=inconsistent-return-statements - def _NextUnusedAddress(network, netmask, used_addresses): - # Excludes '0' and broadcast. - for suffix in range(1, 0xFFFFFFFF & ~netmask): - candidate = network | suffix - if candidate not in used_addresses: - return candidate - # pylint: enable=inconsistent-return-statements + def _IsNetworkUnique(network, addresses): + return all( + (addr & mask != network & mask) for addr, mask in addresses) - def HasHostAddress(): - _, host_address = self._GetHostAddresses(host_iface) - return bool(host_address) + # pylint: disable=inconsistent-return-statements + def _NextUnusedAddress(network, netmask, used_addresses): + # Excludes '0' and broadcast. + for suffix in range(1, 0xFFFFFFFF & ~netmask): + candidate = network | suffix + if candidate not in used_addresses: + return candidate - if not HasHostAddress(): - if platform.GetHostPlatform().GetOSName() == 'mac': - if 'Telemetry' not in subprocess.check_output( - ['networksetup', '-listallnetworkservices'], encoding='utf8'): - subprocess.check_call( - ['/usr/bin/sudo', 'networksetup', - '-createnetworkservice', 'Telemetry', host_iface]) - subprocess.check_call( - ['/usr/bin/sudo', 'networksetup', - '-setmanual', 'Telemetry', '192.168.123.1', '255.255.255.0']) - elif platform.GetHostPlatform().GetOSName() == 'linux': - with open(self._NETWORK_INTERFACES) as f: - orig_interfaces = f.read() - if self._INTERFACES_INCLUDE not in orig_interfaces: - interfaces = '\n'.join([ - orig_interfaces, - '', - '# Added by Telemetry.', - self._INTERFACES_INCLUDE]) - self._WriteProtectedFile(self._NETWORK_INTERFACES, interfaces) - interface_conf_file = self._TELEMETRY_INTERFACE_FILE.format(host_iface) - if not os.path.exists(interface_conf_file): - interface_conf_dir = os.path.dirname(interface_conf_file) - if not os.path.exists(interface_conf_dir): - subprocess.call(['/usr/bin/sudo', '/bin/mkdir', interface_conf_dir]) - subprocess.call( - ['/usr/bin/sudo', '/bin/chmod', '755', interface_conf_dir]) - interface_conf = '\n'.join([ - '# Added by Telemetry for RNDIS forwarding.', - 'allow-hotplug %s' % host_iface, - 'iface %s inet static' % host_iface, - ' address 192.168.123.1', - ' netmask 255.255.255.0', - ]) - self._WriteProtectedFile(interface_conf_file, interface_conf) - subprocess.check_call(['/usr/bin/sudo', 'ifup', host_iface]) - logging.info('Waiting for RNDIS connectivity...') - py_utils.WaitFor(HasHostAddress, 30) + # pylint: enable=inconsistent-return-statements - addresses, host_address = self._GetHostAddresses(host_iface) - assert host_address, 'Interface %s could not be configured.' % host_iface + def HasHostAddress(): + _, host_address = self._GetHostAddresses(host_iface) + return bool(host_address) - host_ip, netmask = host_address # pylint: disable=unpacking-non-sequence - network = host_ip & netmask + if not HasHostAddress(): + if platform.GetHostPlatform().GetOSName() == 'mac': + if 'Telemetry' not in subprocess.check_output( + ['networksetup', '-listallnetworkservices'], + encoding='utf8'): + subprocess.check_call([ + '/usr/bin/sudo', 'networksetup', + '-createnetworkservice', 'Telemetry', host_iface + ]) + subprocess.check_call([ + '/usr/bin/sudo', 'networksetup', '-setmanual', + 'Telemetry', '192.168.123.1', '255.255.255.0' + ]) + elif platform.GetHostPlatform().GetOSName() == 'linux': + with open(self._NETWORK_INTERFACES) as f: + orig_interfaces = f.read() + if self._INTERFACES_INCLUDE not in orig_interfaces: + interfaces = '\n'.join([ + orig_interfaces, '', '# Added by Telemetry.', + self._INTERFACES_INCLUDE + ]) + self._WriteProtectedFile(self._NETWORK_INTERFACES, + interfaces) + interface_conf_file = self._TELEMETRY_INTERFACE_FILE.format( + host_iface) + if not os.path.exists(interface_conf_file): + interface_conf_dir = os.path.dirname(interface_conf_file) + if not os.path.exists(interface_conf_dir): + subprocess.call([ + '/usr/bin/sudo', '/bin/mkdir', interface_conf_dir + ]) + subprocess.call([ + '/usr/bin/sudo', '/bin/chmod', '755', + interface_conf_dir + ]) + interface_conf = '\n'.join([ + '# Added by Telemetry for RNDIS forwarding.', + 'allow-hotplug %s' % host_iface, + 'iface %s inet static' % host_iface, + ' address 192.168.123.1', + ' netmask 255.255.255.0', + ]) + self._WriteProtectedFile(interface_conf_file, + interface_conf) + subprocess.check_call( + ['/usr/bin/sudo', 'ifup', host_iface]) + logging.info('Waiting for RNDIS connectivity...') + py_utils.WaitFor(HasHostAddress, 30) - if not _IsNetworkUnique(network, addresses): - logging.warning( - 'The IP address configuration %s of %s is not unique!\n' - 'Check your /etc/network/interfaces. If this overlap is intended,\n' - 'you might need to use: ip rule add from <device_ip> lookup <table>\n' - 'or add the interface to a bridge in order to route to this network.', - host_address, host_iface - ) + addresses, host_address = self._GetHostAddresses(host_iface) + assert host_address, 'Interface %s could not be configured.' % host_iface - # Find unused IP address. - used_addresses = [addr for addr, _ in addresses] - used_addresses += [self._IpPrefix2AddressMask(addr)[0] - for addr in self._GetDeviceAddresses(device_iface)] - used_addresses += [host_ip] + host_ip, netmask = host_address # pylint: disable=unpacking-non-sequence + network = host_ip & netmask - device_ip = _NextUnusedAddress(network, netmask, used_addresses) - assert device_ip, ('The network %s on %s is full.' % - (host_address, host_iface)) + if not _IsNetworkUnique(network, addresses): + logging.warning( + 'The IP address configuration %s of %s is not unique!\n' + 'Check your /etc/network/interfaces. If this overlap is intended,\n' + 'you might need to use: ip rule add from <device_ip> lookup <table>\n' + 'or add the interface to a bridge in order to route to this network.', + host_address, host_iface) - host_ip = _Long2Ip(host_ip) - device_ip = _Long2Ip(device_ip) - netmask = _Long2Ip(netmask) + # Find unused IP address. + used_addresses = [addr for addr, _ in addresses] + used_addresses += [ + self._IpPrefix2AddressMask(addr)[0] + for addr in self._GetDeviceAddresses(device_iface) + ] + used_addresses += [host_ip] - # TODO(szym) run via su -c if necessary. - self._device.RunShellCommand( - ['ifconfig', device_iface, device_ip, 'netmask', netmask, 'up'], - check_return=True) - # Enabling the interface sometimes breaks adb. - self._device.WaitUntilFullyBooted() - self._host_iface = host_iface - self._host_ip = host_ip - self.device_iface = device_iface - self._device_ip = device_ip + device_ip = _NextUnusedAddress(network, netmask, used_addresses) + assert device_ip, ('The network %s on %s is full.' % + (host_address, host_iface)) - def _TestConnectivity(self): - with open(os.devnull, 'wb') as devnull: - return subprocess.call(['ping', '-q', '-c1', '-W1', self._device_ip], - stdout=devnull) == 0 + host_ip = _Long2Ip(host_ip) + device_ip = _Long2Ip(device_ip) + netmask = _Long2Ip(netmask) - def OverrideRoutingPolicy(self): - """Override any routing policy that could prevent + # TODO(szym) run via su -c if necessary. + self._device.RunShellCommand( + ['ifconfig', device_iface, device_ip, 'netmask', netmask, 'up'], + check_return=True) + # Enabling the interface sometimes breaks adb. + self._device.WaitUntilFullyBooted() + self._host_iface = host_iface + self._host_ip = host_ip + self.device_iface = device_iface + self._device_ip = device_ip + + def _TestConnectivity(self): + with open(os.devnull, 'wb') as devnull: + return subprocess.call( + ['ping', '-q', '-c1', '-W1', self._device_ip], + stdout=devnull) == 0 + + def OverrideRoutingPolicy(self): + """Override any routing policy that could prevent packets from reaching the rndis interface """ - policies = self._device.RunShellCommand(['ip', 'rule'], check_return=True) - if len(policies) > 1 and not 'lookup main' in policies[1]: - self._device.RunShellCommand( - ['ip', 'rule', 'add', 'prio', '1', 'from', 'all', 'table', 'main'], - check_return=True) - self._device.RunShellCommand( - ['ip', 'route', 'flush', 'cache'], check_return=True) + policies = self._device.RunShellCommand(['ip', 'rule'], + check_return=True) + if len(policies) > 1 and not 'lookup main' in policies[1]: + self._device.RunShellCommand([ + 'ip', 'rule', 'add', 'prio', '1', 'from', 'all', 'table', + 'main' + ], + check_return=True) + self._device.RunShellCommand(['ip', 'route', 'flush', 'cache'], + check_return=True) - def RestoreRoutingPolicy(self): - policies = self._device.RunShellCommand(['ip', 'rule'], check_return=True) - if len(policies) > 1 and re.match("^1:.*lookup main", policies[1]): - self._device.RunShellCommand( - ['ip', 'rule', 'del', 'prio', '1'], check_return=True) - self._device.RunShellCommand( - ['ip', 'route', 'flush', 'cache'], check_return=True) + def RestoreRoutingPolicy(self): + policies = self._device.RunShellCommand(['ip', 'rule'], + check_return=True) + if len(policies) > 1 and re.match("^1:.*lookup main", policies[1]): + self._device.RunShellCommand(['ip', 'rule', 'del', 'prio', '1'], + check_return=True) + self._device.RunShellCommand(['ip', 'route', 'flush', 'cache'], + check_return=True) - def _CheckConfigureNetwork(self): - """Enables RNDIS and configures it, retrying until we have connectivity.""" - force = False - for _ in range(3): - device_iface, host_iface = self._CheckEnableRndis(force) - self._ConfigureNetwork(device_iface, host_iface) - self.OverrideRoutingPolicy() - # Sometimes the first packet will wake up the connection. - for _ in range(3): - if self._TestConnectivity(): - return - force = True - self.RestoreRoutingPolicy() - raise Exception('No connectivity, giving up.') + def _CheckConfigureNetwork(self): + """Enables RNDIS and configures it, retrying until we have connectivity.""" + force = False + for _ in range(3): + device_iface, host_iface = self._CheckEnableRndis(force) + self._ConfigureNetwork(device_iface, host_iface) + self.OverrideRoutingPolicy() + # Sometimes the first packet will wake up the connection. + for _ in range(3): + if self._TestConnectivity(): + return + force = True + self.RestoreRoutingPolicy() + raise Exception('No connectivity, giving up.')
diff --git a/components/cronet/tools/api_static_checks.py b/components/cronet/tools/api_static_checks.py index 20c55be2..50e51d1 100755 --- a/components/cronet/tools/api_static_checks.py +++ b/components/cronet/tools/api_static_checks.py
@@ -2,10 +2,8 @@ # Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """api_static_checks.py - Enforce Cronet API requirements.""" - import argparse import os import re @@ -22,7 +20,6 @@ sys.path.insert(0, os.path.join(REPOSITORY_ROOT, 'components')) from cronet.tools import update_api # pylint: disable=wrong-import-position - # These regular expressions catch the beginning of lines that declare classes # and methods. The first group returned by a match is the class or method name. from cronet.tools.update_api import CLASS_RE # pylint: disable=wrong-import-position @@ -130,134 +127,143 @@ def find_api_calls(dump, api_classes, bad_calls): - # Given a dump of an implementation class, find calls through API classes. - # |dump| is the output of "javap -c" on the implementation class files. - # |api_classes| is the list of classes comprising the API. - # |bad_calls| is the list of calls through API classes. This list is built up - # by this function. + # Given a dump of an implementation class, find calls through API classes. + # |dump| is the output of "javap -c" on the implementation class files. + # |api_classes| is the list of classes comprising the API. + # |bad_calls| is the list of calls through API classes. This list is built up + # by this function. - for i, line in enumerate(dump): - try: - if CLASS_RE.match(line): - caller_class = CLASS_RE.match(line).group(2) - if METHOD_RE.match(line): - caller_method = METHOD_RE.match(line).group(1) - if line.startswith(': invoke', 8) and not line.startswith('dynamic', 16): - callee = line.split(' // ')[1].split('Method ')[1].split('\n')[0] - callee_class = callee.split('.')[0] - assert callee_class - if callee_class in api_classes: - callee_method = callee.split('.')[1] - assert callee_method - # Ignore constructor calls for now as every implementation class - # that extends an API class will call them. - # TODO(pauljensen): Look into enforcing restricting constructor calls. - # https://crbug.com/674975 - if callee_method.startswith('"<init>"'): - continue - bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method, - callee_class, callee_method) - if bad_call in ALLOWED_EXCEPTIONS: - continue - bad_calls += [bad_call] - except Exception: - sys.stderr.write(f'Failed on line {i+1}: {line}') - raise + for i, line in enumerate(dump): + try: + if CLASS_RE.match(line): + caller_class = CLASS_RE.match(line).group(2) + if METHOD_RE.match(line): + caller_method = METHOD_RE.match(line).group(1) + if line.startswith(': invoke', + 8) and not line.startswith('dynamic', 16): + callee = line.split(' // ')[1].split('Method ')[1].split( + '\n')[0] + callee_class = callee.split('.')[0] + assert callee_class + if callee_class in api_classes: + callee_method = callee.split('.')[1] + assert callee_method + # Ignore constructor calls for now as every implementation class + # that extends an API class will call them. + # TODO(pauljensen): Look into enforcing restricting constructor calls. + # https://crbug.com/674975 + if callee_method.startswith('"<init>"'): + continue + bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method, + callee_class, callee_method) + if bad_call in ALLOWED_EXCEPTIONS: + continue + bad_calls += [bad_call] + except Exception: + sys.stderr.write(f'Failed on line {i+1}: {line}') + raise def check_api_calls(opts): - # Returns True if no calls through API classes in implementation. + # Returns True if no calls through API classes in implementation. - temp_dir = tempfile.mkdtemp() + temp_dir = tempfile.mkdtemp() - # Extract API class files from jar - jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf', - os.path.abspath(opts.api_jar)] - build_utils.CheckOutput(jar_cmd, cwd=temp_dir) - shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) - - # Collect names of API classes - api_classes = [] - for dirpath, _, filenames in os.walk(temp_dir): - if not filenames: - continue - package = os.path.relpath(dirpath, temp_dir) - for filename in filenames: - if filename.endswith('.class'): - classname = filename[:-len('.class')] - api_classes += [os.path.normpath(os.path.join(package, classname))] - - shutil.rmtree(temp_dir) - temp_dir = tempfile.mkdtemp() - - # Extract impl class files from jars - for impl_jar in opts.impl_jar: - jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf', - os.path.abspath(impl_jar)] + # Extract API class files from jar + jar_cmd = [ + os.path.relpath(JAR_PATH, temp_dir), 'xf', + os.path.abspath(opts.api_jar) + ] build_utils.CheckOutput(jar_cmd, cwd=temp_dir) - shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) + shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) - # Process classes - bad_api_calls = [] - for dirpath, _, filenames in os.walk(temp_dir): - if not filenames: - continue - # Dump classes - dump_file = os.path.join(temp_dir, 'dump.txt') - javap_cmd = '%s -private -c %s > %s' % (JAVAP_PATH, ' '.join( - os.path.join(dirpath, f) - for f in filenames).replace('$', '\\$'), dump_file) - if os.system(javap_cmd): - print('ERROR: javap failed on ' + ' '.join(filenames)) - return False - # Process class dump - with open(dump_file, 'r') as dump: - find_api_calls(dump, api_classes, bad_api_calls) + # Collect names of API classes + api_classes = [] + for dirpath, _, filenames in os.walk(temp_dir): + if not filenames: + continue + package = os.path.relpath(dirpath, temp_dir) + for filename in filenames: + if filename.endswith('.class'): + classname = filename[:-len('.class')] + api_classes += [ + os.path.normpath(os.path.join(package, classname)) + ] - shutil.rmtree(temp_dir) + shutil.rmtree(temp_dir) + temp_dir = tempfile.mkdtemp() - if bad_api_calls: - print('ERROR: Found the following calls from implementation classes ' - 'through') - print(' API classes. These could fail if older API is used that') - print(' does not contain newer methods. Please call through a') - print(' wrapper class from VersionSafeCallbacks.') - print('\n'.join(bad_api_calls)) - return not bad_api_calls + # Extract impl class files from jars + for impl_jar in opts.impl_jar: + jar_cmd = [ + os.path.relpath(JAR_PATH, temp_dir), 'xf', + os.path.abspath(impl_jar) + ] + build_utils.CheckOutput(jar_cmd, cwd=temp_dir) + shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) + + # Process classes + bad_api_calls = [] + for dirpath, _, filenames in os.walk(temp_dir): + if not filenames: + continue + # Dump classes + dump_file = os.path.join(temp_dir, 'dump.txt') + javap_cmd = '%s -private -c %s > %s' % (JAVAP_PATH, ' '.join( + os.path.join(dirpath, f) + for f in filenames).replace('$', '\\$'), dump_file) + if os.system(javap_cmd): + print('ERROR: javap failed on ' + ' '.join(filenames)) + return False + # Process class dump + with open(dump_file, 'r') as dump: + find_api_calls(dump, api_classes, bad_api_calls) + + shutil.rmtree(temp_dir) + + if bad_api_calls: + print('ERROR: Found the following calls from implementation classes ' + 'through') + print( + ' API classes. These could fail if older API is used that') + print(' does not contain newer methods. Please call through a') + print(' wrapper class from VersionSafeCallbacks.') + print('\n'.join(bad_api_calls)) + return not bad_api_calls def check_api_version(opts): - if update_api.check_up_to_date(opts.api_jar): - return True - print('ERROR: API file out of date. Please run this command:') - print(' components/cronet/tools/update_api.py --api_jar %s' % - (os.path.abspath(opts.api_jar))) - return False + if update_api.check_up_to_date(opts.api_jar): + return True + print('ERROR: API file out of date. Please run this command:') + print(' components/cronet/tools/update_api.py --api_jar %s' % + (os.path.abspath(opts.api_jar))) + return False def main(args): - parser = argparse.ArgumentParser( - description='Enforce Cronet API requirements.') - parser.add_argument('--api_jar', - help='Path to API jar (i.e. cronet_api.jar)', - required=True, - metavar='path/to/cronet_api.jar') - parser.add_argument('--impl_jar', - help='Path to implementation jar ' - '(i.e. cronet_impl_native_java.jar)', - required=True, - metavar='path/to/cronet_impl_native_java.jar', - action='append') - parser.add_argument('--stamp', help='Path to touch on success.') - opts = parser.parse_args(args) + parser = argparse.ArgumentParser( + description='Enforce Cronet API requirements.') + parser.add_argument('--api_jar', + help='Path to API jar (i.e. cronet_api.jar)', + required=True, + metavar='path/to/cronet_api.jar') + parser.add_argument('--impl_jar', + help='Path to implementation jar ' + '(i.e. cronet_impl_native_java.jar)', + required=True, + metavar='path/to/cronet_impl_native_java.jar', + action='append') + parser.add_argument('--stamp', help='Path to touch on success.') + opts = parser.parse_args(args) - ret = True - ret = check_api_calls(opts) and ret - ret = check_api_version(opts) and ret - if ret and opts.stamp: - build_utils.Touch(opts.stamp) - return ret + ret = True + ret = check_api_calls(opts) and ret + ret = check_api_version(opts) and ret + if ret and opts.stamp: + build_utils.Touch(opts.stamp) + return ret if __name__ == '__main__': - sys.exit(0 if main(sys.argv[1:]) else -1) + sys.exit(0 if main(sys.argv[1:]) else -1)
diff --git a/components/cronet/tools/api_static_checks_unittest.py b/components/cronet/tools/api_static_checks_unittest.py index 5e9445d..b6dc706 100755 --- a/components/cronet/tools/api_static_checks_unittest.py +++ b/components/cronet/tools/api_static_checks_unittest.py
@@ -52,127 +52,127 @@ @contextlib.contextmanager def capture_output(): - # A contextmanger that collects the stdout and stderr of wrapped code + # A contextmanger that collects the stdout and stderr of wrapped code - oldout, olderr = sys.stdout, sys.stderr - try: - out = [io.StringIO(), io.StringIO()] - sys.stdout, sys.stderr = out - yield out - finally: - sys.stdout, sys.stderr = oldout, olderr - out[0] = out[0].getvalue() - out[1] = out[1].getvalue() + oldout, olderr = sys.stdout, sys.stderr + try: + out = [io.StringIO(), io.StringIO()] + sys.stdout, sys.stderr = out + yield out + finally: + sys.stdout, sys.stderr = oldout, olderr + out[0] = out[0].getvalue() + out[1] = out[1].getvalue() class ApiStaticCheckUnitTest(unittest.TestCase): - def setUp(self): - self.exe_path = os.path.join(REPOSITORY_ROOT, 'out') - self.temp_dir = tempfile.mkdtemp(dir=self.exe_path) - os.chdir(self.temp_dir) - os.mkdir('android') - with open(API_VERSION_FILENAME, 'w') as api_version_file: - api_version_file.write('0') - with open(API_FILENAME, 'w') as api_file: - api_file.write('}\nStamp: 7d9d25f71cb8a5aba86202540a20d405\n') - shutil.copytree(os.path.dirname(__file__), 'tools') + def setUp(self): + self.exe_path = os.path.join(REPOSITORY_ROOT, 'out') + self.temp_dir = tempfile.mkdtemp(dir=self.exe_path) + os.chdir(self.temp_dir) + os.mkdir('android') + with open(API_VERSION_FILENAME, 'w') as api_version_file: + api_version_file.write('0') + with open(API_FILENAME, 'w') as api_file: + api_file.write('}\nStamp: 7d9d25f71cb8a5aba86202540a20d405\n') + shutil.copytree(os.path.dirname(__file__), 'tools') - def tearDown(self): - shutil.rmtree(self.temp_dir) + def tearDown(self): + shutil.rmtree(self.temp_dir) - def make_jar(self, java, class_name): - # Compile |java| wrapped in a class named |class_name| to a jar file and - # return jar filename. + def make_jar(self, java, class_name): + # Compile |java| wrapped in a class named |class_name| to a jar file and + # return jar filename. - java_filename = class_name + '.java' - class_filenames = class_name + '*.class' - jar_filename = class_name + '.jar' + java_filename = class_name + '.java' + class_filenames = class_name + '*.class' + jar_filename = class_name + '.jar' - with open(java_filename, 'w') as java_file: - java_file.write('public class %s {' % class_name) - java_file.write(java) - java_file.write('}') - os.system(f'{os.path.abspath(JAVAC_PATH)} {java_filename}') - os.system( - f'{os.path.abspath(JAR_PATH)} cf {jar_filename} {class_filenames}') - return jar_filename + with open(java_filename, 'w') as java_file: + java_file.write('public class %s {' % class_name) + java_file.write(java) + java_file.write('}') + os.system(f'{os.path.abspath(JAVAC_PATH)} {java_filename}') + os.system( + f'{os.path.abspath(JAR_PATH)} cf {jar_filename} {class_filenames}') + return jar_filename - def run_check_api_calls(self, api_java, impl_java): - test = self + def run_check_api_calls(self, api_java, impl_java): + test = self - class MockOpts(object): + class MockOpts(object): - def __init__(self): - self.api_jar = test.make_jar(api_java, 'Api') - self.impl_jar = [test.make_jar(impl_java, 'Impl')] + def __init__(self): + self.api_jar = test.make_jar(api_java, 'Api') + self.impl_jar = [test.make_jar(impl_java, 'Impl')] - opts = MockOpts() - with capture_output() as return_output: - return_code = api_static_checks.check_api_calls(opts) - return [return_code, return_output[0]] + opts = MockOpts() + with capture_output() as return_output: + return_code = api_static_checks.check_api_calls(opts) + return [return_code, return_output[0]] - def test_check_api_calls_success(self): - # Test simple classes with functions - self.assertEqual(self.run_check_api_calls('void a(){}', 'void b(){}'), - [True, '']) - # Test simple classes with functions calling themselves - self.assertEqual( - self.run_check_api_calls('void a(){} void b(){a();}', - 'void c(){} void d(){c();}'), [True, '']) + def test_check_api_calls_success(self): + # Test simple classes with functions + self.assertEqual(self.run_check_api_calls('void a(){}', 'void b(){}'), + [True, '']) + # Test simple classes with functions calling themselves + self.assertEqual( + self.run_check_api_calls('void a(){} void b(){a();}', + 'void c(){} void d(){c();}'), [True, '']) - def test_check_api_calls_failure(self): - # Test static call - self.assertEqual( - self.run_check_api_calls('public static void a(){}', - 'void b(){Api.a();}'), - [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) - # Test virtual call - self.assertEqual( - self.run_check_api_calls('public void a(){}', - 'void b(){new Api().a();}'), - [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) + def test_check_api_calls_failure(self): + # Test static call + self.assertEqual( + self.run_check_api_calls('public static void a(){}', + 'void b(){Api.a();}'), + [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) + # Test virtual call + self.assertEqual( + self.run_check_api_calls('public void a(){}', + 'void b(){new Api().a();}'), + [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) - def run_check_api_version(self, java): - OUT_FILENAME = 'out.txt' - return_code = os.system('./tools/update_api.py --api_jar %s > %s' % - (self.make_jar(java, 'Api'), OUT_FILENAME)) - with open(API_FILENAME, 'r') as api_file: - api = api_file.read() - with open(API_VERSION_FILENAME, 'r') as api_version_file: - api_version = api_version_file.read() - with open(OUT_FILENAME, 'r') as out_file: - output = out_file.read() + def run_check_api_version(self, java): + OUT_FILENAME = 'out.txt' + return_code = os.system('./tools/update_api.py --api_jar %s > %s' % + (self.make_jar(java, 'Api'), OUT_FILENAME)) + with open(API_FILENAME, 'r') as api_file: + api = api_file.read() + with open(API_VERSION_FILENAME, 'r') as api_version_file: + api_version = api_version_file.read() + with open(OUT_FILENAME, 'r') as out_file: + output = out_file.read() - # Verify stamp - api_stamp = api.split('\n')[-2] - stamp_length = len('Stamp: 78418460c193047980ae9eabb79293f2\n') - api = api[:-stamp_length] - api_hash = hashlib.md5() - api_hash.update(api.encode('utf-8')) - self.assertEqual(api_stamp, 'Stamp: %s' % api_hash.hexdigest()) + # Verify stamp + api_stamp = api.split('\n')[-2] + stamp_length = len('Stamp: 78418460c193047980ae9eabb79293f2\n') + api = api[:-stamp_length] + api_hash = hashlib.md5() + api_hash.update(api.encode('utf-8')) + self.assertEqual(api_stamp, 'Stamp: %s' % api_hash.hexdigest()) - return [return_code == 0, output, api, api_version] + return [return_code == 0, output, api, api_version] - def test_split_by_class_sort(self): - expected = [ - [ - 'public class Api {', - 'public Api();', - 'public void a();', - 'public void b();', - '}', - ], - [ - 'public class zee {', - 'public abstract int z();', - 'public void x();', - 'public void y();', - 'public zee();', - '}', - ], - ] - input_str = """Compiled from Api.java + def test_split_by_class_sort(self): + expected = [ + [ + 'public class Api {', + 'public Api();', + 'public void a();', + 'public void b();', + '}', + ], + [ + 'public class zee {', + 'public abstract int z();', + 'public void x();', + 'public void y();', + 'public zee();', + '}', + ], + ] + input_str = """Compiled from Api.java public class Api { public void b(); public Api(); @@ -186,50 +186,53 @@ public abstract int z(); } """ - self.assertEqual(update_api._split_by_class(input_str.splitlines()), - expected) + self.assertEqual(update_api._split_by_class(input_str.splitlines()), + expected) - def test_update_api_success(self): - # Test simple new API - self.assertEqual(self.run_check_api_version('public void a(){}'), [ - True, '', CHECK_API_VERSION_PREFIX + """public class Api { - public Api(); - public void a(); -} -""", '1' - ]) - # Test version number not increased when API not changed - self.assertEqual(self.run_check_api_version('public void a(){}'), [ - True, '', CHECK_API_VERSION_PREFIX + """public class Api { - public Api(); - public void a(); -} -""", '1' - ]) - # Test acceptable API method addition - self.assertEqual( - self.run_check_api_version('public void a(){} public void b(){}'), [ + def test_update_api_success(self): + # Test simple new API + self.assertEqual(self.run_check_api_version('public void a(){}'), [ True, '', CHECK_API_VERSION_PREFIX + """public class Api { public Api(); public void a(); +} +""", '1' + ]) + # Test version number not increased when API not changed + self.assertEqual(self.run_check_api_version('public void a(){}'), [ + True, '', CHECK_API_VERSION_PREFIX + """public class Api { + public Api(); + public void a(); +} +""", '1' + ]) + # Test acceptable API method addition + self.assertEqual( + self.run_check_api_version('public void a(){} public void b(){}'), + [ + True, '', CHECK_API_VERSION_PREFIX + """public class Api { + public Api(); + public void a(); public void b(); } """, '2' - ]) - # Test version number not increased when API not changed - self.assertEqual( - self.run_check_api_version('public void a(){} public void b(){}'), [ - True, '', CHECK_API_VERSION_PREFIX + """public class Api { + ]) + # Test version number not increased when API not changed + self.assertEqual( + self.run_check_api_version('public void a(){} public void b(){}'), + [ + True, '', CHECK_API_VERSION_PREFIX + """public class Api { public Api(); public void a(); public void b(); } """, '2' - ]) - # Test acceptable API class addition - self.assertEqual( - self.run_check_api_version( - 'public void a(){} public void b(){} public class C {}'), [ + ]) + # Test acceptable API class addition + self.assertEqual( + self.run_check_api_version( + 'public void a(){} public void b(){} public class C {}'), + [ True, '', CHECK_API_VERSION_PREFIX + """public class Api$C { public Api$C(Api); } @@ -240,10 +243,11 @@ } """, '3' ]) - # Test version number not increased when API not changed - self.assertEqual( - self.run_check_api_version( - 'public void a(){} public void b(){} public class C {}'), [ + # Test version number not increased when API not changed + self.assertEqual( + self.run_check_api_version( + 'public void a(){} public void b(){} public class C {}'), + [ True, '', CHECK_API_VERSION_PREFIX + """public class Api$C { public Api$C(Api); } @@ -255,32 +259,33 @@ """, '3' ]) - def test_update_api_failure(self): - # Create a simple new API - self.assertEqual(self.run_check_api_version('public void a(){}'), [ - True, '', CHECK_API_VERSION_PREFIX + """public class Api { + def test_update_api_failure(self): + # Create a simple new API + self.assertEqual(self.run_check_api_version('public void a(){}'), [ + True, '', CHECK_API_VERSION_PREFIX + """public class Api { public Api(); public void a(); } """, '1' - ]) - # Test removing API method not allowed - self.assertEqual(self.run_check_api_version(''), [ - False, - ERROR_PREFIX_UPDATE_API + 'public void a();' + ERROR_SUFFIX_UPDATE_API, - CHECK_API_VERSION_PREFIX + """public class Api { + ]) + # Test removing API method not allowed + self.assertEqual(self.run_check_api_version(''), [ + False, ERROR_PREFIX_UPDATE_API + 'public void a();' + + ERROR_SUFFIX_UPDATE_API, + CHECK_API_VERSION_PREFIX + """public class Api { public Api(); public void a(); } """, '1' - ]) - # Test modifying API method not allowed - self.assertEqual(self.run_check_api_version('public void a(int x){}'), [ - False, - ERROR_PREFIX_UPDATE_API + 'public void a();' + ERROR_SUFFIX_UPDATE_API, - CHECK_API_VERSION_PREFIX + """public class Api { + ]) + # Test modifying API method not allowed + self.assertEqual(self.run_check_api_version('public void a(int x){}'), + [ + False, ERROR_PREFIX_UPDATE_API + + 'public void a();' + ERROR_SUFFIX_UPDATE_API, + CHECK_API_VERSION_PREFIX + """public class Api { public Api(); public void a(); } """, '1' - ]) + ])
diff --git a/components/cronet/tools/breakages_constants.py b/components/cronet/tools/breakages_constants.py index df339b72..451d284 100644 --- a/components/cronet/tools/breakages_constants.py +++ b/components/cronet/tools/breakages_constants.py
@@ -4,4 +4,4 @@ GOOD_CHANGE_IDS_TXT = 'good_change_ids' BAD_CHANGE_ID_TXT = 'bad_change_id' BUG_TXT = 'bugs' -COMMENT_TXT = 'comment' \ No newline at end of file +COMMENT_TXT = 'comment'
diff --git a/components/cronet/tools/breakages_unittest.py b/components/cronet/tools/breakages_unittest.py index bb8b335..c6ad592 100644 --- a/components/cronet/tools/breakages_unittest.py +++ b/components/cronet/tools/breakages_unittest.py
@@ -22,103 +22,108 @@ class TestBreakagesJson(unittest.TestCase): - parsed_breakages = [] + parsed_breakages = [] - @classmethod - def createFromBreakagesJson(cls): - """Loads the JSON file once before tests run.""" - if not os.path.exists(FILE_PATH): - raise FileNotFoundError( - f"CRITICAL: The file '{FILE_PATH}' could not be found.") + @classmethod + def createFromBreakagesJson(cls): + """Loads the JSON file once before tests run.""" + if not os.path.exists(FILE_PATH): + raise FileNotFoundError( + f"CRITICAL: The file '{FILE_PATH}' could not be found.") - content = json.loads(cronet_utils.read_file(FILE_PATH)) - if "breakages" not in content: - raise ValueError("CRITICAL: Root of JSON must contain a 'breakages' key.") + content = json.loads(cronet_utils.read_file(FILE_PATH)) + if "breakages" not in content: + raise ValueError( + "CRITICAL: Root of JSON must contain a 'breakages' key.") - if not isinstance(content["breakages"], list): - raise ValueError("CRITICAL: 'breakages' key must contain a list.") + if not isinstance(content["breakages"], list): + raise ValueError("CRITICAL: 'breakages' key must contain a list.") - cls.parsed_breakages = content["breakages"] + cls.parsed_breakages = content["breakages"] - def _is_valid_change_id(self, change_id): - if not isinstance(change_id, str): - return False - pattern = r'^I[0-9a-fA-F]{40}$' - zeros_pattern = r'^I00*$' - return bool( - re.fullmatch(pattern, change_id) - and not re.fullmatch(zeros_pattern, change_id)) + def _is_valid_change_id(self, change_id): + if not isinstance(change_id, str): + return False + pattern = r'^I[0-9a-fA-F]{40}$' + zeros_pattern = r'^I00*$' + return bool( + re.fullmatch(pattern, change_id) + and not re.fullmatch(zeros_pattern, change_id)) - def _format_error(self, entry, message): - entry_str = json.dumps(entry, indent=2) - return f"\n{message}\n\nFailing Entry:\n{entry_str}\n" + def _format_error(self, entry, message): + entry_str = json.dumps(entry, indent=2) + return f"\n{message}\n\nFailing Entry:\n{entry_str}\n" - def test_id_validation_logic(self): - self.assertTrue(self._is_valid_change_id("I" + "a" * 40)) - self.assertFalse(self._is_valid_change_id("I" + "a" * 39)) # Too short - self.assertFalse(self._is_valid_change_id("a" * 40)) # Missing 'I' - self.assertFalse(self._is_valid_change_id("I" + "z" * 40)) # Non-hex - self.assertFalse(self._is_valid_change_id("I" + "0" * 40)) # All zeros + def test_id_validation_logic(self): + self.assertTrue(self._is_valid_change_id("I" + "a" * 40)) + self.assertFalse(self._is_valid_change_id("I" + "a" * 39)) # Too short + self.assertFalse(self._is_valid_change_id("a" * 40)) # Missing 'I' + self.assertFalse(self._is_valid_change_id("I" + "z" * 40)) # Non-hex + self.assertFalse(self._is_valid_change_id("I" + "0" * 40)) # All zeros - def test_missing_mandatory_keys(self): - required_keys = [ - breakages_constants.BUG_TXT, breakages_constants.BAD_CHANGE_ID_TXT, - breakages_constants.GOOD_CHANGE_IDS_TXT, breakages_constants.COMMENT_TXT - ] + def test_missing_mandatory_keys(self): + required_keys = [ + breakages_constants.BUG_TXT, breakages_constants.BAD_CHANGE_ID_TXT, + breakages_constants.GOOD_CHANGE_IDS_TXT, + breakages_constants.COMMENT_TXT + ] - for entry in self.parsed_breakages: - for key in required_keys: - self.assertIn( - key, entry, - self._format_error(entry, f"Missing mandatory key: '{key}'")) + for entry in self.parsed_breakages: + for key in required_keys: + self.assertIn( + key, entry, + self._format_error(entry, + f"Missing mandatory key: '{key}'")) - def test_unknown_keys(self): - allowed_keys = { - breakages_constants.GOOD_CHANGE_IDS_TXT, - breakages_constants.BAD_CHANGE_ID_TXT, breakages_constants.BUG_TXT, - breakages_constants.COMMENT_TXT - } + def test_unknown_keys(self): + allowed_keys = { + breakages_constants.GOOD_CHANGE_IDS_TXT, + breakages_constants.BAD_CHANGE_ID_TXT, breakages_constants.BUG_TXT, + breakages_constants.COMMENT_TXT + } - for entry in self.parsed_breakages: - for key in entry: - if key.startswith('_'): - continue # comments are allowed - self.assertIn( - key, allowed_keys, - self._format_error( - entry, f"Unknown key found: '{key}'. Allowed: {allowed_keys}")) + for entry in self.parsed_breakages: + for key in entry: + if key.startswith('_'): + continue # comments are allowed + self.assertIn( + key, allowed_keys, + self._format_error( + entry, + f"Unknown key found: '{key}'. Allowed: {allowed_keys}") + ) - def test_bad_change_id_format_in_file(self): - """Test that 'bad_change_id' entries in the file follow the regex.""" - for entry in self.parsed_breakages: - val = entry[breakages_constants.BAD_CHANGE_ID_TXT] - self.assertTrue( - self._is_valid_change_id(val), - self._format_error( - entry, - f"Invalid '{breakages_constants.BAD_CHANGE_ID_TXT}' format: '{val}'" - )) + def test_bad_change_id_format_in_file(self): + """Test that 'bad_change_id' entries in the file follow the regex.""" + for entry in self.parsed_breakages: + val = entry[breakages_constants.BAD_CHANGE_ID_TXT] + self.assertTrue( + self._is_valid_change_id(val), + self._format_error( + entry, + f"Invalid '{breakages_constants.BAD_CHANGE_ID_TXT}' format: '{val}'" + )) - def test_good_change_ids_is_list(self): - for entry in self.parsed_breakages: - val = entry[breakages_constants.GOOD_CHANGE_IDS_TXT] - self.assertIsInstance( - val, list, - self._format_error( - entry, - f"'{breakages_constants.GOOD_CHANGE_IDS_TXT}' must be a list. Found {type(val)}" - )) + def test_good_change_ids_is_list(self): + for entry in self.parsed_breakages: + val = entry[breakages_constants.GOOD_CHANGE_IDS_TXT] + self.assertIsInstance( + val, list, + self._format_error( + entry, + f"'{breakages_constants.GOOD_CHANGE_IDS_TXT}' must be a list. Found {type(val)}" + )) - def test_good_change_ids_format_in_file(self): - for entry in self.parsed_breakages: - for val in entry[breakages_constants.GOOD_CHANGE_IDS_TXT]: - self.assertTrue( - self._is_valid_change_id(val), - self._format_error( - entry, - f"Invalid ID in '{breakages_constants.GOOD_CHANGE_IDS_TXT}': '{val}'" - )) + def test_good_change_ids_format_in_file(self): + for entry in self.parsed_breakages: + for val in entry[breakages_constants.GOOD_CHANGE_IDS_TXT]: + self.assertTrue( + self._is_valid_change_id(val), + self._format_error( + entry, + f"Invalid ID in '{breakages_constants.GOOD_CHANGE_IDS_TXT}': '{val}'" + )) if __name__ == '__main__': - unittest.main() + unittest.main()
diff --git a/components/cronet/tools/check_combined_proguard_file.py b/components/cronet/tools/check_combined_proguard_file.py index 6a08ee4e..8d558b6f 100755 --- a/components/cronet/tools/check_combined_proguard_file.py +++ b/components/cronet/tools/check_combined_proguard_file.py
@@ -19,41 +19,42 @@ # files. _REBASELINE_PROGUARD = os.environ.get('REBASELINE_PROGUARD', '0') != '0' + def CompareGeneratedWithGolden(generated_file_path, golden_file_path): - golden_text = cronet_utils.read_file(golden_file_path) - generated_text = cronet_utils.read_file(generated_file_path) - if _REBASELINE_PROGUARD: - if golden_text != generated_text: - print('Updated', golden_file_path) - with open(golden_file_path, 'w') as f: - f.write(generated_text) - return None + golden_text = cronet_utils.read_file(golden_file_path) + generated_text = cronet_utils.read_file(generated_file_path) + if _REBASELINE_PROGUARD: + if golden_text != generated_text: + print('Updated', golden_file_path) + with open(golden_file_path, 'w') as f: + f.write(generated_text) + return None - if golden_text is None: - print(f'Golden file does not exist: {golden_file_path}') + if golden_text is None: + print(f'Golden file does not exist: {golden_file_path}') - return cronet_utils.compare_text_and_generate_diff(generated_text, - golden_text, - golden_file_path) + return cronet_utils.compare_text_and_generate_diff(generated_text, + golden_text, + golden_file_path) def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--input_generated_file', - type=str, - help="Path to the generated file.") - parser.add_argument('--input_golden_file', - type=str, - help='Path to the input golden file.') - parser.add_argument('--target_name', - help='Target name that generates the golden file.') - parser.add_argument('--stamp', type=str, help='Path to touch on success') - args = parser.parse_args() - text_diff = CompareGeneratedWithGolden(args.input_generated_file, - args.input_golden_file) - if text_diff: + parser = argparse.ArgumentParser() + parser.add_argument('--input_generated_file', + type=str, + help="Path to the generated file.") + parser.add_argument('--input_golden_file', + type=str, + help='Path to the input golden file.') + parser.add_argument('--target_name', + help='Target name that generates the golden file.') + parser.add_argument('--stamp', type=str, help='Path to touch on success') + args = parser.parse_args() + text_diff = CompareGeneratedWithGolden(args.input_generated_file, + args.input_golden_file) + if text_diff: - print(f""" + print(f""" Cronet Proguard golden test failed. To generate it: ####################################################### # # @@ -80,9 +81,10 @@ This will generate a GN output directory with the appropriate GN\ flags to run the golden_test in generation mode rather than verification mode. """) - sys.exit(-1) - else: - build_utils.Touch(args.stamp) + sys.exit(-1) + else: + build_utils.Touch(args.stamp) + if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/check_cronet_dependencies.py b/components/cronet/tools/check_cronet_dependencies.py index 01c6d9d2..68b68b6 100755 --- a/components/cronet/tools/check_cronet_dependencies.py +++ b/components/cronet/tools/check_cronet_dependencies.py
@@ -25,13 +25,14 @@ def _get_current_gn_args() -> List[str]: - """Returns the GN args in the current working directory""" - args = subprocess.check_output(["cat", "args.gn"]).decode('utf-8').split("\n") - return [arg for arg in args if arg and not arg.startswith("#")] + """Returns the GN args in the current working directory""" + args = subprocess.check_output(["cat", + "args.gn"]).decode('utf-8').split("\n") + return [arg for arg in args if arg and not arg.startswith("#")] def normalize_third_party_dep(dependency: str) -> str: - """Normalizes a GN label that includes `third_party` string + """Normalizes a GN label that includes `third_party` string Required because Chromium allows multiple libraries to live under the same third_party directory (eg: `third_party/android_deps` contains @@ -56,34 +57,34 @@ Returns: The normalized third_party path. """ - if _THIRD_PARTY_STR not in dependency: - raise ValueError('Dependency is not a third_party dependency') + if _THIRD_PARTY_STR not in dependency: + raise ValueError('Dependency is not a third_party dependency') - root_to_keep = _THIRD_PARTY_STR - if _THIRD_PARTY_RUST_STR in dependency: - root_to_keep = _THIRD_PARTY_RUST_STR + root_to_keep = _THIRD_PARTY_STR + if _THIRD_PARTY_RUST_STR in dependency: + root_to_keep = _THIRD_PARTY_RUST_STR - root_end_index = dependency.rfind(root_to_keep) + len(root_to_keep) - dependency_name_end_index = dependency.find("/", root_end_index) - if dependency_name_end_index == -1: - return dependency - return dependency[:dependency_name_end_index] + root_end_index = dependency.rfind(root_to_keep) + len(root_to_keep) + dependency_name_end_index = dependency.find("/", root_end_index) + if dependency_name_end_index == -1: + return dependency + return dependency[:dependency_name_end_index] def _get_transitive_deps_from_root_targets(out_dir: str, gn_targets: List[str]) -> Set[str]: - """Executes gn desc |out_dir| |gn_target| deps --all for each gn target""" - all_deps = set() - for gn_target in gn_targets: - all_deps.update( - subprocess.check_output( - [_GN_PATH, "desc", out_dir, gn_target, "deps", - "--all"]).decode("utf-8").split("\n")) - return all_deps + """Executes gn desc |out_dir| |gn_target| deps --all for each gn target""" + all_deps = set() + for gn_target in gn_targets: + all_deps.update( + subprocess.check_output( + [_GN_PATH, "desc", out_dir, gn_target, "deps", + "--all"]).decode("utf-8").split("\n")) + return all_deps def normalize_and_dedup_deps(deps: Set[str]) -> Set[str]: - """Deduplicate after normalizing third_party dependencies + """Deduplicate after normalizing third_party dependencies This process involve the following steps: @@ -102,88 +103,89 @@ Returns: A sorted collection of normalized deps. """ - cleaned_deps = set() - for dep in deps: - if not dep: - # Ignore empty lines. - continue + cleaned_deps = set() + for dep in deps: + if not dep: + # Ignore empty lines. + continue - if dep.startswith("//build/modules/android-"): - # There is one dependencies.txt shared between all cpu architectures. - # On arm64, this would output //build/modules/android-arm64 - # On x64, this would output //build/modules/android-x64 - # It's impossible to normalize this because you can have platforms without - # modules enabled at all, which don't output anything. So we skip it. - continue + if dep.startswith("//build/modules/android-"): + # There is one dependencies.txt shared between all cpu architectures. + # On arm64, this would output //build/modules/android-arm64 + # On x64, this would output //build/modules/android-x64 + # It's impossible to normalize this because you can have platforms without + # modules enabled at all, which don't output anything. So we skip it. + continue - if dep.startswith("//third_party/androidx:") and dep.endswith("_java"): - # We treat androidx dependency differently because - # Cronet MUST NOT depend on any androidx dependency except - # androidx_annotations which is compile-time only. This is - # needed because this is one of mainline restrictions. - # Java/Android targets in GN must end with _java, this is needed - # so we don't bloat the dependencies file with auto-generated targets. - # (eg: androidx_annotation_annotation_java__assetres) + if dep.startswith("//third_party/androidx:") and dep.endswith("_java"): + # We treat androidx dependency differently because + # Cronet MUST NOT depend on any androidx dependency except + # androidx_annotations which is compile-time only. This is + # needed because this is one of mainline restrictions. + # Java/Android targets in GN must end with _java, this is needed + # so we don't bloat the dependencies file with auto-generated targets. + # (eg: androidx_annotation_annotation_java__assetres) - # Don't do any cleaning, add the exact GN label to the dependencies. - cleaned_deps.add(dep) - else: - dep = cronet_utils.get_path_from_gn_label(dep) - if _THIRD_PARTY_STR in dep: - cleaned_deps.add(normalize_third_party_dep(dep)) - else: - cleaned_deps.add(dep) - return sorted(cleaned_deps) + # Don't do any cleaning, add the exact GN label to the dependencies. + cleaned_deps.add(dep) + else: + dep = cronet_utils.get_path_from_gn_label(dep) + if _THIRD_PARTY_STR in dep: + cleaned_deps.add(normalize_third_party_dep(dep)) + else: + cleaned_deps.add(dep) + return sorted(cleaned_deps) def main(): - parser = argparse.ArgumentParser( - prog='Check cronet dependencies', - description= - "Checks whether Cronet's current dependencies match the known ones.") - parser.add_argument( - '--root-deps', - nargs="+", - help="""Those are the root dependencies which the script will + parser = argparse.ArgumentParser( + prog='Check cronet dependencies', + description= + "Checks whether Cronet's current dependencies match the known ones.") + parser.add_argument( + '--root-deps', + nargs="+", + help="""Those are the root dependencies which the script will use to find the closure of all transitive dependencies.""", - required=True, - ) - parser.add_argument( - '--old-dependencies', - type=str, - help='Relative path to file that contains the old dependencies', - required=True, - ) - parser.add_argument( - '--stamp', - type=str, - help='Path to touch on success', - ) - args = parser.parse_args() - gn_args = _get_current_gn_args() - # remove remoteexec related gn args. - gn_args = [ - arg for arg in gn_args - if not re.search(r'use_(remoteexec|reclient|siso)\s*=.*', arg) - ] - # make sure it doesn't use remoteexec - gn_args.append('use_remoteexec = false') + required=True, + ) + parser.add_argument( + '--old-dependencies', + type=str, + help='Relative path to file that contains the old dependencies', + required=True, + ) + parser.add_argument( + '--stamp', + type=str, + help='Path to touch on success', + ) + args = parser.parse_args() + gn_args = _get_current_gn_args() + # remove remoteexec related gn args. + gn_args = [ + arg for arg in gn_args + if not re.search(r'use_(remoteexec|reclient|siso)\s*=.*', arg) + ] + # make sure it doesn't use remoteexec + gn_args.append('use_remoteexec = false') - # Generate a new GN output directory in order - # not to mess with the current one. - with tempfile.TemporaryDirectory() as tmp_dir_name: - cronet_utils.gn(tmp_dir_name, " ".join(gn_args)) + # Generate a new GN output directory in order + # not to mess with the current one. + with tempfile.TemporaryDirectory() as tmp_dir_name: + cronet_utils.gn(tmp_dir_name, " ".join(gn_args)) - final_deps = normalize_and_dedup_deps( - _get_transitive_deps_from_root_targets(tmp_dir_name, args.root_deps)) - golden_deps = cronet_utils.read_file(args.old_dependencies).split("\n") - if not all(dep in golden_deps for dep in final_deps): - # Only generate this text if we found a new dependency - # that does not exist in the golden text. This means - # that we will know not if a dependency gets removed - # as we don't care about that scenario and we don't - # want to block people while cleaning-up code. - print(""" + final_deps = normalize_and_dedup_deps( + _get_transitive_deps_from_root_targets(tmp_dir_name, + args.root_deps)) + golden_deps = cronet_utils.read_file(args.old_dependencies).split("\n") + if not all(dep in golden_deps for dep in final_deps): + # Only generate this text if we found a new dependency + # that does not exist in the golden text. This means + # that we will know not if a dependency gets removed + # as we don't care about that scenario and we don't + # want to block people while cleaning-up code. + print(""" Cronet Dependency check has failed. Please re-generate the golden file: ####################################################### # # @@ -197,13 +199,14 @@ END_DIFF ############ END ############ """ % cronet_utils.compare_text_and_generate_diff( - '\n'.join(final_deps), cronet_utils.read_file(args.old_dependencies), - args.old_dependencies)) - return -1 + '\n'.join(final_deps), + cronet_utils.read_file(args.old_dependencies), + args.old_dependencies)) + return -1 - build_utils.Touch(args.stamp) - return 0 + build_utils.Touch(args.stamp) + return 0 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/check_no_neon.py b/components/cronet/tools/check_no_neon.py index a2709d5..e4c9060 100755 --- a/components/cronet/tools/check_no_neon.py +++ b/components/cronet/tools/check_no_neon.py
@@ -2,7 +2,6 @@ # Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """check_no_neon.py - Check modules do not contain ARM Neon instructions.""" import argparse @@ -17,20 +16,20 @@ def main(args): - parser = argparse.ArgumentParser( - description='Check modules do not contain ARM Neon instructions.') - parser.add_argument('objdump', metavar='path/to/ARM/objdump') - parser.add_argument('objects', metavar='files/to/check/*.o') - parser.add_argument('--stamp', help='Path to touch on success.') - opts = parser.parse_args(args) - ret = os.system(opts.objdump + ' -d --no-show-raw-insn ' + - opts.objects + ' | grep -q "vld[1-9]\\|vst[1-9]"') + parser = argparse.ArgumentParser( + description='Check modules do not contain ARM Neon instructions.') + parser.add_argument('objdump', metavar='path/to/ARM/objdump') + parser.add_argument('objects', metavar='files/to/check/*.o') + parser.add_argument('--stamp', help='Path to touch on success.') + opts = parser.parse_args(args) + ret = os.system(opts.objdump + ' -d --no-show-raw-insn ' + opts.objects + + ' | grep -q "vld[1-9]\\|vst[1-9]"') - # Non-zero exit code means no neon. - if ret and opts.stamp: - build_utils.Touch(opts.stamp) - return ret + # Non-zero exit code means no neon. + if ret and opts.stamp: + build_utils.Touch(opts.stamp) + return ret if __name__ == '__main__': - sys.exit(0 if main(sys.argv[1:]) != 0 else -1) + sys.exit(0 if main(sys.argv[1:]) != 0 else -1)
diff --git a/components/cronet/tools/cr_cronet.py b/components/cronet/tools/cr_cronet.py index 60fb5356..9d393f0 100755 --- a/components/cronet/tools/cr_cronet.py +++ b/components/cronet/tools/cr_cronet.py
@@ -20,139 +20,139 @@ def install(out_dir): - cmd = ['build/android/adb_install_apk.py'] - # Propagate PATH to avoid issues with missing tools http://crbug/1217979 - env = { - 'BUILDTYPE': out_dir[4:], - 'PATH': os.environ.get('PATH', ''), - 'HOME': os.environ.get('HOME', '') - } - return run(cmd + ['CronetTestInstrumentation.apk'], env=env) + cmd = ['build/android/adb_install_apk.py'] + # Propagate PATH to avoid issues with missing tools http://crbug/1217979 + env = { + 'BUILDTYPE': out_dir[4:], + 'PATH': os.environ.get('PATH', ''), + 'HOME': os.environ.get('HOME', '') + } + return run(cmd + ['CronetTestInstrumentation.apk'], env=env) def test(out_dir, extra_options): - # Ideally we would fetch this path from somewhere. Though, that's not trivial - # and very unlikely to change. This being "best effort test code", it should - # be fine just to hardcode it. - remote_netlog_dir = '/data/data/org.chromium.net.tests/app_cronet_test/NetLog' - run(['adb', 'shell', 'rm', '-rf', remote_netlog_dir]) - run([out_dir + '/bin/run_cronet_test_instrumentation_apk'] + extra_options) - local_netlog_dir = out_dir + '/netlogs_for-' + datetime.now().strftime( - "%y_%m_%d-%H_%M_%S") - return run(['adb', 'pull', remote_netlog_dir, local_netlog_dir]) + # Ideally we would fetch this path from somewhere. Though, that's not trivial + # and very unlikely to change. This being "best effort test code", it should + # be fine just to hardcode it. + remote_netlog_dir = '/data/data/org.chromium.net.tests/app_cronet_test/NetLog' + run(['adb', 'shell', 'rm', '-rf', remote_netlog_dir]) + run([out_dir + '/bin/run_cronet_test_instrumentation_apk'] + extra_options) + local_netlog_dir = out_dir + '/netlogs_for-' + datetime.now().strftime( + "%y_%m_%d-%H_%M_%S") + return run(['adb', 'pull', remote_netlog_dir, local_netlog_dir]) def unittest(out_dir, extra_options): - return run([out_dir + '/bin/run_cronet_unittests_android'] + extra_options) + return run([out_dir + '/bin/run_cronet_unittests_android'] + extra_options) def debug(extra_options): - return run([ - 'build/android/adb_gdb', '--start', '--activity=.CronetTestActivity', - '--program-name=CronetTest', '--package-name=org.chromium.net' - ] + extra_options) + return run([ + 'build/android/adb_gdb', '--start', '--activity=.CronetTestActivity', + '--program-name=CronetTest', '--package-name=org.chromium.net' + ] + extra_options) def main(): - parser = argparse.ArgumentParser() - parser.add_argument('command', - choices=[ - 'gn', 'sync', 'build', 'install', 'proguard', 'test', - 'build-test', 'unit', 'build-unit', 'stack', 'debug', - 'build-debug' - ]) - parser.add_argument('-d', - '--out_dir', - action='store', - help='name of the build directory') - parser.add_argument('-x', - '--x86', - action='store_true', - help='build for Intel x86 architecture') - parser.add_argument('--x64', - action='store_true', - help='build for Intel x86_64 architecture') - parser.add_argument('--arm', - action='store_true', - help='build for arm architecture') - parser.add_argument('-R', - '--riscv64', - action='store_true', - help='build for riscv64 architecture') - parser.add_argument('-r', - '--release', - action='store_true', - help='use release configuration') - parser.add_argument('-a', - '--asan', - action='store_true', - help='use address sanitizer') + parser = argparse.ArgumentParser() + parser.add_argument('command', + choices=[ + 'gn', 'sync', 'build', 'install', 'proguard', + 'test', 'build-test', 'unit', 'build-unit', + 'stack', 'debug', 'build-debug' + ]) + parser.add_argument('-d', + '--out_dir', + action='store', + help='name of the build directory') + parser.add_argument('-x', + '--x86', + action='store_true', + help='build for Intel x86 architecture') + parser.add_argument('--x64', + action='store_true', + help='build for Intel x86_64 architecture') + parser.add_argument('--arm', + action='store_true', + help='build for arm architecture') + parser.add_argument('-R', + '--riscv64', + action='store_true', + help='build for riscv64 architecture') + parser.add_argument('-r', + '--release', + action='store_true', + help='use release configuration') + parser.add_argument('-a', + '--asan', + action='store_true', + help='use address sanitizer') - options, extra_options = parser.parse_known_args() - print("Options:", options) - print("Extra options:", extra_options) + options, extra_options = parser.parse_known_args() + print("Options:", options) + print("Extra options:", extra_options) - test_target = 'cronet_test_instrumentation_apk' - unit_target = 'cronet_unittests_android' - if options.x86: - target_cpu = 'x86' - out_dir_suffix = '-x86' - elif options.x64: - target_cpu = 'x64' - out_dir_suffix = '-x64' - elif options.riscv64: - target_cpu = 'riscv64' - out_dir_suffix = '-riscv64' - elif options.arm: - target_cpu = 'arm' - out_dir_suffix = '-arm' - else: - target_cpu = 'arm64' - out_dir_suffix = '-arm64' - - if options.asan: - # ASAN on Android requires one-time setup described here: - # https://www.chromium.org/developers/testing/addresssanitizer - out_dir_suffix += '-asan' - - if options.out_dir: - out_dir = options.out_dir - else: - if options.release: - out_dir = 'out/Release' + out_dir_suffix + test_target = 'cronet_test_instrumentation_apk' + unit_target = 'cronet_unittests_android' + if options.x86: + target_cpu = 'x86' + out_dir_suffix = '-x86' + elif options.x64: + target_cpu = 'x64' + out_dir_suffix = '-x64' + elif options.riscv64: + target_cpu = 'riscv64' + out_dir_suffix = '-riscv64' + elif options.arm: + target_cpu = 'arm' + out_dir_suffix = '-arm' else: - out_dir = 'out/Debug' + out_dir_suffix + target_cpu = 'arm64' + out_dir_suffix = '-arm64' - if (options.command == 'gn'): - return android_gn_gen(options.release, target_cpu, out_dir) - if (options.command == 'sync'): - return run(['git', 'pull', '--rebase']) or run(['gclient', 'sync']) - if (options.command == 'build'): - return build(out_dir, test_target, extra_options) - if (options.command == 'install'): - return install(out_dir) - if (options.command == 'proguard'): - return build(out_dir, 'cronet_sample_proguard_apk') - if (options.command == 'test'): - return install(out_dir) or test(out_dir, extra_options) - if (options.command == 'build-test'): - return build(out_dir, test_target) or install(out_dir) or \ - test(out_dir, extra_options) - if (options.command == 'stack'): - return stack(out_dir) - if (options.command == 'debug'): - return install(out_dir) or debug(extra_options) - if (options.command == 'build-debug'): - return build(out_dir, test_target) or install(out_dir) or \ - debug(extra_options) - if (options.command == 'unit'): - return unittest(out_dir, extra_options) - if (options.command == 'build-unit'): - return build(out_dir, unit_target) or unittest(out_dir, extra_options) + if options.asan: + # ASAN on Android requires one-time setup described here: + # https://www.chromium.org/developers/testing/addresssanitizer + out_dir_suffix += '-asan' - parser.print_help() - return 1 + if options.out_dir: + out_dir = options.out_dir + else: + if options.release: + out_dir = 'out/Release' + out_dir_suffix + else: + out_dir = 'out/Debug' + out_dir_suffix + + if (options.command == 'gn'): + return android_gn_gen(options.release, target_cpu, out_dir) + if (options.command == 'sync'): + return run(['git', 'pull', '--rebase']) or run(['gclient', 'sync']) + if (options.command == 'build'): + return build(out_dir, test_target, extra_options) + if (options.command == 'install'): + return install(out_dir) + if (options.command == 'proguard'): + return build(out_dir, 'cronet_sample_proguard_apk') + if (options.command == 'test'): + return install(out_dir) or test(out_dir, extra_options) + if (options.command == 'build-test'): + return build(out_dir, test_target) or install(out_dir) or \ + test(out_dir, extra_options) + if (options.command == 'stack'): + return stack(out_dir) + if (options.command == 'debug'): + return install(out_dir) or debug(extra_options) + if (options.command == 'build-debug'): + return build(out_dir, test_target) or install(out_dir) or \ + debug(extra_options) + if (options.command == 'unit'): + return unittest(out_dir, extra_options) + if (options.command == 'build-unit'): + return build(out_dir, unit_target) or unittest(out_dir, extra_options) + + parser.print_help() + return 1 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/generate_accept_languages.py b/components/cronet/tools/generate_accept_languages.py index 9c84175..adc3489 100644 --- a/components/cronet/tools/generate_accept_languages.py +++ b/components/cronet/tools/generate_accept_languages.py
@@ -22,32 +22,44 @@ STRINGS_DIR = sys.argv[2] + 'components/strings/' + # pylint: disable=inconsistent-return-statements def extract_accept_langs(filename): - tree = ElementTree.parse(STRINGS_DIR + filename).getroot() - for child in tree: - if child.get('id') == 'IDS_ACCEPT_LANGUAGES': - return tree.get('lang'), child.text + tree = ElementTree.parse(STRINGS_DIR + filename).getroot() + for child in tree: + if child.get('id') == 'IDS_ACCEPT_LANGUAGES': + return tree.get('lang'), child.text + + # pylint: enable=inconsistent-return-statements + def gen_accept_langs_table(): - accept_langs_list = [extract_accept_langs(filename) - for filename in os.listdir(STRINGS_DIR) - if re.match(r'components_locale_settings_\S+.xtb', filename)] - return dict(accept_langs for accept_langs in accept_langs_list - if accept_langs) + accept_langs_list = [ + extract_accept_langs(filename) for filename in os.listdir(STRINGS_DIR) + if re.match(r'components_locale_settings_\S+.xtb', filename) + ] + return dict(accept_langs for accept_langs in accept_langs_list + if accept_langs) + HEADER = "static NSDictionary* const acceptLangs = @{" + + def LINE(locale, accept_langs): - return ' @"' + locale + '" : @"' + accept_langs + '",' + return ' @"' + locale + '" : @"' + accept_langs + '",' + + FOOTER = "};" + def main(): - with open(sys.argv[1] + "/accept_languages_table.h", "w+") as f: - print(HEADER, file=f) - for (locale, accept_langs) in gen_accept_langs_table().items(): - print(LINE(locale, accept_langs), file=f) - print(FOOTER, file=f) + with open(sys.argv[1] + "/accept_languages_table.h", "w+") as f: + print(HEADER, file=f) + for (locale, accept_langs) in gen_accept_langs_table().items(): + print(LINE(locale, accept_langs), file=f) + print(FOOTER, file=f) + if __name__ == "__main__": - main() + main()
diff --git a/components/cronet/tools/generate_idl_bindings.py b/components/cronet/tools/generate_idl_bindings.py index d31f2f2..d16b427 100755 --- a/components/cronet/tools/generate_idl_bindings.py +++ b/components/cronet/tools/generate_idl_bindings.py
@@ -12,38 +12,38 @@ def run(command, extra_options=''): - command = command + ' ' + extra_options - print(command) - ret = os.system(command) - if ret != 0: - raise OSError(ret) + command = command + ' ' + extra_options + print(command) + ret = os.system(command) + if ret != 0: + raise OSError(ret) def GenerateIdlBindings(_, __): - bytecode_path = tempfile.mkdtemp('idl_bytecode') - generator = 'components/cronet/tools/generators/cronet_bindings_generator.py' - input_file = 'components/cronet/native/cronet.idl' - # Precompile bindings templates - run(generator + ' precompile -o ', bytecode_path) - # Generate C interface. - run(generator + ' --use_bundled_pylibs generate ' + input_file + - ' --bytecode_path ' + bytecode_path + ' -g c') - # TODO(mef): Use output_path to put generated code into - # out/<blah>/gen/components/cronet/native directory. - # -o ' + output_path) - # Format generated code. - run('git cl format --full components/cronet/native') - shutil.rmtree(bytecode_path) + bytecode_path = tempfile.mkdtemp('idl_bytecode') + generator = 'components/cronet/tools/generators/cronet_bindings_generator.py' + input_file = 'components/cronet/native/cronet.idl' + # Precompile bindings templates + run(generator + ' precompile -o ', bytecode_path) + # Generate C interface. + run(generator + ' --use_bundled_pylibs generate ' + input_file + + ' --bytecode_path ' + bytecode_path + ' -g c') + # TODO(mef): Use output_path to put generated code into + # out/<blah>/gen/components/cronet/native directory. + # -o ' + output_path) + # Format generated code. + run('git cl format --full components/cronet/native') + shutil.rmtree(bytecode_path) def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--output-path', - help='Output path for generated bindings') + parser = argparse.ArgumentParser() + parser.add_argument('--output-path', + help='Output path for generated bindings') - args, input_files = parser.parse_known_args() - GenerateIdlBindings(args.output_path, input_files) + args, input_files = parser.parse_known_args() + GenerateIdlBindings(args.output_path, input_files) if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/generate_proguard_file.py b/components/cronet/tools/generate_proguard_file.py index ffa5b0e..e79ad8b 100755 --- a/components/cronet/tools/generate_proguard_file.py +++ b/components/cronet/tools/generate_proguard_file.py
@@ -10,26 +10,27 @@ # The final output file is formed by concatenating all of the # input proguard files. - import argparse import pathlib import os import re import sys import json + REPOSITORY_ROOT = os.path.abspath( os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) sys.path.insert(0, os.path.join(REPOSITORY_ROOT, 'build')) import action_helpers # pylint: disable=wrong-import-position + def _ReadFile(path): - """Reads a file as a string.""" - return pathlib.Path(path).read_text() + """Reads a file as a string.""" + return pathlib.Path(path).read_text() def _post_process_concatenated_rules_for_aosp(rules: str) -> str: - """Post-process the concatenated rules to match the packages in AOSP. Cronet + """Post-process the concatenated rules to match the packages in AOSP. Cronet in AOSP is repackaged where all packages are prefixed with `android.net.connectivity`. So does method will append ** to every package name found. @@ -37,53 +38,53 @@ Args: rules: Rules before processing """ - for package_name in ("org.chromium", "com.google.protobuf", "org.jni_zero", - "androidx.annotation"): - # This regex will match anything substring that matches one of the package - # names but is not preceded by either '*' or '/'. The latter prevents - # comments containing these packages names from being modified. Example: - # -------- Config Path: androidx.annotation/proguard.txt -------- - rules = re.sub(f"([^\*\/]){package_name}", - f"\g<1>android.net.http.internal.{package_name}", rules) - return rules + for package_name in ("org.chromium", "com.google.protobuf", "org.jni_zero", + "androidx.annotation"): + # This regex will match anything substring that matches one of the package + # names but is not preceded by either '*' or '/'. The latter prevents + # comments containing these packages names from being modified. Example: + # -------- Config Path: androidx.annotation/proguard.txt -------- + rules = re.sub(f"([^\*\/]){package_name}", + f"\g<1>android.net.http.internal.{package_name}", rules) + return rules + def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--output_file', - help='Output file for the generated proguard file') - parser.add_argument( - '--aosp-mode', - help='This will apply additional processing for the combined rules to ' - 'ensure they match HttpEngine (Cronet In AOSP).', - action='store_true') - parser.add_argument('--dep_file', - help='Depfile path to write the implicit inputs') - parser.add_argument( - 'build_config', - help='Path to the generated build_config that contains the transitive ' - 'dependencies of the proguard rules') + parser = argparse.ArgumentParser() + parser.add_argument('--output_file', + help='Output file for the generated proguard file') + parser.add_argument( + '--aosp-mode', + help='This will apply additional processing for the combined rules to ' + 'ensure they match HttpEngine (Cronet In AOSP).', + action='store_true') + parser.add_argument('--dep_file', + help='Depfile path to write the implicit inputs') + parser.add_argument( + 'build_config', + help='Path to the generated build_config that contains the transitive ' + 'dependencies of the proguard rules') - args = parser.parse_args() + args = parser.parse_args() - # Fetch all proguard configs - with open(args.build_config, 'r') as f: - build_config = json.load(f) - all_proguard_configs_path = set(build_config['proguard_all_configs']) + # Fetch all proguard configs + with open(args.build_config, 'r') as f: + build_config = json.load(f) + all_proguard_configs_path = set(build_config['proguard_all_configs']) - str_output = "" - # Concatenate all proguard rules and sort to maintain deterministic output. - for proguard_config_path in sorted(all_proguard_configs_path): - noramlized_path = proguard_config_path.replace('../', '') - str_output += f"# -------- Config Path: {noramlized_path} --------\n" - str_output += _ReadFile(proguard_config_path) - if args.aosp_mode: - str_output = _post_process_concatenated_rules_for_aosp(str_output) - with open(args.output_file, 'w') as target: - target.write(str_output) - action_helpers.write_depfile( - args.dep_file, args.output_file, all_proguard_configs_path) - + str_output = "" + # Concatenate all proguard rules and sort to maintain deterministic output. + for proguard_config_path in sorted(all_proguard_configs_path): + noramlized_path = proguard_config_path.replace('../', '') + str_output += f"# -------- Config Path: {noramlized_path} --------\n" + str_output += _ReadFile(proguard_config_path) + if args.aosp_mode: + str_output = _post_process_concatenated_rules_for_aosp(str_output) + with open(args.output_file, 'w') as target: + target.write(str_output) + action_helpers.write_depfile(args.dep_file, args.output_file, + all_proguard_configs_path) if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/generators/cronet_bindings_generator.py b/components/cronet/tools/generators/cronet_bindings_generator.py index 0f77084..89cabcf7 100755 --- a/components/cronet/tools/generators/cronet_bindings_generator.py +++ b/components/cronet/tools/generators/cronet_bindings_generator.py
@@ -2,10 +2,8 @@ # Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """The frontend for the Mojo bindings system.""" - import argparse import hashlib import importlib.util @@ -18,23 +16,27 @@ # Disable lint check for finding modules: # pylint: disable=F0401 + def _GetDirAbove(dirname): - """Returns the directory "above" this file containing |dirname| (which must + """Returns the directory "above" this file containing |dirname| (which must also be "above" this file).""" - path = os.path.abspath(__file__) - while True: - path, tail = os.path.split(path) - assert tail - if tail == dirname: - return path + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + # Manually check for the command-line flag. (This isn't quite right, since it # ignores, e.g., "--", but it's close enough.) if "--use_bundled_pylibs" in sys.argv[1:]: - sys.path.insert(0, os.path.join(_GetDirAbove("components"), "third_party")) + sys.path.insert(0, os.path.join(_GetDirAbove("components"), "third_party")) -sys.path.insert(0, os.path.join(_GetDirAbove("components"), - "mojo", "public", "tools", "mojom")) +sys.path.insert( + 0, + os.path.join(_GetDirAbove("components"), "mojo", "public", "tools", + "mojom")) # pylint: disable=wrong-import-position from mojom.error import Error @@ -47,336 +49,376 @@ # pylint: disable=useless-object-inheritance - _BUILTIN_GENERATORS = { - "c": "cronet_c_generator.py", + "c": "cronet_c_generator.py", } def LoadGenerators(generators_string): - if not generators_string: - return [] # No generators. + if not generators_string: + return [] # No generators. - script_dir = os.path.dirname(os.path.abspath(__file__)) - generators = {} - for generator_name in [s.strip() for s in generators_string.split(",")]: - language = generator_name.lower() - if language in _BUILTIN_GENERATORS: - generator_name = os.path.join(script_dir, - _BUILTIN_GENERATORS[language]) - else: - print("Unknown generator name %s" % generator_name) - sys.exit(1) - spec = importlib.util.spec_from_file_location( - os.path.basename(generator_name)[:-3], generator_name) - generator_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(generator_module) - generators[language] = generator_module - return generators + script_dir = os.path.dirname(os.path.abspath(__file__)) + generators = {} + for generator_name in [s.strip() for s in generators_string.split(",")]: + language = generator_name.lower() + if language in _BUILTIN_GENERATORS: + generator_name = os.path.join(script_dir, + _BUILTIN_GENERATORS[language]) + else: + print("Unknown generator name %s" % generator_name) + sys.exit(1) + spec = importlib.util.spec_from_file_location( + os.path.basename(generator_name)[:-3], generator_name) + generator_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(generator_module) + generators[language] = generator_module + return generators def MakeImportStackMessage(imported_filename_stack): - """Make a (human-readable) message listing a chain of imports. (Returned + """Make a (human-readable) message listing a chain of imports. (Returned string begins with a newline (if nonempty) and does not end with one.)""" - return ''.join( - reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ - zip(imported_filename_stack[1:], imported_filename_stack)])) + return ''.join( + reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ + zip(imported_filename_stack[1:], imported_filename_stack)])) class RelativePath(object): - """Represents a path relative to the source tree.""" - def __init__(self, path, source_root): - self.path = path - self.source_root = source_root + """Represents a path relative to the source tree.""" - def relative_path(self): - return os.path.relpath(os.path.abspath(self.path), - os.path.abspath(self.source_root)) + def __init__(self, path, source_root): + self.path = path + self.source_root = source_root + + def relative_path(self): + return os.path.relpath(os.path.abspath(self.path), + os.path.abspath(self.source_root)) def FindImportFile(rel_dir, file_name, search_rel_dirs): - """Finds |file_name| in either |rel_dir| or |search_rel_dirs|. Returns a + """Finds |file_name| in either |rel_dir| or |search_rel_dirs|. Returns a RelativePath with first file found, or an arbitrary non-existent file otherwise.""" - for rel_search_dir in [rel_dir] + search_rel_dirs: - path = os.path.join(rel_search_dir.path, file_name) - if os.path.isfile(path): - return RelativePath(path, rel_search_dir.source_root) - return RelativePath(os.path.join(rel_dir.path, file_name), - rel_dir.source_root) + for rel_search_dir in [rel_dir] + search_rel_dirs: + path = os.path.join(rel_search_dir.path, file_name) + if os.path.isfile(path): + return RelativePath(path, rel_search_dir.source_root) + return RelativePath(os.path.join(rel_dir.path, file_name), + rel_dir.source_root) def ScrambleMethodOrdinals(interfaces, salt): - already_generated = set() - for interface in interfaces: - i = 0 - already_generated.clear() - for method in interface.methods: - while True: - i = i + 1 - if i == 1000000: - raise Exception("Could not generate %d method ordinals for %s" % - (len(interface.methods), interface.mojom_name)) - # Generate a scrambled method.ordinal value. The algorithm doesn't have - # to be very strong, cryptographically. It just needs to be non-trivial - # to guess the results without the secret salt, in order to make it - # harder for a compromised process to send fake Mojo messages. - sha256 = hashlib.sha256(salt) - sha256.update(interface.mojom_name) - sha256.update(str(i)) - # Take the first 4 bytes as a little-endian uint32. - ordinal = struct.unpack('<L', sha256.digest()[:4])[0] - # Trim to 31 bits, so it always fits into a Java (signed) int. - ordinal = ordinal & 0x7fffffff - if ordinal in already_generated: - continue - already_generated.add(ordinal) - method.ordinal = ordinal - method.ordinal_comment = ( - 'The %s value is based on sha256(salt + "%s%d").' % - (ordinal, interface.mojom_name, i)) - break + already_generated = set() + for interface in interfaces: + i = 0 + already_generated.clear() + for method in interface.methods: + while True: + i = i + 1 + if i == 1000000: + raise Exception( + "Could not generate %d method ordinals for %s" % + (len(interface.methods), interface.mojom_name)) + # Generate a scrambled method.ordinal value. The algorithm doesn't have + # to be very strong, cryptographically. It just needs to be non-trivial + # to guess the results without the secret salt, in order to make it + # harder for a compromised process to send fake Mojo messages. + sha256 = hashlib.sha256(salt) + sha256.update(interface.mojom_name) + sha256.update(str(i)) + # Take the first 4 bytes as a little-endian uint32. + ordinal = struct.unpack('<L', sha256.digest()[:4])[0] + # Trim to 31 bits, so it always fits into a Java (signed) int. + ordinal = ordinal & 0x7fffffff + if ordinal in already_generated: + continue + already_generated.add(ordinal) + method.ordinal = ordinal + method.ordinal_comment = ( + 'The %s value is based on sha256(salt + "%s%d").' % + (ordinal, interface.mojom_name, i)) + break def ReadFileContents(filename): - with open(filename, 'rb') as f: - return f.read() + with open(filename, 'rb') as f: + return f.read() class MojomProcessor(object): - """Parses mojom files and creates ASTs for them. + """Parses mojom files and creates ASTs for them. Attributes: _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from relative mojom filename paths to the module AST for that mojom file. """ - def __init__(self, should_generate): - self._should_generate = should_generate - self._processed_files = {} - self._parsed_files = {} - self._typemap = {} - def LoadTypemaps(self, typemaps): - # Support some very simple single-line comments in typemap JSON. - comment_expr = r"^\s*//.*$" - def no_comments(line): - return not re.match(comment_expr, line) - for filename in typemaps: - with open(filename) as f: - typemaps = json.loads("".join([l for l in f.readlines() - if no_comments(l)])) - for language, typemap in typemaps.items(): - language_map = self._typemap.get(language, {}) - language_map.update(typemap) - self._typemap[language] = language_map + def __init__(self, should_generate): + self._should_generate = should_generate + self._processed_files = {} + self._parsed_files = {} + self._typemap = {} - def ProcessFile(self, args, remaining_args, generator_modules, filename): - self._ParseFileAndImports(RelativePath(filename, args.depth), - args.import_directories, []) + def LoadTypemaps(self, typemaps): + # Support some very simple single-line comments in typemap JSON. + comment_expr = r"^\s*//.*$" - return self._GenerateModule(args, remaining_args, generator_modules, - RelativePath(filename, args.depth)) + def no_comments(line): + return not re.match(comment_expr, line) - def _GenerateModule(self, args, remaining_args, generator_modules, - rel_filename): - # Return the already-generated module. - if rel_filename.path in self._processed_files: - return self._processed_files[rel_filename.path] - tree = self._parsed_files[rel_filename.path] + for filename in typemaps: + with open(filename) as f: + typemaps = json.loads("".join( + [l for l in f.readlines() if no_comments(l)])) + for language, typemap in typemaps.items(): + language_map = self._typemap.get(language, {}) + language_map.update(typemap) + self._typemap[language] = language_map - dirname = os.path.dirname(rel_filename.path) + def ProcessFile(self, args, remaining_args, generator_modules, filename): + self._ParseFileAndImports(RelativePath(filename, args.depth), + args.import_directories, []) - # Process all our imports first and collect the module object for each. - # We use these to generate proper type info. - imports = {} - for parsed_imp in tree.import_list: - rel_import_file = FindImportFile( - RelativePath(dirname, rel_filename.source_root), - parsed_imp.import_filename, args.import_directories) - imports[parsed_imp.import_filename] = self._GenerateModule( - args, remaining_args, generator_modules, rel_import_file) + return self._GenerateModule(args, remaining_args, generator_modules, + RelativePath(filename, args.depth)) - # Set the module path as relative to the source root. - # Normalize to unix-style path here to keep the generators simpler. - module_path = rel_filename.relative_path().replace('\\', '/') + def _GenerateModule(self, args, remaining_args, generator_modules, + rel_filename): + # Return the already-generated module. + if rel_filename.path in self._processed_files: + return self._processed_files[rel_filename.path] + tree = self._parsed_files[rel_filename.path] - module = translate.OrderedModule(tree, module_path, imports) + dirname = os.path.dirname(rel_filename.path) - if args.scrambled_message_id_salt_paths: - salt = ''.join( - [ReadFileContents(f) for f in args.scrambled_message_id_salt_paths]) - ScrambleMethodOrdinals(module.interfaces, salt) + # Process all our imports first and collect the module object for each. + # We use these to generate proper type info. + imports = {} + for parsed_imp in tree.import_list: + rel_import_file = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + parsed_imp.import_filename, args.import_directories) + imports[parsed_imp.import_filename] = self._GenerateModule( + args, remaining_args, generator_modules, rel_import_file) - if self._should_generate(rel_filename.path): - AddComputedData(module) - for language, generator_module in generator_modules.items(): - generator = generator_module.Generator( - module, args.output_dir, typemap=self._typemap.get(language, {}), - variant=args.variant, bytecode_path=args.bytecode_path, - for_blink=args.for_blink, - export_attribute=args.export_attribute, - export_header=args.export_header, - generate_non_variant_code=args.generate_non_variant_code) - filtered_args = [] - if hasattr(generator_module, 'GENERATOR_PREFIX'): - prefix = '--' + generator_module.GENERATOR_PREFIX + '_' - filtered_args = [arg for arg in remaining_args - if arg.startswith(prefix)] - generator.GenerateFiles(filtered_args) + # Set the module path as relative to the source root. + # Normalize to unix-style path here to keep the generators simpler. + module_path = rel_filename.relative_path().replace('\\', '/') - # Save result. - self._processed_files[rel_filename.path] = module - return module + module = translate.OrderedModule(tree, module_path, imports) - def _ParseFileAndImports(self, rel_filename, import_directories, - imported_filename_stack): - # Ignore already-parsed files. - if rel_filename.path in self._parsed_files: - return + if args.scrambled_message_id_salt_paths: + salt = ''.join([ + ReadFileContents(f) + for f in args.scrambled_message_id_salt_paths + ]) + ScrambleMethodOrdinals(module.interfaces, salt) - if rel_filename.path in imported_filename_stack: - print("%s: Error: Circular dependency" % rel_filename.path + \ - MakeImportStackMessage(imported_filename_stack + [rel_filename.path])) - sys.exit(1) + if self._should_generate(rel_filename.path): + AddComputedData(module) + for language, generator_module in generator_modules.items(): + generator = generator_module.Generator( + module, + args.output_dir, + typemap=self._typemap.get(language, {}), + variant=args.variant, + bytecode_path=args.bytecode_path, + for_blink=args.for_blink, + export_attribute=args.export_attribute, + export_header=args.export_header, + generate_non_variant_code=args.generate_non_variant_code) + filtered_args = [] + if hasattr(generator_module, 'GENERATOR_PREFIX'): + prefix = '--' + generator_module.GENERATOR_PREFIX + '_' + filtered_args = [ + arg for arg in remaining_args if arg.startswith(prefix) + ] + generator.GenerateFiles(filtered_args) - try: - with open(rel_filename.path) as f: - source = f.read() - except IOError as e: - print("%s: Error: %s" % (rel_filename.path, e.strerror) + \ - MakeImportStackMessage(imported_filename_stack + [rel_filename.path])) - sys.exit(1) + # Save result. + self._processed_files[rel_filename.path] = module + return module - try: - tree = Parse(source, rel_filename.path) - except Error as e: - full_stack = imported_filename_stack + [rel_filename.path] - print(str(e) + MakeImportStackMessage(full_stack)) - sys.exit(1) + def _ParseFileAndImports(self, rel_filename, import_directories, + imported_filename_stack): + # Ignore already-parsed files. + if rel_filename.path in self._parsed_files: + return - dirname = os.path.split(rel_filename.path)[0] - for imp_entry in tree.import_list: - import_file_entry = FindImportFile( - RelativePath(dirname, rel_filename.source_root), - imp_entry.import_filename, import_directories) - self._ParseFileAndImports(import_file_entry, import_directories, - imported_filename_stack + [rel_filename.path]) + if rel_filename.path in imported_filename_stack: + print("%s: Error: Circular dependency" % rel_filename.path + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path])) + sys.exit(1) - self._parsed_files[rel_filename.path] = tree + try: + with open(rel_filename.path) as f: + source = f.read() + except IOError as e: + print("%s: Error: %s" % (rel_filename.path, e.strerror) + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path])) + sys.exit(1) + + try: + tree = Parse(source, rel_filename.path) + except Error as e: + full_stack = imported_filename_stack + [rel_filename.path] + print(str(e) + MakeImportStackMessage(full_stack)) + sys.exit(1) + + dirname = os.path.split(rel_filename.path)[0] + for imp_entry in tree.import_list: + import_file_entry = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + imp_entry.import_filename, import_directories) + self._ParseFileAndImports( + import_file_entry, import_directories, + imported_filename_stack + [rel_filename.path]) + + self._parsed_files[rel_filename.path] = tree def _Generate(args, remaining_args): - if args.variant == "none": - args.variant = None + if args.variant == "none": + args.variant = None - for idx, import_dir in enumerate(args.import_directories): - tokens = import_dir.split(":") - if len(tokens) >= 2: - args.import_directories[idx] = RelativePath(tokens[0], tokens[1]) - else: - args.import_directories[idx] = RelativePath(tokens[0], args.depth) - generator_modules = LoadGenerators(args.generators_string) + for idx, import_dir in enumerate(args.import_directories): + tokens = import_dir.split(":") + if len(tokens) >= 2: + args.import_directories[idx] = RelativePath(tokens[0], tokens[1]) + else: + args.import_directories[idx] = RelativePath(tokens[0], args.depth) + generator_modules = LoadGenerators(args.generators_string) - fileutil.EnsureDirectoryExists(args.output_dir) + fileutil.EnsureDirectoryExists(args.output_dir) - processor = MojomProcessor(lambda filename: filename in args.filename) - processor.LoadTypemaps(set(args.typemaps)) - for filename in args.filename: - processor.ProcessFile(args, remaining_args, generator_modules, filename) - if args.depfile: - assert args.depfile_target - with open(args.depfile, 'w') as f: - f.write('%s: %s' % ( - args.depfile_target, - ' '.join(list(processor._parsed_files.keys())))) + processor = MojomProcessor(lambda filename: filename in args.filename) + processor.LoadTypemaps(set(args.typemaps)) + for filename in args.filename: + processor.ProcessFile(args, remaining_args, generator_modules, + filename) + if args.depfile: + assert args.depfile_target + with open(args.depfile, 'w') as f: + f.write('%s: %s' % (args.depfile_target, ' '.join( + list(processor._parsed_files.keys())))) - return 0 + return 0 def _Precompile(args, _): - generator_modules = LoadGenerators(",".join(list(_BUILTIN_GENERATORS.keys()))) + generator_modules = LoadGenerators(",".join( + list(_BUILTIN_GENERATORS.keys()))) - template_expander.PrecompileTemplates(generator_modules, args.output_dir) - return 0 - + template_expander.PrecompileTemplates(generator_modules, args.output_dir) + return 0 def main(): - parser = argparse.ArgumentParser( - description="Generate bindings from mojom files.") - parser.add_argument("--use_bundled_pylibs", action="store_true", - help="use Python modules bundled in the SDK") + parser = argparse.ArgumentParser( + description="Generate bindings from mojom files.") + parser.add_argument("--use_bundled_pylibs", + action="store_true", + help="use Python modules bundled in the SDK") - subparsers = parser.add_subparsers() - generate_parser = subparsers.add_parser( - "generate", description="Generate bindings from mojom files.") - generate_parser.add_argument("filename", nargs="+", - help="mojom input file") - generate_parser.add_argument("-d", "--depth", dest="depth", default=".", - help="depth from source root") - generate_parser.add_argument("-o", "--output_dir", dest="output_dir", - default=".", - help="output directory for generated files") - generate_parser.add_argument("-g", "--generators", - dest="generators_string", - metavar="GENERATORS", - default="c++,javascript,java", - help="comma-separated list of generators") - generate_parser.add_argument( - "-I", dest="import_directories", action="append", metavar="directory", - default=[], - help="add a directory to be searched for import files. The depth from " - "source root can be specified for each import by appending it after " - "a colon") - generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP", - default=[], dest="typemaps", - help="apply TYPEMAP to generated output") - generate_parser.add_argument("--variant", dest="variant", default=None, - help="output a named variant of the bindings") - generate_parser.add_argument( - "--bytecode_path", required=True, help=( - "the path from which to load template bytecode; to generate template " - "bytecode, run %s precompile BYTECODE_PATH" % os.path.basename( - sys.argv[0]))) - generate_parser.add_argument("--for_blink", action="store_true", - help="Use WTF types as generated types for mojo " - "string/array/map.") - generate_parser.add_argument( - "--export_attribute", default="", - help="Optional attribute to specify on class declaration to export it " - "for the component build.") - generate_parser.add_argument( - "--export_header", default="", - help="Optional header to include in the generated headers to support the " - "component build.") - generate_parser.add_argument( - "--generate_non_variant_code", action="store_true", - help="Generate code that is shared by different variants.") - generate_parser.add_argument( - "--depfile", - help="A file into which the list of input files will be written.") - generate_parser.add_argument( - "--depfile_target", - help="The target name to use in the depfile.") - generate_parser.add_argument( - "--scrambled_message_id_salt_path", - dest="scrambled_message_id_salt_paths", - help="If non-empty, the path to a file whose contents should be used as" - "a salt for generating scrambled message IDs. If this switch is specified" - "more than once, the contents of all salt files are concatenated to form" - "the salt value.", default=[], action="append") - generate_parser.set_defaults(func=_Generate) + subparsers = parser.add_subparsers() + generate_parser = subparsers.add_parser( + "generate", description="Generate bindings from mojom files.") + generate_parser.add_argument("filename", + nargs="+", + help="mojom input file") + generate_parser.add_argument("-d", + "--depth", + dest="depth", + default=".", + help="depth from source root") + generate_parser.add_argument("-o", + "--output_dir", + dest="output_dir", + default=".", + help="output directory for generated files") + generate_parser.add_argument("-g", + "--generators", + dest="generators_string", + metavar="GENERATORS", + default="c++,javascript,java", + help="comma-separated list of generators") + generate_parser.add_argument( + "-I", + dest="import_directories", + action="append", + metavar="directory", + default=[], + help="add a directory to be searched for import files. The depth from " + "source root can be specified for each import by appending it after " + "a colon") + generate_parser.add_argument("--typemap", + action="append", + metavar="TYPEMAP", + default=[], + dest="typemaps", + help="apply TYPEMAP to generated output") + generate_parser.add_argument("--variant", + dest="variant", + default=None, + help="output a named variant of the bindings") + generate_parser.add_argument( + "--bytecode_path", + required=True, + help= + ("the path from which to load template bytecode; to generate template " + "bytecode, run %s precompile BYTECODE_PATH" % + os.path.basename(sys.argv[0]))) + generate_parser.add_argument( + "--for_blink", + action="store_true", + help="Use WTF types as generated types for mojo " + "string/array/map.") + generate_parser.add_argument( + "--export_attribute", + default="", + help="Optional attribute to specify on class declaration to export it " + "for the component build.") + generate_parser.add_argument( + "--export_header", + default="", + help= + "Optional header to include in the generated headers to support the " + "component build.") + generate_parser.add_argument( + "--generate_non_variant_code", + action="store_true", + help="Generate code that is shared by different variants.") + generate_parser.add_argument( + "--depfile", + help="A file into which the list of input files will be written.") + generate_parser.add_argument("--depfile_target", + help="The target name to use in the depfile.") + generate_parser.add_argument( + "--scrambled_message_id_salt_path", + dest="scrambled_message_id_salt_paths", + help="If non-empty, the path to a file whose contents should be used as" + "a salt for generating scrambled message IDs. If this switch is specified" + "more than once, the contents of all salt files are concatenated to form" + "the salt value.", + default=[], + action="append") + generate_parser.set_defaults(func=_Generate) - precompile_parser = subparsers.add_parser("precompile", - description="Precompile templates for the mojom bindings generator.") - precompile_parser.add_argument( - "-o", "--output_dir", dest="output_dir", default=".", - help="output directory for precompiled templates") - precompile_parser.set_defaults(func=_Precompile) + precompile_parser = subparsers.add_parser( + "precompile", + description="Precompile templates for the mojom bindings generator.") + precompile_parser.add_argument( + "-o", + "--output_dir", + dest="output_dir", + default=".", + help="output directory for precompiled templates") + precompile_parser.set_defaults(func=_Precompile) - args, remaining_args = parser.parse_known_args() - return args.func(args, remaining_args) + args, remaining_args = parser.parse_known_args() + return args.func(args, remaining_args) if __name__ == "__main__": - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/generators/cronet_c_generator.py b/components/cronet/tools/generators/cronet_c_generator.py index 9d61aa8..f7689ff2 100644 --- a/components/cronet/tools/generators/cronet_c_generator.py +++ b/components/cronet/tools/generators/cronet_c_generator.py
@@ -42,19 +42,19 @@ class _NameFormatter(object): - """A formatter for the names of kinds or values.""" + """A formatter for the names of kinds or values.""" - def __init__(self, token, variant): - self._token = token - self._variant = variant + def __init__(self, token, variant): + self._token = token + self._variant = variant - def Format(self, - separator, - internal=False, - include_variant=False, - omit_namespace_for_module=None, - flatten_nested_kind=False): - """Formats the name according to the given configuration. + def Format(self, + separator, + internal=False, + include_variant=False, + omit_namespace_for_module=None, + flatten_nested_kind=False): + """Formats the name according to the given configuration. Args: separator: Separator between different parts of the name. @@ -69,84 +69,86 @@ parent kind and the nested kind with '_', instead of treating the parent kind as a scope.""" - parts = [] - if self._ShouldIncludeNamespace(omit_namespace_for_module): - parts.extend(self._GetNamespace()) - if include_variant and self._variant and not internal: - parts.append(self._variant) - parts.extend(self._GetName(internal, flatten_nested_kind)) - return separator.join(parts) + parts = [] + if self._ShouldIncludeNamespace(omit_namespace_for_module): + parts.extend(self._GetNamespace()) + if include_variant and self._variant and not internal: + parts.append(self._variant) + parts.extend(self._GetName(internal, flatten_nested_kind)) + return separator.join(parts) - def FormatForCpp(self, - omit_namespace_for_module=None, - internal=False, - flatten_nested_kind=False): - return self.Format("_", - omit_namespace_for_module=omit_namespace_for_module, - internal=internal, - include_variant=True, - flatten_nested_kind=flatten_nested_kind) + def FormatForCpp(self, + omit_namespace_for_module=None, + internal=False, + flatten_nested_kind=False): + return self.Format("_", + omit_namespace_for_module=omit_namespace_for_module, + internal=internal, + include_variant=True, + flatten_nested_kind=flatten_nested_kind) - def FormatForMojom(self): - return self.Format(".") + def FormatForMojom(self): + return self.Format(".") - def _MapKindName(self, token, internal): - if not internal: - return token.name - if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) - or mojom.IsEnumKind(token)): - return token.name + "_Data" - return token.name + def _MapKindName(self, token, internal): + if not internal: + return token.name + if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) + or mojom.IsEnumKind(token)): + return token.name + "_Data" + return token.name - def _GetName(self, internal, flatten_nested_kind): - if isinstance(self._token, mojom.EnumValue): - name_parts = _NameFormatter(self._token.enum, self._variant)._GetName( - internal, flatten_nested_kind) - name_parts.append(self._token.name) - return name_parts + def _GetName(self, internal, flatten_nested_kind): + if isinstance(self._token, mojom.EnumValue): + name_parts = _NameFormatter(self._token.enum, + self._variant)._GetName( + internal, flatten_nested_kind) + name_parts.append(self._token.name) + return name_parts - name_parts = [] - if internal: - name_parts.append("internal") + name_parts = [] + if internal: + name_parts.append("internal") - if (flatten_nested_kind and mojom.IsEnumKind(self._token) - and self._token.parent_kind): - name = "%s_%s" % (self._token.parent_kind.name, - self._MapKindName(self._token, internal)) - name_parts.append(name) - return name_parts + if (flatten_nested_kind and mojom.IsEnumKind(self._token) + and self._token.parent_kind): + name = "%s_%s" % (self._token.parent_kind.name, + self._MapKindName(self._token, internal)) + name_parts.append(name) + return name_parts - if self._token.parent_kind: - name_parts.append(self._MapKindName(self._token.parent_kind, internal)) - name_parts.append(self._MapKindName(self._token, internal)) - return name_parts + if self._token.parent_kind: + name_parts.append( + self._MapKindName(self._token.parent_kind, internal)) + name_parts.append(self._MapKindName(self._token, internal)) + return name_parts - def _ShouldIncludeNamespace(self, _): - return self._token.module + def _ShouldIncludeNamespace(self, _): + return self._token.module - # pylint: disable=inconsistent-return-statements - def _GetNamespace(self): - if self._token.module: - return NamespaceToArray(self._token.module.namespace) + # pylint: disable=inconsistent-return-statements + def _GetNamespace(self): + if self._token.module: + return NamespaceToArray(self._token.module.namespace) - # pylint: enable=inconsistent-return-statements + # pylint: enable=inconsistent-return-statements def NamespaceToArray(namespace): - return namespace.split(".") if namespace else [] + return namespace.split(".") if namespace else [] def IsNativeOnlyKind(kind): - return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \ - kind.native_only + return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \ + kind.native_only def UseCustomSerializer(kind): - return mojom.IsStructKind(kind) and kind.custom_serializer + return mojom.IsStructKind(kind) and kind.custom_serializer def AllEnumValues(enum): - """Return all enum values associated with an enum. + """Return all enum values associated with an enum. Args: enum: {mojom.Enum} The enum type. @@ -154,353 +156,362 @@ Returns: {Set[int]} The values. """ - return set(field.numeric_value for field in enum.fields) + return set(field.numeric_value for field in enum.fields) def GetCppPodType(kind): - return _kind_to_cpp_type[kind] + return _kind_to_cpp_type[kind] def RequiresContextForDataView(kind): - for field in kind.fields: - if mojom.IsReferenceKind(field.kind): - return True - return False + for field in kind.fields: + if mojom.IsReferenceKind(field.kind): + return True + return False def ShouldInlineStruct(struct): - # TODO(darin): Base this on the size of the wrapper class. - if len(struct.fields) > 4: - return False - for field in struct.fields: - if mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind): - return False - return True + # TODO(darin): Base this on the size of the wrapper class. + if len(struct.fields) > 4: + return False + for field in struct.fields: + if mojom.IsReferenceKind( + field.kind) and not mojom.IsStringKind(field.kind): + return False + return True def ShouldInlineUnion(union): - return not any( - mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind) - for field in union.fields) + return not any( + mojom.IsReferenceKind(field.kind) + and not mojom.IsStringKind(field.kind) for field in union.fields) def IsAbstract(kind): - return kind.attributes.get(ATTRIBUTE_ABSTRACT, False) \ - if kind.attributes else False + return kind.attributes.get(ATTRIBUTE_ABSTRACT, False) \ + if kind.attributes else False class StructConstructor(object): - """Represents a constructor for a generated struct. + """Represents a constructor for a generated struct. Fields: fields: {[Field]} All struct fields in order. params: {[Field]} The fields that are passed as params. """ - def __init__(self, fields, params): - self._fields = fields - self._params = set(params) + def __init__(self, fields, params): + self._fields = fields + self._params = set(params) - @property - def params(self): - return [field for field in self._fields if field in self._params] + @property + def params(self): + return [field for field in self._fields if field in self._params] - @property - def fields(self): - for field in self._fields: - yield (field, field in self._params) + @property + def fields(self): + for field in self._fields: + yield (field, field in self._params) class Generator(generator.Generator): - def _GetExtraTraitsHeaders(self): - extra_headers = set() - for typemap in self._GetAllUsedTypemaps(): - extra_headers.update(typemap.get("traits_headers", [])) - return sorted(extra_headers) + def _GetExtraTraitsHeaders(self): + extra_headers = set() + for typemap in self._GetAllUsedTypemaps(): + extra_headers.update(typemap.get("traits_headers", [])) + return sorted(extra_headers) - def _GetAllUsedTypemaps(self): - """Returns the typemaps for types needed for serialization in this module. + def _GetAllUsedTypemaps(self): + """Returns the typemaps for types needed for serialization in this module. A type is needed for serialization if it is contained by a struct or union defined in this module, is a parameter of a message in an interface in this module or is contained within another type needed for serialization. """ - used_typemaps = [] - seen_types = set() + used_typemaps = [] + seen_types = set() - def IsBasicKind(kind): - return (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) - or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) - or mojom.IsAnyHandleKind(kind) or mojom.IsInterfaceKind(kind) - or mojom.IsInterfaceRequestKind(kind) - or mojom.IsAssociatedKind(kind)) + def IsBasicKind(kind): + return (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) + or mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) + or mojom.IsAnyHandleKind(kind) + or mojom.IsInterfaceKind(kind) + or mojom.IsInterfaceRequestKind(kind) + or mojom.IsAssociatedKind(kind)) - def AddKind(kind): - if IsBasicKind(kind): - pass - elif mojom.IsArrayKind(kind): - AddKind(kind.kind) - elif mojom.IsMapKind(kind): - AddKind(kind.key_kind) - AddKind(kind.value_kind) - else: - name = self._GetFullMojomNameForKind(kind) - if name in seen_types: - return - seen_types.add(name) + def AddKind(kind): + if IsBasicKind(kind): + pass + elif mojom.IsArrayKind(kind): + AddKind(kind.kind) + elif mojom.IsMapKind(kind): + AddKind(kind.key_kind) + AddKind(kind.value_kind) + else: + name = self._GetFullMojomNameForKind(kind) + if name in seen_types: + return + seen_types.add(name) - typemap = self.typemap.get(name, None) - if typemap: - used_typemaps.append(typemap) - if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): - for field in kind.fields: - AddKind(field.kind) + typemap = self.typemap.get(name, None) + if typemap: + used_typemaps.append(typemap) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + for field in kind.fields: + AddKind(field.kind) - for kind in self.module.structs + self.module.unions: - for field in kind.fields: - AddKind(field.kind) + for kind in self.module.structs + self.module.unions: + for field in kind.fields: + AddKind(field.kind) - for interface in self.module.interfaces: - for method in interface.methods: - for parameter in method.parameters + (method.response_parameters or []): - AddKind(parameter.kind) + for interface in self.module.interfaces: + for method in interface.methods: + for parameter in method.parameters + ( + method.response_parameters or []): + AddKind(parameter.kind) - return used_typemaps + return used_typemaps - def _GetExtraPublicHeaders(self): - all_enums = list(self.module.enums) - for struct in self.module.structs: - all_enums.extend(struct.enums) - for interface in self.module.interfaces: - all_enums.extend(interface.enums) + def _GetExtraPublicHeaders(self): + all_enums = list(self.module.enums) + for struct in self.module.structs: + all_enums.extend(struct.enums) + for interface in self.module.interfaces: + all_enums.extend(interface.enums) - types = set( - self._GetFullMojomNameForKind(typename) - for typename in self.module.structs + all_enums + self.module.unions) - headers = set() - for typename, typemap in self.typemap.items(): - if typename in types: - headers.update(typemap.get("public_headers", [])) - return sorted(headers) + types = set( + self._GetFullMojomNameForKind(typename) + for typename in self.module.structs + all_enums + + self.module.unions) + headers = set() + for typename, typemap in self.typemap.items(): + if typename in types: + headers.update(typemap.get("public_headers", [])) + return sorted(headers) - def _GetDirectlyUsedKinds(self): - for struct in self.module.structs + self.module.unions: - for field in struct.fields: - yield field.kind + def _GetDirectlyUsedKinds(self): + for struct in self.module.structs + self.module.unions: + for field in struct.fields: + yield field.kind - for interface in self.module.interfaces: - for method in interface.methods: - for param in method.parameters + (method.response_parameters or []): - yield param.kind + for interface in self.module.interfaces: + for method in interface.methods: + for param in method.parameters + (method.response_parameters + or []): + yield param.kind - def _GetJinjaExports(self): - all_enums = list(self.module.enums) - for struct in self.module.structs: - all_enums.extend(struct.enums) - for interface in self.module.interfaces: - all_enums.extend(interface.enums) + def _GetJinjaExports(self): + all_enums = list(self.module.enums) + for struct in self.module.structs: + all_enums.extend(struct.enums) + for interface in self.module.interfaces: + all_enums.extend(interface.enums) - return { - "all_enums": all_enums, - "enums": self.module.enums, - "export_attribute": self.export_attribute, - "export_header": self.export_header, - "extra_public_headers": self._GetExtraPublicHeaders(), - "extra_traits_headers": self._GetExtraTraitsHeaders(), - "for_blink": self.for_blink, - "imports": self.module.imports, - "interfaces": self.module.interfaces, - "kinds": self.module.kinds, - "module": self.module, - "namespace": self.module.namespace, - "namespaces_as_array": NamespaceToArray(self.module.namespace), - "structs": self.module.structs, - "unions": self.module.unions, - "variant": self.variant, - } + return { + "all_enums": all_enums, + "enums": self.module.enums, + "export_attribute": self.export_attribute, + "export_header": self.export_header, + "extra_public_headers": self._GetExtraPublicHeaders(), + "extra_traits_headers": self._GetExtraTraitsHeaders(), + "for_blink": self.for_blink, + "imports": self.module.imports, + "interfaces": self.module.interfaces, + "kinds": self.module.kinds, + "module": self.module, + "namespace": self.module.namespace, + "namespaces_as_array": NamespaceToArray(self.module.namespace), + "structs": self.module.structs, + "unions": self.module.unions, + "variant": self.variant, + } - @staticmethod - def GetTemplatePrefix(): - return "c_templates" + @staticmethod + def GetTemplatePrefix(): + return "c_templates" - def GetFilters(self): - cpp_filters = { - "all_enum_values": AllEnumValues, - "c_wrapper_type": self._GetCWrapperType, - "constant_value": self._ConstantValue, - "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces, - "contains_move_only_members": self._ContainsMoveOnlyMembers, - "cpp_wrapper_param_type": self._GetCppWrapperParamType, - "cpp_data_view_type": self._GetCppDataViewType, - "cpp_field_type": self._GetCppFieldType, - "cpp_union_field_type": self._GetCppUnionFieldType, - "cpp_pod_type": GetCppPodType, - "cpp_union_getter_return_type": self._GetUnionGetterReturnType, - "cpp_union_trait_getter_return_type": - self._GetUnionTraitGetterReturnType, - "cpp_wrapper_type": self._GetCppWrapperType, - "default_value": self._DefaultValue, - "expression_to_text": self._ExpressionToText, - "format_constant_declaration": self._FormatConstantDeclaration, - "get_container_validate_params_ctor_args": - self._GetContainerValidateParamsCtorArgs, - "get_name_for_kind": self._GetNameForKind, - "get_pad": pack.GetPad, - "get_qualified_name_for_kind": self._GetQualifiedNameForKind, - "has_callbacks": mojom.HasCallbacks, - "has_sync_methods": mojom.HasSyncMethods, - "requires_context_for_data_view": RequiresContextForDataView, - "should_inline": ShouldInlineStruct, - "should_inline_union": ShouldInlineUnion, - "is_abstract": IsAbstract, - "is_array_kind": mojom.IsArrayKind, - "is_enum_kind": mojom.IsEnumKind, - "is_integral_kind": mojom.IsIntegralKind, - "is_native_only_kind": IsNativeOnlyKind, - "is_any_handle_kind": mojom.IsAnyHandleKind, - "is_any_interface_kind": mojom.IsAnyInterfaceKind, - "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind, - "is_associated_kind": mojom.IsAssociatedKind, - "is_hashable": self._IsHashableKind, - "is_map_kind": mojom.IsMapKind, - "is_nullable_kind": mojom.IsNullableKind, - "is_object_kind": mojom.IsObjectKind, - "is_reference_kind": mojom.IsReferenceKind, - "is_string_kind": mojom.IsStringKind, - "is_struct_kind": mojom.IsStructKind, - "is_typemapped_kind": self._IsTypemappedKind, - "is_union_kind": mojom.IsUnionKind, - "passes_associated_kinds": mojom.PassesAssociatedKinds, - "struct_constructors": self._GetStructConstructors, - "under_to_camel": generator.ToCamel, - "unmapped_type_for_serializer": self._GetUnmappedTypeForSerializer, - } - return cpp_filters + def GetFilters(self): + cpp_filters = { + "all_enum_values": AllEnumValues, + "c_wrapper_type": self._GetCWrapperType, + "constant_value": self._ConstantValue, + "contains_handles_or_interfaces": + mojom.ContainsHandlesOrInterfaces, + "contains_move_only_members": self._ContainsMoveOnlyMembers, + "cpp_wrapper_param_type": self._GetCppWrapperParamType, + "cpp_data_view_type": self._GetCppDataViewType, + "cpp_field_type": self._GetCppFieldType, + "cpp_union_field_type": self._GetCppUnionFieldType, + "cpp_pod_type": GetCppPodType, + "cpp_union_getter_return_type": self._GetUnionGetterReturnType, + "cpp_union_trait_getter_return_type": + self._GetUnionTraitGetterReturnType, + "cpp_wrapper_type": self._GetCppWrapperType, + "default_value": self._DefaultValue, + "expression_to_text": self._ExpressionToText, + "format_constant_declaration": self._FormatConstantDeclaration, + "get_container_validate_params_ctor_args": + self._GetContainerValidateParamsCtorArgs, + "get_name_for_kind": self._GetNameForKind, + "get_pad": pack.GetPad, + "get_qualified_name_for_kind": self._GetQualifiedNameForKind, + "has_callbacks": mojom.HasCallbacks, + "has_sync_methods": mojom.HasSyncMethods, + "requires_context_for_data_view": RequiresContextForDataView, + "should_inline": ShouldInlineStruct, + "should_inline_union": ShouldInlineUnion, + "is_abstract": IsAbstract, + "is_array_kind": mojom.IsArrayKind, + "is_enum_kind": mojom.IsEnumKind, + "is_integral_kind": mojom.IsIntegralKind, + "is_native_only_kind": IsNativeOnlyKind, + "is_any_handle_kind": mojom.IsAnyHandleKind, + "is_any_interface_kind": mojom.IsAnyInterfaceKind, + "is_any_handle_or_interface_kind": + mojom.IsAnyHandleOrInterfaceKind, + "is_associated_kind": mojom.IsAssociatedKind, + "is_hashable": self._IsHashableKind, + "is_map_kind": mojom.IsMapKind, + "is_nullable_kind": mojom.IsNullableKind, + "is_object_kind": mojom.IsObjectKind, + "is_reference_kind": mojom.IsReferenceKind, + "is_string_kind": mojom.IsStringKind, + "is_struct_kind": mojom.IsStructKind, + "is_typemapped_kind": self._IsTypemappedKind, + "is_union_kind": mojom.IsUnionKind, + "passes_associated_kinds": mojom.PassesAssociatedKinds, + "struct_constructors": self._GetStructConstructors, + "under_to_camel": generator.ToCamel, + "unmapped_type_for_serializer": self._GetUnmappedTypeForSerializer, + } + return cpp_filters - @UseJinja("module_c.h.tmpl") - def _GenerateModuleHeader(self): - return self._GetJinjaExports() + @UseJinja("module_c.h.tmpl") + def _GenerateModuleHeader(self): + return self._GetJinjaExports() - @UseJinja("module_impl_interface.h.tmpl") - def _GenerateModuleInterfaceHeader(self): - return self._GetJinjaExports() + @UseJinja("module_impl_interface.h.tmpl") + def _GenerateModuleInterfaceHeader(self): + return self._GetJinjaExports() - @UseJinja("module_impl_interface.cc.tmpl") - def _GenerateModuleInterfaceSource(self): - return self._GetJinjaExports() + @UseJinja("module_impl_interface.cc.tmpl") + def _GenerateModuleInterfaceSource(self): + return self._GetJinjaExports() - @UseJinja("module_impl_interface_unittest.cc.tmpl") - def _GenerateModuleInterfaceUnittest(self): - return self._GetJinjaExports() + @UseJinja("module_impl_interface_unittest.cc.tmpl") + def _GenerateModuleInterfaceUnittest(self): + return self._GetJinjaExports() - @UseJinja("module_impl_struct.h.tmpl") - def _GenerateModuleStructHeader(self): - return self._GetJinjaExports() + @UseJinja("module_impl_struct.h.tmpl") + def _GenerateModuleStructHeader(self): + return self._GetJinjaExports() - @UseJinja("module_impl_struct.cc.tmpl") - def _GenerateModuleStructSource(self): - return self._GetJinjaExports() + @UseJinja("module_impl_struct.cc.tmpl") + def _GenerateModuleStructSource(self): + return self._GetJinjaExports() - @UseJinja("module_impl_struct_unittest.cc.tmpl") - def _GenerateModuleStructUnittest(self): - return self._GetJinjaExports() + @UseJinja("module_impl_struct_unittest.cc.tmpl") + def _GenerateModuleStructUnittest(self): + return self._GetJinjaExports() - @UseJinja("module.cc.tmpl") - def _GenerateModuleSource(self): - return self._GetJinjaExports() + @UseJinja("module.cc.tmpl") + def _GenerateModuleSource(self): + return self._GetJinjaExports() - @UseJinja("module-shared.h.tmpl") - def _GenerateModuleSharedHeader(self): - return self._GetJinjaExports() + @UseJinja("module-shared.h.tmpl") + def _GenerateModuleSharedHeader(self): + return self._GetJinjaExports() - @UseJinja("module-shared-internal.h.tmpl") - def _GenerateModuleSharedInternalHeader(self): - return self._GetJinjaExports() + @UseJinja("module-shared-internal.h.tmpl") + def _GenerateModuleSharedInternalHeader(self): + return self._GetJinjaExports() - @UseJinja("module-shared.cc.tmpl") - def _GenerateModuleSharedSource(self): - return self._GetJinjaExports() + @UseJinja("module-shared.cc.tmpl") + def _GenerateModuleSharedSource(self): + return self._GetJinjaExports() - def GenerateFiles(self, _): - self.module.Stylize(generator.Stylizer()) - # TODO(mef): Remove this when generated files are not checked in. - path, module = os.path.split(self.module.path) - self.module.path = os.path.join(path, "generated", module) + def GenerateFiles(self, _): + self.module.Stylize(generator.Stylizer()) + # TODO(mef): Remove this when generated files are not checked in. + path, module = os.path.split(self.module.path) + self.module.path = os.path.join(path, "generated", module) - if self.generate_non_variant_code: - self.Write(self._GenerateModuleSharedHeader(), - "%s-shared.h" % self.module.path) - self.Write(self._GenerateModuleSharedInternalHeader(), - "%s-shared-internal.h" % self.module.path) - self.Write(self._GenerateModuleSharedSource(), - "%s-shared.cc" % self.module.path) - else: - suffix = "-%s" % self.variant if self.variant else "" - self.Write(self._GenerateModuleHeader(), - "%s%s_c.h" % (self.module.path, suffix)) - self.Write(self._GenerateModuleInterfaceHeader(), - "%s%s_impl_interface.h" % (self.module.path, suffix)) - self.Write(self._GenerateModuleInterfaceSource(), - "%s%s_impl_interface.cc" % (self.module.path, suffix)) - self.Write(self._GenerateModuleInterfaceUnittest(), - "%s%s_impl_interface_unittest.cc" % (self.module.path, suffix)) - self.Write(self._GenerateModuleStructHeader(), - "%s%s_impl_struct.h" % (self.module.path, suffix)) - self.Write(self._GenerateModuleStructSource(), - "%s%s_impl_struct.cc" % (self.module.path, suffix)) - self.Write(self._GenerateModuleStructUnittest(), - "%s%s_impl_struct_unittest.cc" % (self.module.path, suffix)) + if self.generate_non_variant_code: + self.Write(self._GenerateModuleSharedHeader(), + "%s-shared.h" % self.module.path) + self.Write(self._GenerateModuleSharedInternalHeader(), + "%s-shared-internal.h" % self.module.path) + self.Write(self._GenerateModuleSharedSource(), + "%s-shared.cc" % self.module.path) + else: + suffix = "-%s" % self.variant if self.variant else "" + self.Write(self._GenerateModuleHeader(), + "%s%s_c.h" % (self.module.path, suffix)) + self.Write(self._GenerateModuleInterfaceHeader(), + "%s%s_impl_interface.h" % (self.module.path, suffix)) + self.Write(self._GenerateModuleInterfaceSource(), + "%s%s_impl_interface.cc" % (self.module.path, suffix)) + self.Write( + self._GenerateModuleInterfaceUnittest(), + "%s%s_impl_interface_unittest.cc" % (self.module.path, suffix)) + self.Write(self._GenerateModuleStructHeader(), + "%s%s_impl_struct.h" % (self.module.path, suffix)) + self.Write(self._GenerateModuleStructSource(), + "%s%s_impl_struct.cc" % (self.module.path, suffix)) + self.Write( + self._GenerateModuleStructUnittest(), + "%s%s_impl_struct_unittest.cc" % (self.module.path, suffix)) - def _ConstantValue(self, constant): - return self._ExpressionToText(constant.value, kind=constant.kind) + def _ConstantValue(self, constant): + return self._ExpressionToText(constant.value, kind=constant.kind) - def _DefaultValue(self, field): - if not field.default: - return "" + def _DefaultValue(self, field): + if not field.default: + return "" - if mojom.IsStructKind(field.kind): - assert field.default == "default" - if self._IsTypemappedKind(field.kind): - return "" - return "%s::New()" % self._GetNameForKind(field.kind) + if mojom.IsStructKind(field.kind): + assert field.default == "default" + if self._IsTypemappedKind(field.kind): + return "" + return "%s::New()" % self._GetNameForKind(field.kind) - expression = self._ExpressionToText(field.default, kind=field.kind) - if mojom.IsEnumKind(field.kind) and self._IsTypemappedKind(field.kind): - expression = "mojo::internal::ConvertEnumValue<%s, %s>(%s)" % ( - self._GetNameForKind(field.kind), self._GetCppWrapperType( - field.kind), expression) - return expression + expression = self._ExpressionToText(field.default, kind=field.kind) + if mojom.IsEnumKind(field.kind) and self._IsTypemappedKind(field.kind): + expression = "mojo::internal::ConvertEnumValue<%s, %s>(%s)" % ( + self._GetNameForKind(field.kind), + self._GetCppWrapperType(field.kind), expression) + return expression - def _GetNameForKind(self, - kind, - internal=False, - flatten_nested_kind=False, - add_same_module_namespaces=False): - return _NameFormatter(kind, self.variant).FormatForCpp( - internal=internal, - flatten_nested_kind=flatten_nested_kind, - omit_namespace_for_module=(None if add_same_module_namespaces else - self.module)) + def _GetNameForKind(self, + kind, + internal=False, + flatten_nested_kind=False, + add_same_module_namespaces=False): + return _NameFormatter(kind, self.variant).FormatForCpp( + internal=internal, + flatten_nested_kind=flatten_nested_kind, + omit_namespace_for_module=(None if add_same_module_namespaces else + self.module)) - def _GetQualifiedNameForKind(self, - kind, - internal=False, - flatten_nested_kind=False, - include_variant=True): - return _NameFormatter( - kind, self.variant if include_variant else None).FormatForCpp( - internal=internal, flatten_nested_kind=flatten_nested_kind) + def _GetQualifiedNameForKind(self, + kind, + internal=False, + flatten_nested_kind=False, + include_variant=True): + return _NameFormatter( + kind, self.variant if include_variant else None).FormatForCpp( + internal=internal, flatten_nested_kind=flatten_nested_kind) - def _GetFullMojomNameForKind(self, kind): - return _NameFormatter(kind, self.variant).FormatForMojom() + def _GetFullMojomNameForKind(self, kind): + return _NameFormatter(kind, self.variant).FormatForMojom() - def _IsTypemappedKind(self, kind): - return hasattr(kind, "name") and \ - self._GetFullMojomNameForKind(kind) in self.typemap + def _IsTypemappedKind(self, kind): + return hasattr(kind, "name") and \ + self._GetFullMojomNameForKind(kind) in self.typemap - def _IsHashableKind(self, kind): - """Check if the kind can be hashed. + def _IsHashableKind(self, kind): + """Check if the kind can be hashed. Args: kind: {Kind} The kind to check. @@ -508,202 +519,209 @@ Returns: {bool} True if a value of this kind can be hashed. """ - checked = set() + checked = set() - def Check(kind): - if kind.spec in checked: - return True - checked.add(kind.spec) - if mojom.IsNullableKind(kind): + def Check(kind): + if kind.spec in checked: + return True + checked.add(kind.spec) + if mojom.IsNullableKind(kind): + return False + if mojom.IsStructKind(kind): + if kind.native_only: + return False + if (self._IsTypemappedKind(kind) and not self.typemap[ + self._GetFullMojomNameForKind(kind)]["hashable"]): + return False + return all(Check(field.kind) for field in kind.fields) + if mojom.IsEnumKind(kind): + return not self._IsTypemappedKind(kind) or self.typemap[ + self._GetFullMojomNameForKind(kind)]["hashable"] + if mojom.IsUnionKind(kind): + return all(Check(field.kind) for field in kind.fields) + if mojom.IsAnyHandleKind(kind): + return False + if mojom.IsAnyInterfaceKind(kind): + return False + # TODO(crbug.com/41326458): Arrays and maps could be made hashable. We just + # don't have a use case yet. + if mojom.IsArrayKind(kind): + return False + if mojom.IsMapKind(kind): + return False + return True + + return Check(kind) + + def _GetNativeTypeName(self, typemapped_kind): + return self.typemap[self._GetFullMojomNameForKind( + typemapped_kind)]["typename"] + + def _FormatConstantDeclaration(self, constant, nested=False): + if mojom.IsStringKind(constant.kind): + if nested: + return "const char %s[]" % constant.name + return "%sextern const char %s[]" % \ + ((self.export_attribute + " ") if self.export_attribute else "", + constant.name) + return "const %s %s_%s = %s" % (GetCppPodType( + constant.kind), self.module.namespace, constant.name, + self._ConstantValue(constant)) + + def _GetCppWrapperType(self, kind, add_same_module_namespaces=False): + + def _AddOptional(type_name): + return "std::optional<%s>" % type_name + + if self._IsTypemappedKind(kind): + type_name = self._GetNativeTypeName(kind) + if (mojom.IsNullableKind(kind) + and not self.typemap[self._GetFullMojomNameForKind( + kind)]["nullable_is_same_type"]): + type_name = _AddOptional(type_name) + return type_name + if mojom.IsEnumKind(kind): + return self._GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%s" % self._GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsArrayKind(kind): + pattern = "blink::Vector<%s>" if self.for_blink else "std::vector<%s>" + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % self._GetCppWrapperType( + kind.kind, + add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsMapKind(kind): + pattern = ("blink::HashMap<%s, %s>" + if self.for_blink else "std::unordered_map<%s, %s>") + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % ( + self._GetCppWrapperType( + kind.key_kind, + add_same_module_namespaces=add_same_module_namespaces), + self._GetCppWrapperType( + kind.value_kind, + add_same_module_namespaces=add_same_module_namespaces)) + if mojom.IsInterfaceKind(kind): + return "%sPtr" % self._GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequest" % self._GetNameForKind( + kind.kind, + add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfo" % self._GetNameForKind( + kind.kind, + add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequest" % self._GetNameForKind( + kind.kind, + add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStringKind(kind): + if self.for_blink: + return "blink::String" + type_name = "std::string" + return (_AddOptional(type_name) + if mojom.IsNullableKind(kind) else type_name) + if mojom.IsGenericHandleKind(kind): + return "Cronet_RawDataPtr" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + if not kind in _kind_to_cpp_type: + raise Exception("Unrecognized kind %s" % kind.spec) + return _kind_to_cpp_type[kind] + + def _IsMoveOnlyKind(self, kind): + if self._IsTypemappedKind(kind): + if mojom.IsEnumKind(kind): + return False + return self.typemap[self._GetFullMojomNameForKind( + kind)]["move_only"] + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return True + if mojom.IsArrayKind(kind): + return self._IsMoveOnlyKind(kind.kind) + if mojom.IsMapKind(kind): + return (self._IsMoveOnlyKind(kind.value_kind) + or self._IsMoveOnlyKind(kind.key_kind)) + if mojom.IsAnyHandleOrInterfaceKind(kind): + return True return False - if mojom.IsStructKind(kind): - if kind.native_only: - return False - if (self._IsTypemappedKind(kind) and - not self.typemap[self._GetFullMojomNameForKind(kind)]["hashable"]): - return False - return all(Check(field.kind) for field in kind.fields) - if mojom.IsEnumKind(kind): - return not self._IsTypemappedKind(kind) or self.typemap[ - self._GetFullMojomNameForKind(kind)]["hashable"] - if mojom.IsUnionKind(kind): - return all(Check(field.kind) for field in kind.fields) - if mojom.IsAnyHandleKind(kind): - return False - if mojom.IsAnyInterfaceKind(kind): - return False - # TODO(crbug.com/41326458): Arrays and maps could be made hashable. We just - # don't have a use case yet. - if mojom.IsArrayKind(kind): - return False - if mojom.IsMapKind(kind): - return False - return True - return Check(kind) + def _IsCopyablePassByValue(self, kind): + if not self._IsTypemappedKind(kind): + return False + return self.typemap[self._GetFullMojomNameForKind( + kind)]["copyable_pass_by_value"] - def _GetNativeTypeName(self, typemapped_kind): - return self.typemap[self._GetFullMojomNameForKind( - typemapped_kind)]["typename"] + def _ShouldPassParamByValue(self, kind): + return ((not mojom.IsReferenceKind(kind)) or self._IsMoveOnlyKind(kind) + or self._IsCopyablePassByValue(kind)) - def _FormatConstantDeclaration(self, constant, nested=False): - if mojom.IsStringKind(constant.kind): - if nested: - return "const char %s[]" % constant.name - return "%sextern const char %s[]" % \ - ((self.export_attribute + " ") if self.export_attribute else "", - constant.name) - return "const %s %s_%s = %s" % (GetCppPodType( - constant.kind), self.module.namespace, constant.name, - self._ConstantValue(constant)) + def _GetCWrapperType(self, kind): + if mojom.IsStringKind(kind): + return "Cronet_String" + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sPtr" % self._GetNameForKind(kind) + return self._GetCppWrapperType(kind) - def _GetCppWrapperType(self, kind, add_same_module_namespaces=False): + def _GetCppWrapperParamType(self, kind): + cpp_wrapper_type = self._GetCppWrapperType(kind) + return (cpp_wrapper_type if self._ShouldPassParamByValue(kind) else + "const %s&" % cpp_wrapper_type) - def _AddOptional(type_name): - return "std::optional<%s>" % type_name + def _GetCppFieldType(self, kind): + if mojom.IsStructKind(kind): + return ("mojo::internal::Pointer<%s>" % + self._GetNameForKind(kind, internal=True)) + if mojom.IsUnionKind(kind): + return "%s" % self._GetNameForKind(kind, internal=True) + if mojom.IsArrayKind(kind): + return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" % + self._GetCppFieldType(kind.kind)) + if mojom.IsMapKind(kind): + return ( + "mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" % + (self._GetCppFieldType( + kind.key_kind), self._GetCppFieldType(kind.value_kind))) + if mojom.IsInterfaceKind(kind): + return "mojo::internal::Interface_Data" + if mojom.IsInterfaceRequestKind(kind): + return "mojo::internal::Handle_Data" + if mojom.IsAssociatedInterfaceKind(kind): + return "mojo::internal::AssociatedInterface_Data" + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "mojo::internal::AssociatedEndpointHandle_Data" + if mojom.IsEnumKind(kind): + return "int32_t" + if mojom.IsStringKind(kind): + return "mojo::internal::Pointer<mojo::internal::String_Data>" + if mojom.IsAnyHandleKind(kind): + return "mojo::internal::Handle_Data" + return _kind_to_cpp_type[kind] - if self._IsTypemappedKind(kind): - type_name = self._GetNativeTypeName(kind) - if (mojom.IsNullableKind(kind) and not self.typemap[ - self._GetFullMojomNameForKind(kind)]["nullable_is_same_type"]): - type_name = _AddOptional(type_name) - return type_name - if mojom.IsEnumKind(kind): - return self._GetNameForKind( - kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): - return "%s" % self._GetNameForKind( - kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsArrayKind(kind): - pattern = "blink::Vector<%s>" if self.for_blink else "std::vector<%s>" - if mojom.IsNullableKind(kind): - pattern = _AddOptional(pattern) - return pattern % self._GetCppWrapperType( - kind.kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsMapKind(kind): - pattern = ("blink::HashMap<%s, %s>" - if self.for_blink else "std::unordered_map<%s, %s>") - if mojom.IsNullableKind(kind): - pattern = _AddOptional(pattern) - return pattern % ( - self._GetCppWrapperType( - kind.key_kind, - add_same_module_namespaces=add_same_module_namespaces), - self._GetCppWrapperType( - kind.value_kind, - add_same_module_namespaces=add_same_module_namespaces)) - if mojom.IsInterfaceKind(kind): - return "%sPtr" % self._GetNameForKind( - kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsInterfaceRequestKind(kind): - return "%sRequest" % self._GetNameForKind( - kind.kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsAssociatedInterfaceKind(kind): - return "%sAssociatedPtrInfo" % self._GetNameForKind( - kind.kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsAssociatedInterfaceRequestKind(kind): - return "%sAssociatedRequest" % self._GetNameForKind( - kind.kind, add_same_module_namespaces=add_same_module_namespaces) - if mojom.IsStringKind(kind): - if self.for_blink: - return "blink::String" - type_name = "std::string" - return (_AddOptional(type_name) - if mojom.IsNullableKind(kind) else type_name) - if mojom.IsGenericHandleKind(kind): - return "Cronet_RawDataPtr" - if mojom.IsDataPipeConsumerKind(kind): - return "mojo::ScopedDataPipeConsumerHandle" - if mojom.IsDataPipeProducerKind(kind): - return "mojo::ScopedDataPipeProducerHandle" - if mojom.IsMessagePipeKind(kind): - return "mojo::ScopedMessagePipeHandle" - if mojom.IsSharedBufferKind(kind): - return "mojo::ScopedSharedBufferHandle" - if not kind in _kind_to_cpp_type: - raise Exception("Unrecognized kind %s" % kind.spec) - return _kind_to_cpp_type[kind] + def _GetCppUnionFieldType(self, kind): + if mojom.IsUnionKind(kind): + return ("mojo::internal::Pointer<%s>" % + self._GetNameForKind(kind, internal=True)) + return self._GetCppFieldType(kind) - def _IsMoveOnlyKind(self, kind): - if self._IsTypemappedKind(kind): - if mojom.IsEnumKind(kind): - return False - return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"] - if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): - return True - if mojom.IsArrayKind(kind): - return self._IsMoveOnlyKind(kind.kind) - if mojom.IsMapKind(kind): - return (self._IsMoveOnlyKind(kind.value_kind) - or self._IsMoveOnlyKind(kind.key_kind)) - if mojom.IsAnyHandleOrInterfaceKind(kind): - return True - return False + def _GetUnionGetterReturnType(self, kind): + if mojom.IsReferenceKind(kind): + return "%s&" % self._GetCppWrapperType(kind) + return self._GetCppWrapperType(kind) - def _IsCopyablePassByValue(self, kind): - if not self._IsTypemappedKind(kind): - return False - return self.typemap[self._GetFullMojomNameForKind( - kind)]["copyable_pass_by_value"] - - def _ShouldPassParamByValue(self, kind): - return ((not mojom.IsReferenceKind(kind)) or self._IsMoveOnlyKind(kind) - or self._IsCopyablePassByValue(kind)) - - def _GetCWrapperType(self, kind): - if mojom.IsStringKind(kind): - return "Cronet_String" - if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): - return "%sPtr" % self._GetNameForKind(kind) - return self._GetCppWrapperType(kind) - - def _GetCppWrapperParamType(self, kind): - cpp_wrapper_type = self._GetCppWrapperType(kind) - return (cpp_wrapper_type if self._ShouldPassParamByValue(kind) else - "const %s&" % cpp_wrapper_type) - - def _GetCppFieldType(self, kind): - if mojom.IsStructKind(kind): - return ("mojo::internal::Pointer<%s>" % - self._GetNameForKind(kind, internal=True)) - if mojom.IsUnionKind(kind): - return "%s" % self._GetNameForKind(kind, internal=True) - if mojom.IsArrayKind(kind): - return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" % - self._GetCppFieldType(kind.kind)) - if mojom.IsMapKind(kind): - return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" % - (self._GetCppFieldType( - kind.key_kind), self._GetCppFieldType(kind.value_kind))) - if mojom.IsInterfaceKind(kind): - return "mojo::internal::Interface_Data" - if mojom.IsInterfaceRequestKind(kind): - return "mojo::internal::Handle_Data" - if mojom.IsAssociatedInterfaceKind(kind): - return "mojo::internal::AssociatedInterface_Data" - if mojom.IsAssociatedInterfaceRequestKind(kind): - return "mojo::internal::AssociatedEndpointHandle_Data" - if mojom.IsEnumKind(kind): - return "int32_t" - if mojom.IsStringKind(kind): - return "mojo::internal::Pointer<mojo::internal::String_Data>" - if mojom.IsAnyHandleKind(kind): - return "mojo::internal::Handle_Data" - return _kind_to_cpp_type[kind] - - def _GetCppUnionFieldType(self, kind): - if mojom.IsUnionKind(kind): - return ("mojo::internal::Pointer<%s>" % - self._GetNameForKind(kind, internal=True)) - return self._GetCppFieldType(kind) - - def _GetUnionGetterReturnType(self, kind): - if mojom.IsReferenceKind(kind): - return "%s&" % self._GetCppWrapperType(kind) - return self._GetCppWrapperType(kind) - - def _GetUnionTraitGetterReturnType(self, kind): - """Get field type used in UnionTraits template specialization. + def _GetUnionTraitGetterReturnType(self, kind): + """Get field type used in UnionTraits template specialization. The type may be qualified as UnionTraits specializations live outside the namespace where e.g. structs are defined. @@ -714,45 +732,45 @@ Returns: {str} The C++ type to use for the field. """ - if mojom.IsReferenceKind(kind): - return "%s&" % self._GetCppWrapperType(kind, - add_same_module_namespaces=True) - return self._GetCppWrapperType(kind, add_same_module_namespaces=True) + if mojom.IsReferenceKind(kind): + return "%s&" % self._GetCppWrapperType( + kind, add_same_module_namespaces=True) + return self._GetCppWrapperType(kind, add_same_module_namespaces=True) - def _TranslateConstants(self, token, kind): - if isinstance(token, mojom.NamedValue): - return self._GetNameForKind(token, flatten_nested_kind=True) + def _TranslateConstants(self, token, kind): + if isinstance(token, mojom.NamedValue): + return self._GetNameForKind(token, flatten_nested_kind=True) - if isinstance(token, mojom.BuiltinValue): - if token.value == "double.INFINITY": - return "std::numeric_limits<double>::infinity()" - if token.value == "float.INFINITY": - return "std::numeric_limits<float>::infinity()" - if token.value == "double.NEGATIVE_INFINITY": - return "-std::numeric_limits<double>::infinity()" - if token.value == "float.NEGATIVE_INFINITY": - return "-std::numeric_limits<float>::infinity()" - if token.value == "double.NAN": - return "std::numeric_limits<double>::quiet_NaN()" - if token.value == "float.NAN": - return "std::numeric_limits<float>::quiet_NaN()" + if isinstance(token, mojom.BuiltinValue): + if token.value == "double.INFINITY": + return "std::numeric_limits<double>::infinity()" + if token.value == "float.INFINITY": + return "std::numeric_limits<float>::infinity()" + if token.value == "double.NEGATIVE_INFINITY": + return "-std::numeric_limits<double>::infinity()" + if token.value == "float.NEGATIVE_INFINITY": + return "-std::numeric_limits<float>::infinity()" + if token.value == "double.NAN": + return "std::numeric_limits<double>::quiet_NaN()" + if token.value == "float.NAN": + return "std::numeric_limits<float>::quiet_NaN()" - if (kind is not None and mojom.IsFloatKind(kind)): - return token if token.isdigit() else token + "f" + if (kind is not None and mojom.IsFloatKind(kind)): + return token if token.isdigit() else token + "f" - return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, "")) + return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, "")) - def _ExpressionToText(self, value, kind=None): - return self._TranslateConstants(value, kind) + def _ExpressionToText(self, value, kind=None): + return self._TranslateConstants(value, kind) - def _ContainsMoveOnlyMembers(self, struct): - for field in struct.fields: - if self._IsMoveOnlyKind(field.kind): - return True - return False + def _ContainsMoveOnlyMembers(self, struct): + for field in struct.fields: + if self._IsMoveOnlyKind(field.kind): + return True + return False - def _GetStructConstructors(self, struct): - """Returns a list of constructors for a struct. + def _GetStructConstructors(self, struct): + """Returns a list of constructors for a struct. Params: struct: {Struct} The struct to return constructors for. @@ -761,107 +779,109 @@ {[StructConstructor]} A list of StructConstructors that should be generated for |struct|. """ - if not mojom.IsStructKind(struct): - raise TypeError - # Types that are neither copyable nor movable can't be passed to a struct - # constructor so only generate a default constructor. - if any( - self._IsTypemappedKind(field.kind) - and self.typemap[self._GetFullMojomNameForKind( - field.kind)]["non_copyable_non_movable"] - for field in struct.fields): - return [StructConstructor(struct.fields, [])] + if not mojom.IsStructKind(struct): + raise TypeError + # Types that are neither copyable nor movable can't be passed to a struct + # constructor so only generate a default constructor. + if any( + self._IsTypemappedKind(field.kind) + and self.typemap[self._GetFullMojomNameForKind( + field.kind)]["non_copyable_non_movable"] + for field in struct.fields): + return [StructConstructor(struct.fields, [])] - param_counts = [0] - for version in struct.versions: - if param_counts[-1] != version.num_fields: - param_counts.append(version.num_fields) + param_counts = [0] + for version in struct.versions: + if param_counts[-1] != version.num_fields: + param_counts.append(version.num_fields) - ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal) - return (StructConstructor(struct.fields, ordinal_fields[:param_count]) - for param_count in param_counts) + ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal) + return (StructConstructor(struct.fields, ordinal_fields[:param_count]) + for param_count in param_counts) - def _GetContainerValidateParamsCtorArgs(self, kind): - if mojom.IsStringKind(kind): - expected_num_elements = 0 - element_is_nullable = False - key_validate_params = "nullptr" - element_validate_params = "nullptr" - enum_validate_func = "nullptr" - elif mojom.IsMapKind(kind): - expected_num_elements = 0 - element_is_nullable = False - key_validate_params = self._GetNewContainerValidateParams( - mojom.Array(kind=kind.key_kind)) - element_validate_params = self._GetNewContainerValidateParams( - mojom.Array(kind=kind.value_kind)) - enum_validate_func = "nullptr" - else: # mojom.IsArrayKind(kind) - expected_num_elements = generator.ExpectedArraySize(kind) or 0 - element_is_nullable = mojom.IsNullableKind(kind.kind) - key_validate_params = "nullptr" - element_validate_params = self._GetNewContainerValidateParams(kind.kind) - if mojom.IsEnumKind(kind.kind): - enum_validate_func = ("%s::Validate" % self._GetQualifiedNameForKind( - kind.kind, internal=True, flatten_nested_kind=True)) - else: - enum_validate_func = "nullptr" + def _GetContainerValidateParamsCtorArgs(self, kind): + if mojom.IsStringKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = "nullptr" + element_validate_params = "nullptr" + enum_validate_func = "nullptr" + elif mojom.IsMapKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = self._GetNewContainerValidateParams( + mojom.Array(kind=kind.key_kind)) + element_validate_params = self._GetNewContainerValidateParams( + mojom.Array(kind=kind.value_kind)) + enum_validate_func = "nullptr" + else: # mojom.IsArrayKind(kind) + expected_num_elements = generator.ExpectedArraySize(kind) or 0 + element_is_nullable = mojom.IsNullableKind(kind.kind) + key_validate_params = "nullptr" + element_validate_params = self._GetNewContainerValidateParams( + kind.kind) + if mojom.IsEnumKind(kind.kind): + enum_validate_func = ( + "%s::Validate" % self._GetQualifiedNameForKind( + kind.kind, internal=True, flatten_nested_kind=True)) + else: + enum_validate_func = "nullptr" - if enum_validate_func == "nullptr": - if key_validate_params == "nullptr": - return "%d, %s, %s" % (expected_num_elements, - "true" if element_is_nullable else "false", - element_validate_params) - return "%s, %s" % (key_validate_params, element_validate_params) - return "%d, %s" % (expected_num_elements, enum_validate_func) + if enum_validate_func == "nullptr": + if key_validate_params == "nullptr": + return "%d, %s, %s" % (expected_num_elements, "true" + if element_is_nullable else "false", + element_validate_params) + return "%s, %s" % (key_validate_params, element_validate_params) + return "%d, %s" % (expected_num_elements, enum_validate_func) - def _GetNewContainerValidateParams(self, kind): - if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) - and not mojom.IsStringKind(kind)): - return "nullptr" + def _GetNewContainerValidateParams(self, kind): + if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) + and not mojom.IsStringKind(kind)): + return "nullptr" - return "new mojo::internal::ContainerValidateParams(%s)" % ( - self._GetContainerValidateParamsCtorArgs(kind)) + return "new mojo::internal::ContainerValidateParams(%s)" % ( + self._GetContainerValidateParamsCtorArgs(kind)) - def _GetCppDataViewType(self, kind, qualified=False): + def _GetCppDataViewType(self, kind, qualified=False): - def _GetName(input_kind): - return _NameFormatter(input_kind, None).FormatForCpp( - omit_namespace_for_module=(None if qualified else self.module), - flatten_nested_kind=True) + def _GetName(input_kind): + return _NameFormatter(input_kind, None).FormatForCpp( + omit_namespace_for_module=(None if qualified else self.module), + flatten_nested_kind=True) - if mojom.IsEnumKind(kind): - return _GetName(kind) - if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): - return "%sDataView" % _GetName(kind) - if mojom.IsArrayKind(kind): - return "mojo::ArrayDataView<%s>" % (self._GetCppDataViewType( - kind.kind, qualified)) - if mojom.IsMapKind(kind): - return ("mojo::MapDataView<%s, %s>" % - (self._GetCppDataViewType(kind.key_kind, qualified), - self._GetCppDataViewType(kind.value_kind, qualified))) - if mojom.IsStringKind(kind): - return "mojo::StringDataView" - if mojom.IsInterfaceKind(kind): - return "%sPtrDataView" % _GetName(kind) - if mojom.IsInterfaceRequestKind(kind): - return "%sRequestDataView" % _GetName(kind.kind) - if mojom.IsAssociatedInterfaceKind(kind): - return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind) - if mojom.IsAssociatedInterfaceRequestKind(kind): - return "%sAssociatedRequestDataView" % _GetName(kind.kind) - if mojom.IsGenericHandleKind(kind): - return "mojo::ScopedHandle" - if mojom.IsDataPipeConsumerKind(kind): - return "mojo::ScopedDataPipeConsumerHandle" - if mojom.IsDataPipeProducerKind(kind): - return "mojo::ScopedDataPipeProducerHandle" - if mojom.IsMessagePipeKind(kind): - return "mojo::ScopedMessagePipeHandle" - if mojom.IsSharedBufferKind(kind): - return "mojo::ScopedSharedBufferHandle" - return _kind_to_cpp_type[kind] + if mojom.IsEnumKind(kind): + return _GetName(kind) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sDataView" % _GetName(kind) + if mojom.IsArrayKind(kind): + return "mojo::ArrayDataView<%s>" % (self._GetCppDataViewType( + kind.kind, qualified)) + if mojom.IsMapKind(kind): + return ("mojo::MapDataView<%s, %s>" % + (self._GetCppDataViewType(kind.key_kind, qualified), + self._GetCppDataViewType(kind.value_kind, qualified))) + if mojom.IsStringKind(kind): + return "mojo::StringDataView" + if mojom.IsInterfaceKind(kind): + return "%sPtrDataView" % _GetName(kind) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequestDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequestDataView" % _GetName(kind.kind) + if mojom.IsGenericHandleKind(kind): + return "mojo::ScopedHandle" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] - def _GetUnmappedTypeForSerializer(self, kind): - return self._GetCppDataViewType(kind, qualified=True) + def _GetUnmappedTypeForSerializer(self, kind): + return self._GetCppDataViewType(kind, qualified=True)
diff --git a/components/cronet/tools/jar_src.py b/components/cronet/tools/jar_src.py index adb7ddcf..94244314 100755 --- a/components/cronet/tools/jar_src.py +++ b/components/cronet/tools/jar_src.py
@@ -26,108 +26,112 @@ def main(): - parser = argparse.ArgumentParser() - action_helpers.add_depfile_arg(parser) - parser.add_argument( - '--excluded-classes', - help='A list of .class file patterns to exclude from the jar.') - parser.add_argument( - '--src-search-dirs', - action='append', - help='A list of directories that should be searched' - ' for the source files.') - parser.add_argument( - '--src-files', action='append', help='A list of source files to jar.') - parser.add_argument( - '--src-jars', - action='append', - help='A list of source jars to include in addition to source files.') - parser.add_argument( - '--src-list-files', - action='append', - help='A list of files that contain a list of sources,' - ' e.g. a list of \'.sources\' files generated by GN.') - parser.add_argument('--jar-path', help='Jar output path.', required=True) + parser = argparse.ArgumentParser() + action_helpers.add_depfile_arg(parser) + parser.add_argument( + '--excluded-classes', + help='A list of .class file patterns to exclude from the jar.') + parser.add_argument('--src-search-dirs', + action='append', + help='A list of directories that should be searched' + ' for the source files.') + parser.add_argument('--src-files', + action='append', + help='A list of source files to jar.') + parser.add_argument( + '--src-jars', + action='append', + help='A list of source jars to include in addition to source files.') + parser.add_argument('--src-list-files', + action='append', + help='A list of files that contain a list of sources,' + ' e.g. a list of \'.sources\' files generated by GN.') + parser.add_argument('--jar-path', help='Jar output path.', required=True) - options = parser.parse_args() + options = parser.parse_args() - options.src_jars = action_helpers.parse_gn_list(options.src_jars) - options.src_search_dirs = action_helpers.parse_gn_list( - options.src_search_dirs) - options.src_list_files = action_helpers.parse_gn_list(options.src_list_files) - options.src_files = action_helpers.parse_gn_list(options.src_files) - options.excluded_classes = action_helpers.parse_gn_list( - options.excluded_classes) + options.src_jars = action_helpers.parse_gn_list(options.src_jars) + options.src_search_dirs = action_helpers.parse_gn_list( + options.src_search_dirs) + options.src_list_files = action_helpers.parse_gn_list( + options.src_list_files) + options.src_files = action_helpers.parse_gn_list(options.src_files) + options.excluded_classes = action_helpers.parse_gn_list( + options.excluded_classes) - src_files = options.src_files + src_files = options.src_files - # Add files from --source_list_files - for src_list_file in options.src_list_files: - with open(src_list_file, 'r') as f: - src_files.extend(f.read().splitlines()) + # Add files from --source_list_files + for src_list_file in options.src_list_files: + with open(src_list_file, 'r') as f: + src_files.extend(f.read().splitlines()) - # Preprocess source files by removing any prefix that comes before - # the Java package name. - for i, s in enumerate(src_files): - prefix_position = s.find(JAVA_PACKAGE_PREFIX) - if prefix_position == -1: - prefix_position = s.find(JNI_ZERO_PACKAGE_PREFIX) - if prefix_position != -1: - src_files[i] = s[prefix_position:] + # Preprocess source files by removing any prefix that comes before + # the Java package name. + for i, s in enumerate(src_files): + prefix_position = s.find(JAVA_PACKAGE_PREFIX) + if prefix_position == -1: + prefix_position = s.find(JNI_ZERO_PACKAGE_PREFIX) + if prefix_position != -1: + src_files[i] = s[prefix_position:] - excluded_classes = [ - f.replace('.class', '.java') for f in options.excluded_classes - ] + excluded_classes = [ + f.replace('.class', '.java') for f in options.excluded_classes + ] - predicate = None - if excluded_classes: - predicate = lambda f: not build_utils.MatchesGlob(f, excluded_classes) + predicate = None + if excluded_classes: + predicate = lambda f: not build_utils.MatchesGlob(f, excluded_classes) - # Create a dictionary that maps every source directory - # to source files that it contains. - dir_to_files_map = {} - # Initialize the map. - for src_search_dir in options.src_search_dirs: - dir_to_files_map[src_search_dir] = [] - # Fill the map. - for src_file in src_files: - number_of_file_instances = 0 + # Create a dictionary that maps every source directory + # to source files that it contains. + dir_to_files_map = {} + # Initialize the map. for src_search_dir in options.src_search_dirs: - target_path = os.path.join(src_search_dir, src_file) - if os.path.isfile(target_path): - number_of_file_instances += 1 - if not predicate or predicate(src_file): - dir_to_files_map[src_search_dir].append(target_path) - if (number_of_file_instances > 1): - raise Exception('There is more than one instance of file %s in %s' % - (src_file, options.src_search_dirs)) - if (number_of_file_instances < 1): - raise Exception('Unable to find file %s in %s' % - (src_file, options.src_search_dirs)) + dir_to_files_map[src_search_dir] = [] + # Fill the map. + for src_file in src_files: + number_of_file_instances = 0 + for src_search_dir in options.src_search_dirs: + target_path = os.path.join(src_search_dir, src_file) + if os.path.isfile(target_path): + number_of_file_instances += 1 + if not predicate or predicate(src_file): + dir_to_files_map[src_search_dir].append(target_path) + if (number_of_file_instances > 1): + raise Exception( + 'There is more than one instance of file %s in %s' % + (src_file, options.src_search_dirs)) + if (number_of_file_instances < 1): + raise Exception('Unable to find file %s in %s' % + (src_file, options.src_search_dirs)) - # Jar the sources from every source search directory. - with action_helpers.atomic_output(options.jar_path) as o, \ - zipfile.ZipFile(o, 'w', zipfile.ZIP_DEFLATED) as z: - for src_search_dir in options.src_search_dirs: - subpaths = dir_to_files_map[src_search_dir] - if subpaths: - zip_helpers.add_files_to_zip(subpaths, z, base_dir=src_search_dir) - else: - raise Exception( - 'Directory %s does not contain any files and can be' - ' removed from the list of directories to search' % src_search_dir) + # Jar the sources from every source search directory. + with action_helpers.atomic_output(options.jar_path) as o, \ + zipfile.ZipFile(o, 'w', zipfile.ZIP_DEFLATED) as z: + for src_search_dir in options.src_search_dirs: + subpaths = dir_to_files_map[src_search_dir] + if subpaths: + zip_helpers.add_files_to_zip(subpaths, + z, + base_dir=src_search_dir) + else: + raise Exception( + 'Directory %s does not contain any files and can be' + ' removed from the list of directories to search' % + src_search_dir) - # Jar additional src jars - if options.src_jars: - zip_helpers.merge_zips(z, options.src_jars, compress=True) + # Jar additional src jars + if options.src_jars: + zip_helpers.merge_zips(z, options.src_jars, compress=True) - if options.depfile: - deps = [] - for sources in dir_to_files_map.values(): - deps.extend(sources) - # Srcjar deps already captured in GN rules (no need to list them here). - action_helpers.write_depfile(options.depfile, options.jar_path, deps) + if options.depfile: + deps = [] + for sources in dir_to_files_map.values(): + deps.extend(sources) + # Srcjar deps already captured in GN rules (no need to list them here). + action_helpers.write_depfile(options.depfile, options.jar_path, deps) if __name__ == '__main__': - sys.exit(main()) + sys.exit(main())
diff --git a/components/cronet/tools/link_dependencies.py b/components/cronet/tools/link_dependencies.py index a4c21c9..fd588e75 100755 --- a/components/cronet/tools/link_dependencies.py +++ b/components/cronet/tools/link_dependencies.py
@@ -3,7 +3,6 @@ # Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """Links the deps of a binary into a static library. Run with a working directory, the name of a binary target, and the name of the @@ -22,11 +21,11 @@ class SubprocessError(Exception): - pass + pass def extract_inputs(query_result, prefix=''): - """Extracts inputs from ninja query output. + """Extracts inputs from ninja query output. Given 'ninja -t query' output for a target, extracts all the inputs of that target, prefixing them with an optional prefix. Inputs prefixed with '|' are @@ -58,20 +57,20 @@ Returns: A list of the inputs. """ - extracting = False - inputs = [] - for line in query_result.splitlines(): - if line.startswith(' input:'): - extracting = True - elif line.startswith(' outputs:'): - extracting = False - elif extracting and '|' not in line: - inputs.append(os.path.join(prefix, line.strip())) - return inputs + extracting = False + inputs = [] + for line in query_result.splitlines(): + if line.startswith(' input:'): + extracting = True + elif line.startswith(' outputs:'): + extracting = False + elif extracting and '|' not in line: + inputs.append(os.path.join(prefix, line.strip())) + return inputs def query_ninja(target, workdir, prefix=''): - """Returns the inputs for the named target. + """Returns the inputs for the named target. Queries ninja for the set of inputs of the named target, then returns the list of inputs to that target. @@ -84,19 +83,19 @@ Returns: A list of file system paths to the inputs to the named target. """ - proc = subprocess.Popen(['ninja', '-C', workdir, '-t', 'query', target], - stdout=subprocess.PIPE) - stdout, _ = proc.communicate() - return extract_inputs(stdout, prefix) + proc = subprocess.Popen(['ninja', '-C', workdir, '-t', 'query', target], + stdout=subprocess.PIPE) + stdout, _ = proc.communicate() + return extract_inputs(stdout, prefix) def is_library(target): - """Returns whether target is a library file.""" - return os.path.splitext(target)[1] in ('.a', '.o') + """Returns whether target is a library file.""" + return os.path.splitext(target)[1] in ('.a', '.o') def library_deps(targets, workdir, query=query_ninja): - """Returns the set of library dependencies for the supplied targets. + """Returns the set of library dependencies for the supplied targets. The entries in the targets list can be either a static library, an object file, or an executable. Static libraries and object files are incorporated @@ -112,46 +111,49 @@ Returns: Set of library dependencies. """ - deps = set() - for target in targets: - if is_library(target): - deps.add(os.path.join(workdir, target)) - else: - deps = deps.union(query(target, workdir, workdir)) - return deps + deps = set() + for target in targets: + if is_library(target): + deps.add(os.path.join(workdir, target)) + else: + deps = deps.union(query(target, workdir, workdir)) + return deps def link(output, inputs): - """Links output from inputs using libtool. + """Links output from inputs using libtool. Args: output: file system path to desired output library inputs: list of file system paths to input libraries """ - libtool_re = re.compile(r'^.*libtool: (?:for architecture: \S* )?' - r'file: .* has no symbols$') - p = subprocess.Popen( - ['libtool', '-o', output] + inputs, stderr=subprocess.PIPE) - _, err = p.communicate() - for line in err.splitlines(): - if not libtool_re.match(line): - sys.stderr.write(line) - if p.returncode != 0: - message = "subprocess libtool returned {0}".format(p.returncode) - raise SubprocessError(message) + libtool_re = re.compile(r'^.*libtool: (?:for architecture: \S* )?' + r'file: .* has no symbols$') + p = subprocess.Popen(['libtool', '-o', output] + inputs, + stderr=subprocess.PIPE) + _, err = p.communicate() + for line in err.splitlines(): + if not libtool_re.match(line): + sys.stderr.write(line) + if p.returncode != 0: + message = "subprocess libtool returned {0}".format(p.returncode) + raise SubprocessError(message) def main(): - parser = argparse.ArgumentParser( - description='Link dependencies of a ninja target into a static library') - parser.add_argument('workdir', nargs=1, help='ninja working directory') - parser.add_argument('target', nargs=1, help='target to query for deps') - parser.add_argument('output', nargs=1, help='path to output static library') - args = parser.parse_args() + parser = argparse.ArgumentParser( + description='Link dependencies of a ninja target into a static library' + ) + parser.add_argument('workdir', nargs=1, help='ninja working directory') + parser.add_argument('target', nargs=1, help='target to query for deps') + parser.add_argument('output', + nargs=1, + help='path to output static library') + args = parser.parse_args() - inputs = query_ninja(args.target[0], args.workdir[0]) - link(args.output[0], list(library_deps(inputs, args.workdir[0]))) + inputs = query_ninja(args.target[0], args.workdir[0]) + link(args.output[0], list(library_deps(inputs, args.workdir[0]))) if __name__ == '__main__': - main() + main()
diff --git a/components/cronet/tools/perf_test_utils.py b/components/cronet/tools/perf_test_utils.py index 122ffea..04efab5 100755 --- a/components/cronet/tools/perf_test_utils.py +++ b/components/cronet/tools/perf_test_utils.py
@@ -15,9 +15,8 @@ # pylint: disable=useless-object-inheritance - -REPOSITORY_ROOT = os.path.abspath(os.path.join( - os.path.dirname(__file__), '..', '..', '..')) +REPOSITORY_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..')) BUILD_TYPE = 'Release' BUILD_DIR = os.path.join(REPOSITORY_ROOT, 'out', BUILD_TYPE) QUIC_SERVER = os.path.join(BUILD_DIR, 'quic_server') @@ -35,31 +34,31 @@ # TODO(pauljensen): Consider whether we can avoid loading this # DEFAULT_BENCHMARK_CONFIG dict into globals. DEFAULT_BENCHMARK_CONFIG = { - # Control various metric recording for further investigation. - 'CAPTURE_NETLOG': False, - 'CAPTURE_TRACE': False, - 'CAPTURE_SAMPLED_TRACE': False, - # While running Cronet Async API benchmarks, indicate if callbacks should be - # run on network thread rather than posted back to caller thread. This allows - # measuring if thread-hopping overhead is significant. - 'CRONET_ASYNC_USE_NETWORK_THREAD': False, - # A small resource for device to fetch from host. - 'SMALL_RESOURCE': 'small.html', - 'SMALL_RESOURCE_SIZE': 26, - # Number of times to fetch SMALL_RESOURCE. - 'SMALL_ITERATIONS': 1000, - # A large resource for device to fetch from host. - 'LARGE_RESOURCE': 'large.html', - 'LARGE_RESOURCE_SIZE': 10000026, - # Number of times to fetch LARGE_RESOURCE. - 'LARGE_ITERATIONS': 4, - # Ports of HTTP and QUIC servers on host. - 'HTTP_PORT': 9000, - 'QUIC_PORT': 9001, - # Maximum read/write buffer size to use. - 'MAX_BUFFER_SIZE': 16384, - 'HOST': QUIC_CERT_HOST, - 'QUIC_CERT_FILE': QUIC_CERT_FILENAME, + # Control various metric recording for further investigation. + 'CAPTURE_NETLOG': False, + 'CAPTURE_TRACE': False, + 'CAPTURE_SAMPLED_TRACE': False, + # While running Cronet Async API benchmarks, indicate if callbacks should be + # run on network thread rather than posted back to caller thread. This allows + # measuring if thread-hopping overhead is significant. + 'CRONET_ASYNC_USE_NETWORK_THREAD': False, + # A small resource for device to fetch from host. + 'SMALL_RESOURCE': 'small.html', + 'SMALL_RESOURCE_SIZE': 26, + # Number of times to fetch SMALL_RESOURCE. + 'SMALL_ITERATIONS': 1000, + # A large resource for device to fetch from host. + 'LARGE_RESOURCE': 'large.html', + 'LARGE_RESOURCE_SIZE': 10000026, + # Number of times to fetch LARGE_RESOURCE. + 'LARGE_ITERATIONS': 4, + # Ports of HTTP and QUIC servers on host. + 'HTTP_PORT': 9000, + 'QUIC_PORT': 9001, + # Maximum read/write buffer size to use. + 'MAX_BUFFER_SIZE': 16384, + 'HOST': QUIC_CERT_HOST, + 'QUIC_CERT_FILE': QUIC_CERT_FILENAME, } # Add benchmark config to global state for easy access. globals().update(DEFAULT_BENCHMARK_CONFIG) @@ -68,126 +67,139 @@ # bad string format type warnings. #pylint: disable=undefined-variable,bad-string-format-type + class NativeDevice(object): - def GetExternalStoragePath(self): - return '/tmp' - def RunShellCommand(self, cmd, check_return=False): - if check_return: - subprocess.check_call(cmd) - else: - subprocess.call(cmd) + def GetExternalStoragePath(self): + return '/tmp' - def WriteFile(self, path, data): - with open(path, 'w') as f: - f.write(data) + def RunShellCommand(self, cmd, check_return=False): + if check_return: + subprocess.check_call(cmd) + else: + subprocess.call(cmd) + + def WriteFile(self, path, data): + with open(path, 'w') as f: + f.write(data) + def GetConfig(device): - config = DEFAULT_BENCHMARK_CONFIG - config['HOST_IP'] = GetServersHost(device) - if isinstance(device, NativeDevice): - config['RESULTS_FILE'] = '/tmp/cronet_perf_test_results.txt' - config['DONE_FILE'] = '/tmp/cronet_perf_test_done.txt' - else: - # An on-device file containing benchmark timings. Written by benchmark app. - config['RESULTS_FILE'] = '/data/data/' + APP_PACKAGE + '/results.txt' - # An on-device file whose presence indicates benchmark app has terminated. - config['DONE_FILE'] = '/data/data/' + APP_PACKAGE + '/done.txt' - return config + config = DEFAULT_BENCHMARK_CONFIG + config['HOST_IP'] = GetServersHost(device) + if isinstance(device, NativeDevice): + config['RESULTS_FILE'] = '/tmp/cronet_perf_test_results.txt' + config['DONE_FILE'] = '/tmp/cronet_perf_test_done.txt' + else: + # An on-device file containing benchmark timings. Written by benchmark app. + config['RESULTS_FILE'] = '/data/data/' + APP_PACKAGE + '/results.txt' + # An on-device file whose presence indicates benchmark app has terminated. + config['DONE_FILE'] = '/data/data/' + APP_PACKAGE + '/done.txt' + return config def GetAndroidRndisConfig(device): - return android_rndis_forwarder.AndroidRndisConfigurator(device) + return android_rndis_forwarder.AndroidRndisConfigurator(device) def GetServersHost(device): - if isinstance(device, NativeDevice): - return '127.0.0.1' - return GetAndroidRndisConfig(device).host_ip + if isinstance(device, NativeDevice): + return '127.0.0.1' + return GetAndroidRndisConfig(device).host_ip def GetHttpServerURL(device, resource): - return 'http://%s:%d/%s' % (GetServersHost(device), HTTP_PORT, resource) + return 'http://%s:%d/%s' % (GetServersHost(device), HTTP_PORT, resource) class QuicServer(object): - def __init__(self, quic_server_doc_root): - self._process = None - self._quic_server_doc_root = quic_server_doc_root + def __init__(self, quic_server_doc_root): + self._process = None + self._quic_server_doc_root = quic_server_doc_root - def StartupQuicServer(self, device): - cmd = [QUIC_SERVER, - '--quic_response_cache_dir=%s' % self._quic_server_doc_root, - '--certificate_file=%s' % QUIC_CERT, - '--key_file=%s' % QUIC_KEY, - '--port=%d' % QUIC_PORT] - logging.info("Starting Quic Server: %s", cmd) - self._process = subprocess.Popen(cmd) - assert self._process is not None - # Wait for quic_server to start serving. - waited_s = 0 - while subprocess.call(['lsof', '-i', 'udp:%d' % QUIC_PORT, '-p', - '%d' % self._process.pid], - stdout=open(os.devnull, 'w')) != 0: - sleep(0.1) - waited_s += 0.1 - assert waited_s < 5, "quic_server failed to start after %fs" % waited_s - # Push certificate to device. - cert = open(QUIC_CERT, 'r').read() - device_cert_path = posixpath.join( - device.GetExternalStoragePath(), 'chromium_tests_root', CERT_PATH) - device.RunShellCommand(['mkdir', '-p', device_cert_path], check_return=True) - device.WriteFile(os.path.join(device_cert_path, QUIC_CERT_FILENAME), cert) + def StartupQuicServer(self, device): + cmd = [ + QUIC_SERVER, + '--quic_response_cache_dir=%s' % self._quic_server_doc_root, + '--certificate_file=%s' % QUIC_CERT, + '--key_file=%s' % QUIC_KEY, + '--port=%d' % QUIC_PORT + ] + logging.info("Starting Quic Server: %s", cmd) + self._process = subprocess.Popen(cmd) + assert self._process is not None + # Wait for quic_server to start serving. + waited_s = 0 + while subprocess.call([ + 'lsof', '-i', + 'udp:%d' % QUIC_PORT, '-p', + '%d' % self._process.pid + ], + stdout=open(os.devnull, 'w')) != 0: + sleep(0.1) + waited_s += 0.1 + assert waited_s < 5, "quic_server failed to start after %fs" % waited_s + # Push certificate to device. + cert = open(QUIC_CERT, 'r').read() + device_cert_path = posixpath.join(device.GetExternalStoragePath(), + 'chromium_tests_root', CERT_PATH) + device.RunShellCommand(['mkdir', '-p', device_cert_path], + check_return=True) + device.WriteFile(os.path.join(device_cert_path, QUIC_CERT_FILENAME), + cert) - def ShutdownQuicServer(self): - if self._process: - self._process.terminate() + def ShutdownQuicServer(self): + if self._process: + self._process.terminate() def GenerateHttpTestResources(): - http_server_doc_root = tempfile.mkdtemp() - # Create a small test file to serve. - small_file_name = os.path.join(http_server_doc_root, SMALL_RESOURCE) - small_file = open(small_file_name, 'wb') - small_file.write('<html><body></body></html>'); - small_file.close() - assert SMALL_RESOURCE_SIZE == os.path.getsize(small_file_name) - # Create a large (10MB) test file to serve. - large_file_name = os.path.join(http_server_doc_root, LARGE_RESOURCE) - large_file = open(large_file_name, 'wb') - large_file.write('<html><body>'); - for _ in range(0, 1000000): - large_file.write('1234567890'); - large_file.write('</body></html>'); - large_file.close() - assert LARGE_RESOURCE_SIZE == os.path.getsize(large_file_name) - return http_server_doc_root + http_server_doc_root = tempfile.mkdtemp() + # Create a small test file to serve. + small_file_name = os.path.join(http_server_doc_root, SMALL_RESOURCE) + small_file = open(small_file_name, 'wb') + small_file.write('<html><body></body></html>') + small_file.close() + assert SMALL_RESOURCE_SIZE == os.path.getsize(small_file_name) + # Create a large (10MB) test file to serve. + large_file_name = os.path.join(http_server_doc_root, LARGE_RESOURCE) + large_file = open(large_file_name, 'wb') + large_file.write('<html><body>') + for _ in range(0, 1000000): + large_file.write('1234567890') + large_file.write('</body></html>') + large_file.close() + assert LARGE_RESOURCE_SIZE == os.path.getsize(large_file_name) + return http_server_doc_root def GenerateQuicTestResources(device): - quic_server_doc_root = tempfile.mkdtemp() - # Use wget to build up fake QUIC in-memory cache dir for serving. - # quic_server expects the dir/file layout that wget produces. - for resource in [SMALL_RESOURCE, LARGE_RESOURCE]: - assert subprocess.Popen(['wget', '-p', '-q', '--save-headers', - GetHttpServerURL(device, resource)], - cwd=quic_server_doc_root).wait() == 0 - # wget places results in host:port directory. Adjust for QUIC port. - os.rename(os.path.join(quic_server_doc_root, - "%s:%d" % (GetServersHost(device), HTTP_PORT)), - os.path.join(quic_server_doc_root, - "%s:%d" % (QUIC_CERT_HOST, QUIC_PORT))) - return quic_server_doc_root + quic_server_doc_root = tempfile.mkdtemp() + # Use wget to build up fake QUIC in-memory cache dir for serving. + # quic_server expects the dir/file layout that wget produces. + for resource in [SMALL_RESOURCE, LARGE_RESOURCE]: + assert subprocess.Popen([ + 'wget', '-p', '-q', '--save-headers', + GetHttpServerURL(device, resource) + ], + cwd=quic_server_doc_root).wait() == 0 + # wget places results in host:port directory. Adjust for QUIC port. + os.rename( + os.path.join(quic_server_doc_root, + "%s:%d" % (GetServersHost(device), HTTP_PORT)), + os.path.join(quic_server_doc_root, + "%s:%d" % (QUIC_CERT_HOST, QUIC_PORT))) + return quic_server_doc_root def GenerateLighttpdConfig(config_file, http_server_doc_root, http_server): - # Must create customized config file to allow overriding the server.bind - # setting. - config_file.write('server.document-root = "%s"\n' % http_server_doc_root) - config_file.write('server.port = %d\n' % HTTP_PORT) - # These lines are added so lighttpd_server.py's internal test succeeds. - config_file.write('server.tag = "%s"\n' % http_server.server_tag) - config_file.write('server.pid-file = "%s"\n' % http_server.pid_file) - config_file.write('dir-listing.activate = "enable"\n') - config_file.flush() + # Must create customized config file to allow overriding the server.bind + # setting. + config_file.write('server.document-root = "%s"\n' % http_server_doc_root) + config_file.write('server.port = %d\n' % HTTP_PORT) + # These lines are added so lighttpd_server.py's internal test succeeds. + config_file.write('server.tag = "%s"\n' % http_server.server_tag) + config_file.write('server.pid-file = "%s"\n' % http_server.pid_file) + config_file.write('dir-listing.activate = "enable"\n') + config_file.flush()
diff --git a/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection.py b/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection.py index 644a1005..0d6b538 100644 --- a/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection.py +++ b/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection.py
@@ -14,69 +14,69 @@ def filter_undefined_only(line): - return "*UND*" in line + return "*UND*" in line def filter_anything_but_undefined(line): - return "*UND*" not in line + return "*UND*" not in line def parse_args(): - parser = argparse.ArgumentParser( - description= - "Generate a linker script that strips symbols from a shared library that are not used from another shared library." - ) - parser.add_argument("--objdump", - required=True, - help="Path to llvm-objdump tool") - parser.add_argument( - "--lib-undefined", - required=True, - help="Library containing undefined symbols (e.g., cronet)") - parser.add_argument("--lib-defined", - required=True, - help="Library providing the symbols (e.g., libcrypto)") - parser.add_argument("--out", - required=True, - help="Path to the output map file") - return parser.parse_args() + parser = argparse.ArgumentParser( + description= + "Generate a linker script that strips symbols from a shared library that are not used from another shared library." + ) + parser.add_argument("--objdump", + required=True, + help="Path to llvm-objdump tool") + parser.add_argument( + "--lib-undefined", + required=True, + help="Library containing undefined symbols (e.g., cronet)") + parser.add_argument("--lib-defined", + required=True, + help="Library providing the symbols (e.g., libcrypto)") + parser.add_argument("--out", + required=True, + help="Path to the output map file") + return parser.parse_args() def get_symbols(objdump_path, lib_path): - return cronet_utils.run_and_get_stdout([objdump_path, "-T", lib_path]) + return cronet_utils.run_and_get_stdout([objdump_path, "-T", lib_path]) def parse_and_filter_objdump_symbols(objdump_text, filter_fn): - symbols = set() - seen_dynamic_table_header = False - for line in objdump_text.splitlines(): - line = line.strip() - if not line: - continue + symbols = set() + seen_dynamic_table_header = False + for line in objdump_text.splitlines(): + line = line.strip() + if not line: + continue - if "DYNAMIC SYMBOL TABLE:" in line: - seen_dynamic_table_header = True - continue + if "DYNAMIC SYMBOL TABLE:" in line: + seen_dynamic_table_header = True + continue - if not seen_dynamic_table_header: - continue + if not seen_dynamic_table_header: + continue - if filter_fn(line): - symbols.add(line.split()[-1]) + if filter_fn(line): + symbols.add(line.split()[-1]) - return symbols + return symbols def main(): - args = parse_args() - undefined_symbols = parse_and_filter_objdump_symbols( - get_symbols(args.objdump, args.lib_undefined), filter_undefined_only) - defined_symbols = parse_and_filter_objdump_symbols( - get_symbols(args.objdump, args.lib_defined), - filter_anything_but_undefined) - intersection = sorted(undefined_symbols.intersection(defined_symbols)) - cronet_utils.write_file( - args.out, """ + args = parse_args() + undefined_symbols = parse_and_filter_objdump_symbols( + get_symbols(args.objdump, args.lib_undefined), filter_undefined_only) + defined_symbols = parse_and_filter_objdump_symbols( + get_symbols(args.objdump, args.lib_defined), + filter_anything_but_undefined) + intersection = sorted(undefined_symbols.intersection(defined_symbols)) + cronet_utils.write_file( + args.out, """ INTERSECTED_SYMBOLS {{ global: {symbols}; @@ -86,4 +86,4 @@ if __name__ == "__main__": - main() + main()
diff --git a/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection_unittest.py b/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection_unittest.py index 456bd95..75c44a7 100644 --- a/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection_unittest.py +++ b/components/cronet/tools/symbols_map_generator/generate_version_script_from_symbols_intersection_unittest.py
@@ -16,27 +16,27 @@ import components.cronet.tools.symbols_map_generator.generate_version_script_from_symbols_intersection as intersection_generator # pylint: disable=wrong-import-position import components.cronet.tools.utils as cronet_utils # pylint: disable=wrong-import-position -SYMBOLS_OBJDUMP = os.path.join(REPOSITORY_ROOT, 'components', 'cronet', 'tools', - 'symbols_map_generator', 'test_data', +SYMBOLS_OBJDUMP = os.path.join(REPOSITORY_ROOT, 'components', 'cronet', + 'tools', 'symbols_map_generator', 'test_data', 'symbols_objdump.txt') class TestBreakagesJson(unittest.TestCase): - def test_ensure_parsing_of_undefined_symbols_is_correct(self): - self.assertEqual( - intersection_generator.parse_and_filter_objdump_symbols( - cronet_utils.read_file(SYMBOLS_OBJDUMP), - filter_fn=intersection_generator.filter_undefined_only), - set(["EVP_aead_aes_256_cbc_sha1_tls_implicit_iv", "boo"])) + def test_ensure_parsing_of_undefined_symbols_is_correct(self): + self.assertEqual( + intersection_generator.parse_and_filter_objdump_symbols( + cronet_utils.read_file(SYMBOLS_OBJDUMP), + filter_fn=intersection_generator.filter_undefined_only), + set(["EVP_aead_aes_256_cbc_sha1_tls_implicit_iv", "boo"])) - def test_ensure_parsing_of_defined_symbols_is_correct(self): - self.assertEqual( - intersection_generator.parse_and_filter_objdump_symbols( - cronet_utils.read_file(SYMBOLS_OBJDUMP), - filter_fn=intersection_generator.filter_anything_but_undefined), - set(["Java_android_net_http_internal_J_N_Mx3geLfM", "foo"])) + def test_ensure_parsing_of_defined_symbols_is_correct(self): + self.assertEqual( + intersection_generator.parse_and_filter_objdump_symbols( + cronet_utils.read_file(SYMBOLS_OBJDUMP), + filter_fn=intersection_generator.filter_anything_but_undefined + ), set(["Java_android_net_http_internal_J_N_Mx3geLfM", "foo"])) if __name__ == '__main__': - unittest.main() + unittest.main()
diff --git a/components/cronet/tools/update_api.py b/components/cronet/tools/update_api.py index 3645285..abcf8626 100755 --- a/components/cronet/tools/update_api.py +++ b/components/cronet/tools/update_api.py
@@ -2,7 +2,6 @@ # Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """update_api.py - Update committed Cronet API.""" import argparse @@ -14,7 +13,6 @@ import sys import tempfile - REPOSITORY_ROOT = os.path.abspath( os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) @@ -22,11 +20,12 @@ from util import build_utils # pylint: disable=wrong-import-position # Filename of dump of current API. -API_FILENAME = os.path.abspath(os.path.join( - os.path.dirname(__file__), '..', 'android', 'api.txt')) +API_FILENAME = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', 'android', 'api.txt')) # Filename of file containing API version number. API_VERSION_FILENAME = os.path.abspath( - os.path.join(os.path.dirname(__file__), '..', 'android', 'api_version.txt')) + os.path.join(os.path.dirname(__file__), '..', 'android', + 'api_version.txt')) # Regular expression that catches the beginning of lines that declare classes. # The first group returned by a match is the class name. @@ -46,147 +45,157 @@ def _split_by_class(javap_output): - """Splits the combined javap output to separate classes. + """Splits the combined javap output to separate classes. * Removes unneeded comments like "Compiled from ...". * Sorts the declarations inside the class. Returns an array where each element represents a class. """ - current_class_lines = [] - all_classes = [] - for line in javap_output: - # Lines starting with Compiled from are just comments and not part of the - # api. - if line.startswith('Compiled from'): - continue - current_class_lines.append(line) - if line == '}': - # sort only the lines between the {}. - current_class_lines = ([current_class_lines[0]] + - sorted(current_class_lines[1:-1]) + - [current_class_lines[-1]]) - all_classes.append(current_class_lines) - current_class_lines = [] - return all_classes + current_class_lines = [] + all_classes = [] + for line in javap_output: + # Lines starting with Compiled from are just comments and not part of the + # api. + if line.startswith('Compiled from'): + continue + current_class_lines.append(line) + if line == '}': + # sort only the lines between the {}. + current_class_lines = ([current_class_lines[0]] + + sorted(current_class_lines[1:-1]) + + [current_class_lines[-1]]) + all_classes.append(current_class_lines) + current_class_lines = [] + return all_classes def _generate_api(api_jar, output_filename): - """Dumps the API in |api_jar| into |outpuf_filename|.""" - # Extract API class files from api_jar. - with tempfile.TemporaryDirectory() as temp_dir: - api_jar_path = os.path.abspath(api_jar) - jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf', api_jar_path] - build_utils.CheckOutput(jar_cmd, cwd=temp_dir) + """Dumps the API in |api_jar| into |outpuf_filename|.""" + # Extract API class files from api_jar. + with tempfile.TemporaryDirectory() as temp_dir: + api_jar_path = os.path.abspath(api_jar) + jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf', api_jar_path] + build_utils.CheckOutput(jar_cmd, cwd=temp_dir) - shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) + shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True) - # Collect paths of all API class files - api_class_files = [] - for root, _, filenames in os.walk(temp_dir): - api_class_files += [os.path.join(root, f) for f in filenames] - api_class_files.sort() + # Collect paths of all API class files + api_class_files = [] + for root, _, filenames in os.walk(temp_dir): + api_class_files += [os.path.join(root, f) for f in filenames] + api_class_files.sort() - output_lines = ['DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT\n'] - javap_cmd = [JAVAP_PATH, '-protected'] + api_class_files - javap_output = build_utils.CheckOutput(javap_cmd) + output_lines = [ + 'DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT\n' + ] + javap_cmd = [JAVAP_PATH, '-protected'] + api_class_files + javap_output = build_utils.CheckOutput(javap_cmd) - all_classes = _split_by_class(javap_output.splitlines()) - for class_lines in all_classes: - first_line = class_lines[0] - # Skip classes we do not care about. - if UNNAMED_CLASS_RE.match(first_line) or INTERNAL_CLASS_RE.match( - first_line): - continue - output_lines.extend(class_lines) + all_classes = _split_by_class(javap_output.splitlines()) + for class_lines in all_classes: + first_line = class_lines[0] + # Skip classes we do not care about. + if UNNAMED_CLASS_RE.match(first_line) or INTERNAL_CLASS_RE.match( + first_line): + continue + output_lines.extend(class_lines) - output_string = '\n'.join(output_lines) + '\n' - md5_hash = hashlib.md5() - md5_hash.update(output_string.encode('utf-8')) - output_string += 'Stamp: %s\n' % md5_hash.hexdigest() + output_string = '\n'.join(output_lines) + '\n' + md5_hash = hashlib.md5() + md5_hash.update(output_string.encode('utf-8')) + output_string += 'Stamp: %s\n' % md5_hash.hexdigest() - with open(output_filename, 'w') as output_file: - output_file.write(output_string) + with open(output_filename, 'w') as output_file: + output_file.write(output_string) def check_up_to_date(api_jar): - """Returns True if API_FILENAME matches the API exposed by |api_jar|.""" - with tempfile.NamedTemporaryFile() as temp: - _generate_api(api_jar, temp.name) - return filecmp.cmp(API_FILENAME, temp.name) + """Returns True if API_FILENAME matches the API exposed by |api_jar|.""" + with tempfile.NamedTemporaryFile() as temp: + _generate_api(api_jar, temp.name) + return filecmp.cmp(API_FILENAME, temp.name) def _check_api_update(old_api, new_api): - """Enforce that lines are only added when updating API.""" - new_hash = hashlib.md5() - old_hash = hashlib.md5() - seen_stamp = False - with open(old_api, 'r') as old_api_file, open(new_api, 'r') as new_api_file: - for old_line in old_api_file: - while True: - new_line = new_api_file.readline() - if seen_stamp: - print('ERROR: Stamp is not the last line.') - return False - if new_line.startswith('Stamp: ') and old_line.startswith('Stamp: '): - if old_line != 'Stamp: %s\n' % old_hash.hexdigest(): - print('ERROR: Prior api.txt not stamped by update_api.py') - return False - if new_line != 'Stamp: %s\n' % new_hash.hexdigest(): - print('ERROR: New api.txt not stamped by update_api.py') - return False - seen_stamp = True - break - new_hash.update(new_line.encode('utf8')) - if new_line == old_line: - break - if not new_line: - if old_line.startswith('Stamp: '): - print('ERROR: New api.txt not stamped by update_api.py') - else: - print('ERROR: This API was modified or removed:') - print(' ' + old_line) - print(' Cronet API methods and classes cannot be modified.') - return False - old_hash.update(old_line.encode('utf8')) - if not seen_stamp: - print('ERROR: api.txt not stamped by update_api.py.') - return False - return True + """Enforce that lines are only added when updating API.""" + new_hash = hashlib.md5() + old_hash = hashlib.md5() + seen_stamp = False + with open(old_api, 'r') as old_api_file, open(new_api, + 'r') as new_api_file: + for old_line in old_api_file: + while True: + new_line = new_api_file.readline() + if seen_stamp: + print('ERROR: Stamp is not the last line.') + return False + if new_line.startswith('Stamp: ') and old_line.startswith( + 'Stamp: '): + if old_line != 'Stamp: %s\n' % old_hash.hexdigest(): + print( + 'ERROR: Prior api.txt not stamped by update_api.py' + ) + return False + if new_line != 'Stamp: %s\n' % new_hash.hexdigest(): + print( + 'ERROR: New api.txt not stamped by update_api.py') + return False + seen_stamp = True + break + new_hash.update(new_line.encode('utf8')) + if new_line == old_line: + break + if not new_line: + if old_line.startswith('Stamp: '): + print( + 'ERROR: New api.txt not stamped by update_api.py') + else: + print('ERROR: This API was modified or removed:') + print(' ' + old_line) + print( + ' Cronet API methods and classes cannot be modified.' + ) + return False + old_hash.update(old_line.encode('utf8')) + if not seen_stamp: + print('ERROR: api.txt not stamped by update_api.py.') + return False + return True def main(args): - parser = argparse.ArgumentParser(description='Update Cronet api.txt.') - parser.add_argument('--api_jar', - help='Path to API jar (i.e. cronet_api.jar)', - required=True, - metavar='path/to/cronet_api.jar') - parser.add_argument( - '--allow_breaking_api_changes', - help= - 'If true, allows changing the API surface in a non-backward compatible manner (e.g., deleting existing APIs)', - required=False, - default=False, - action='store_true') - opts = parser.parse_args(args) + parser = argparse.ArgumentParser(description='Update Cronet api.txt.') + parser.add_argument('--api_jar', + help='Path to API jar (i.e. cronet_api.jar)', + required=True, + metavar='path/to/cronet_api.jar') + parser.add_argument( + '--allow_breaking_api_changes', + help= + 'If true, allows changing the API surface in a non-backward compatible manner (e.g., deleting existing APIs)', + required=False, + default=False, + action='store_true') + opts = parser.parse_args(args) - if check_up_to_date(opts.api_jar): - return True + if check_up_to_date(opts.api_jar): + return True - with tempfile.NamedTemporaryFile() as temp: - _generate_api(opts.api_jar, temp.name) - if opts.allow_breaking_api_changes or _check_api_update( - API_FILENAME, temp.name): - # Update API version number to new version number - with open(API_VERSION_FILENAME, 'r+') as f: - version = int(f.read()) - f.seek(0) - f.write(str(version + 1)) - # Update API file to new API - shutil.copyfile(temp.name, API_FILENAME) - return True - return False + with tempfile.NamedTemporaryFile() as temp: + _generate_api(opts.api_jar, temp.name) + if opts.allow_breaking_api_changes or _check_api_update( + API_FILENAME, temp.name): + # Update API version number to new version number + with open(API_VERSION_FILENAME, 'r+') as f: + version = int(f.read()) + f.seek(0) + f.write(str(version + 1)) + # Update API file to new API + shutil.copyfile(temp.name, API_FILENAME) + return True + return False if __name__ == '__main__': - sys.exit(0 if main(sys.argv[1:]) else -1) + sys.exit(0 if main(sys.argv[1:]) else -1)
diff --git a/components/cronet/tools/utils.py b/components/cronet/tools/utils.py index 88b3c29..6db5a8c 100755 --- a/components/cronet/tools/utils.py +++ b/components/cronet/tools/utils.py
@@ -34,7 +34,7 @@ def build_targets_list_chunking(out_path: str, targets: List[str]) -> None: - """Builds the provided targets by chunking them and passing each chunk into GN. This is + """Builds the provided targets by chunking them and passing each chunk into GN. This is generally faster than building each target separately. However, the |chunk_size| must be tweaked carefully to avoid exceeding the command-line length. @@ -42,31 +42,32 @@ out_path: GN output path targets: List of targets to build. """ - # Split the build script actions into chunk of _MAX_TARGETS_PER_NINJA_EXECUTION. - # This is needed in order not to exceed the command-line length. - build_script_actions_chunks = [ - targets[i:i + _MAX_TARGETS_PER_NINJA_EXECUTION] - for i in range(0, len(targets), _MAX_TARGETS_PER_NINJA_EXECUTION) - ] - for chunk in build_script_actions_chunks: - build_all(out_path, chunk) + # Split the build script actions into chunk of _MAX_TARGETS_PER_NINJA_EXECUTION. + # This is needed in order not to exceed the command-line length. + build_script_actions_chunks = [ + targets[i:i + _MAX_TARGETS_PER_NINJA_EXECUTION] + for i in range(0, len(targets), _MAX_TARGETS_PER_NINJA_EXECUTION) + ] + for chunk in build_script_actions_chunks: + build_all(out_path, chunk) + def run(command, **kwargs): - """See the official documentation for subprocess.check_call. + """See the official documentation for subprocess.check_call. Args: command (list[str]): command to be executed """ - if kwargs.get("shell"): - quoted_cmd = command - else: - quoted_cmd = ' '.join(shlex.quote(arg) for arg in command) - print('Executing: ' + quoted_cmd) - subprocess.check_call(command, **kwargs) + if kwargs.get("shell"): + quoted_cmd = command + else: + quoted_cmd = ' '.join(shlex.quote(arg) for arg in command) + print('Executing: ' + quoted_cmd) + subprocess.check_call(command, **kwargs) def run_and_get_stdout(command, **kwargs): - """See the official documentation for subprocess.run. + """See the official documentation for subprocess.run. Args: command (list[str]): command to be executed @@ -74,13 +75,13 @@ Returns: str: stdout for the executed command """ - print('Executing: ' + ' '.join(shlex.quote(arg) for arg in command)) - return subprocess.run(command, capture_output=True, - check=True, **kwargs).stdout.decode('utf-8').strip() + print('Executing: ' + ' '.join(shlex.quote(arg) for arg in command)) + return subprocess.run(command, capture_output=True, check=True, + **kwargs).stdout.decode('utf-8').strip() def gn(out_dir, gn_args, gn_extra=None, **kwargs): - """ Executes `gn gen`. + """ Executes `gn gen`. Runs `gn gen |out_dir| |gn_args + gn_extra|` which will generate a GN configuration that lives under |out_dir|. This is done @@ -91,138 +92,141 @@ gn_args (str): Args as a string delimited by space. gn_extra (str): extra args as a string delimited by space. """ - cmd = [GN_PATH, 'gen', out_dir, '--args=%s' % gn_args] - if gn_extra: - cmd += gn_extra - run(cmd, **kwargs) + cmd = [GN_PATH, 'gen', out_dir, '--args=%s' % gn_args] + if gn_extra: + cmd += gn_extra + run(cmd, **kwargs) def compare_text_and_generate_diff(generated_text, golden_text, golden_file_path): - """ + """ Compares the generated text with the golden text. returns a diff that can be applied with `patch` if exists. """ - golden_lines = [line.rstrip() for line in golden_text.splitlines()] - generated_lines = [line.rstrip() for line in generated_text.splitlines()] - if golden_lines == generated_lines: - return None + golden_lines = [line.rstrip() for line in golden_text.splitlines()] + generated_lines = [line.rstrip() for line in generated_text.splitlines()] + if golden_lines == generated_lines: + return None - expected_path = os.path.relpath(golden_file_path, REPOSITORY_ROOT) + expected_path = os.path.relpath(golden_file_path, REPOSITORY_ROOT) - diff = difflib.unified_diff( - golden_lines, - generated_lines, - fromfile=os.path.join('before', expected_path), - tofile=os.path.join('after', expected_path), - n=0, - lineterm='', - ) + diff = difflib.unified_diff( + golden_lines, + generated_lines, + fromfile=os.path.join('before', expected_path), + tofile=os.path.join('after', expected_path), + n=0, + lineterm='', + ) - return '\n'.join(diff) + return '\n'.join(diff) def read_file(path): - """Reads a file as a string""" - return pathlib.Path(path).read_text() + """Reads a file as a string""" + return pathlib.Path(path).read_text() def write_file(path, contents): - """Writes contents to a file""" - return pathlib.Path(path).write_text(contents) + """Writes contents to a file""" + return pathlib.Path(path).write_text(contents) def build(out_dir, build_target, extra_options=None): - """Runs `ninja build`. + """Runs `ninja build`. Runs `ninja -C |out_dir| |build_target| |extra_options|` which will build the target |build_target| for the GN configuration living under |out_dir|. This is done locally on the same chromium checkout. """ - cmd = [NINJA_PATH, '-C', out_dir, build_target] - if extra_options: - cmd += extra_options - run(cmd) + cmd = [NINJA_PATH, '-C', out_dir, build_target] + if extra_options: + cmd += extra_options + run(cmd) def build_all(out_dir, build_targets, extra_options=None): - """Runs `ninja build`. + """Runs `ninja build`. Runs `ninja -C |out_dir| |build_targets| |extra_options|` which will build the targets |build_targets| for the GN configuration living under |out_dir|. This is done locally on the same chromium checkout. """ - cmd = [NINJA_PATH, '-C', out_dir] - cmd.extend(build_targets) - if extra_options: - cmd += extra_options - run(cmd) + cmd = [NINJA_PATH, '-C', out_dir] + cmd.extend(build_targets) + if extra_options: + cmd += extra_options + run(cmd) def get_transitive_deps_build_files(repo_path: str, out_dir: str, gn_targets: List[str]) -> Set[str]: - """Executes gn desc |out_dir| |gn_target| deps --all --as=buildfile for each gn target""" - all_deps = set() - for gn_target in gn_targets: - all_deps.update( - subprocess.check_output([ - GN_PATH, "desc", out_dir, gn_target, "deps", "--all", - "--as=buildfile" - ]).decode("utf-8").split("\n")) - # gn desc deps does not return the build file that includes the target - # which we want to find its transitive dependencies, in order to - # account for this corner case, the BUILD file for the current target - # is added manually. - all_deps.add( - f"{os.path.join(repo_path, gn_target[2:gn_target.find(':')])}/BUILD.gn") - # It seems that we always get an empty string as part of the output. This - # could happen if we get an empty line in the output which can happen so - # let's remove that so downstream consumers don't have to check for it. - all_deps.remove('') - return all_deps + """Executes gn desc |out_dir| |gn_target| deps --all --as=buildfile for each gn target""" + all_deps = set() + for gn_target in gn_targets: + all_deps.update( + subprocess.check_output([ + GN_PATH, "desc", out_dir, gn_target, "deps", "--all", + "--as=buildfile" + ]).decode("utf-8").split("\n")) + # gn desc deps does not return the build file that includes the target + # which we want to find its transitive dependencies, in order to + # account for this corner case, the BUILD file for the current target + # is added manually. + all_deps.add( + f"{os.path.join(repo_path, gn_target[2:gn_target.find(':')])}/BUILD.gn" + ) + # It seems that we always get an empty string as part of the output. This + # could happen if we get an empty line in the output which can happen so + # let's remove that so downstream consumers don't have to check for it. + all_deps.remove('') + return all_deps + def get_gn_args_for_aosp(arch: str) -> List[str]: - # This is the source of truth for GN args for Cronet in Android. - # Note: for readability and discoverability, prefer to make the default value - # of the GN arg depend on `is_cronet_for_aosp_build` GN arg whenever possible, - # instead of setting the value here. - return ( - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'dcheck_always_on = false', - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'debuggable_apks = false', - # Override here, instead of modifying `default_min_sdk_version`'s - # declaration to avoid hardcoding this value twice (there is no easy way - # to share a constant between GN and python files). - f'default_min_sdk_version={MIN_SDK_VERSION_FOR_AOSP}', - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'is_debug = false', - 'is_cronet_for_aosp_build=true', - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'is_component_build = false', - # TODO: https://crbug.com/446652193 - It might be possible to drop this. - 'is_official_build = true', - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'strip_debug_info = true', - # TODO: https://crbug.com/446652679 - It might be possible to drop this. - 'symbol_level = 1', - f'target_cpu = "{arch}"', - 'target_os = "android"', - ) + # This is the source of truth for GN args for Cronet in Android. + # Note: for readability and discoverability, prefer to make the default value + # of the GN arg depend on `is_cronet_for_aosp_build` GN arg whenever possible, + # instead of setting the value here. + return ( + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'dcheck_always_on = false', + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'debuggable_apks = false', + # Override here, instead of modifying `default_min_sdk_version`'s + # declaration to avoid hardcoding this value twice (there is no easy way + # to share a constant between GN and python files). + f'default_min_sdk_version={MIN_SDK_VERSION_FOR_AOSP}', + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'is_debug = false', + 'is_cronet_for_aosp_build=true', + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'is_component_build = false', + # TODO: https://crbug.com/446652193 - It might be possible to drop this. + 'is_official_build = true', + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'strip_debug_info = true', + # TODO: https://crbug.com/446652679 - It might be possible to drop this. + 'symbol_level = 1', + f'target_cpu = "{arch}"', + 'target_os = "android"', + ) + def android_gn_gen(is_release, target_cpu, out_dir): - """Runs `gn gen` using Cronet's android gn_args. + """Runs `gn gen` using Cronet's android gn_args. Creates a local GN configuration under |out_dir| with the provided argument as input to `get_android_gn_args`, see the documentation of `get_android_gn_args` for more information. """ - return gn(out_dir, ' '.join(get_android_gn_args(is_release, target_cpu))) + return gn(out_dir, ' '.join(get_android_gn_args(is_release, target_cpu))) def get_android_gn_args(is_release, target_cpu): - """Fetches the gn args for a specific builder. + """Fetches the gn args for a specific builder. Returns a list of gn args used by the builders whose target cpu is |target_cpu| and (dev or rel) depending on is_release. @@ -235,54 +239,54 @@ get_android_gn_args(true, 'x86') -> GN Args for `android-cronet-x86-rel` get_android_gn_args(false, 'x86') -> GN Args for `android-cronet-x86-dev` """ - group_name = 'chromium.android' - builder_name = _map_config_to_android_builder(is_release, target_cpu) - # Ideally we would call `mb_py gen` directly, but we need to filter out the - # use_remoteexec arg, as that cannot be used in a local environment. - gn_args = subprocess.check_output( - ['python3', _MB_PATH, 'lookup', '-m', group_name, '-b', - builder_name]).decode('utf-8').strip() - return filter_gn_args(gn_args.split("\n"), []) + group_name = 'chromium.android' + builder_name = _map_config_to_android_builder(is_release, target_cpu) + # Ideally we would call `mb_py gen` directly, but we need to filter out the + # use_remoteexec arg, as that cannot be used in a local environment. + gn_args = subprocess.check_output( + ['python3', _MB_PATH, 'lookup', '-m', group_name, '-b', + builder_name]).decode('utf-8').strip() + return filter_gn_args(gn_args.split("\n"), []) def get_path_from_gn_label(gn_label: str) -> str: - """Returns the path part from a GN Label + """Returns the path part from a GN Label GN label consist of two parts, path and target_name, this will remove the target name and return the path or throw an error if it can't remove the target_name or if it doesn't exist. """ - if ":" not in gn_label: - raise ValueError(f"Provided gn label {gn_label} is not a proper label") - return gn_label[:gn_label.find(":")] + if ":" not in gn_label: + raise ValueError(f"Provided gn label {gn_label} is not a proper label") + return gn_label[:gn_label.find(":")] def _map_config_to_android_builder(is_release, target_cpu): - target_cpu_to_base_builder = { - 'x86': 'android-cronet-x86', - 'x64': 'android-cronet-x64', - 'arm': 'android-cronet-arm', - 'arm64': 'android-cronet-arm64', - 'riscv64': 'android-cronet-riscv64', - } - if target_cpu not in target_cpu_to_base_builder: - raise ValueError('Unsupported target CPU') + target_cpu_to_base_builder = { + 'x86': 'android-cronet-x86', + 'x64': 'android-cronet-x64', + 'arm': 'android-cronet-arm', + 'arm64': 'android-cronet-arm64', + 'riscv64': 'android-cronet-riscv64', + } + if target_cpu not in target_cpu_to_base_builder: + raise ValueError('Unsupported target CPU') - builder_name = target_cpu_to_base_builder[target_cpu] - if is_release: - builder_name += '-rel' - else: - builder_name += '-dbg' - return builder_name + builder_name = target_cpu_to_base_builder[target_cpu] + if is_release: + builder_name += '-rel' + else: + builder_name += '-dbg' + return builder_name def _should_remove_arg(arg, keys): - """An arg is removed if its key appear in the list of |keys|""" - return arg.split("=")[0].strip() in keys + """An arg is removed if its key appear in the list of |keys|""" + return arg.split("=")[0].strip() in keys def filter_gn_args(gn_args, keys_to_remove): - """Returns a list of filtered GN args. + """Returns a list of filtered GN args. (1) GN arg's returned must match the regex |_GN_ARG_MATCHER|. (2) GN arg's key must not be in |keys_to_remove|. @@ -291,9 +295,9 @@ gn_args: list of GN args. keys_to_remove: List of string that will be removed from gn_args. """ - filtered_args = [] - for arg in gn_args: - if _GN_ARG_MATCHER.match(arg) and not _should_remove_arg( - arg, keys_to_remove): - filtered_args.append(arg) - return filtered_args + filtered_args = [] + for arg in gn_args: + if _GN_ARG_MATCHER.match(arg) and not _should_remove_arg( + arg, keys_to_remove): + filtered_args.append(arg) + return filtered_args
diff --git a/components/cronet/tools_unittest.py b/components/cronet/tools_unittest.py index 7dbd9f7..82f1066 100755 --- a/components/cronet/tools_unittest.py +++ b/components/cronet/tools_unittest.py
@@ -2,16 +2,15 @@ # Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """Run tools/ unittests.""" import sys import unittest if __name__ == '__main__': - # jar/java/javac aren't typically installed on Windows so these tests don't - # work and give many verbose and cryptic failure messages. - if sys.platform == 'win32': - sys.exit(0) - suite = unittest.TestLoader().discover('tools', pattern = "*_unittest.py") - sys.exit(0 if unittest.TextTestRunner().run(suite).wasSuccessful() else 1) + # jar/java/javac aren't typically installed on Windows so these tests don't + # work and give many verbose and cryptic failure messages. + if sys.platform == 'win32': + sys.exit(0) + suite = unittest.TestLoader().discover('tools', pattern="*_unittest.py") + sys.exit(0 if unittest.TextTestRunner().run(suite).wasSuccessful() else 1)
diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc index 58a774a..c3ee090 100644 --- a/components/desks_storage/core/desk_sync_bridge.cc +++ b/components/desks_storage/core/desk_sync_bridge.cc
@@ -469,8 +469,8 @@ } size_t DeskSyncBridge::GetDeskTemplateEntryCount() const { - size_t template_count = std::count_if( - desk_template_entries_.begin(), desk_template_entries_.end(), + size_t template_count = std::ranges::count_if( + desk_template_entries_, [](const std::pair<base::Uuid, std::unique_ptr<ash::DeskTemplate>>& entry) { return entry.second->type() == ash::DeskTemplateType::kTemplate;
diff --git a/components/desks_storage/core/fake_desk_sync_bridge.cc b/components/desks_storage/core/fake_desk_sync_bridge.cc index ca9aa11..2895d85 100644 --- a/components/desks_storage/core/fake_desk_sync_bridge.cc +++ b/components/desks_storage/core/fake_desk_sync_bridge.cc
@@ -145,8 +145,8 @@ } size_t FakeDeskSyncBridge::GetDeskTemplateEntryCount() const { - size_t template_count = std::count_if( - desk_template_entries_.begin(), desk_template_entries_.end(), + size_t template_count = std::ranges::count_if( + desk_template_entries_, [](const std::pair<base::Uuid, std::unique_ptr<ash::DeskTemplate>>& entry) { return entry.second->type() == ash::DeskTemplateType::kTemplate;
diff --git a/components/enterprise/data_protection/BUILD.gn b/components/enterprise/data_protection/BUILD.gn new file mode 100644 index 0000000..8e957521 --- /dev/null +++ b/components/enterprise/data_protection/BUILD.gn
@@ -0,0 +1,33 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("data_protection") { + sources = [ + "data_protection_url_lookup_service.cc", + "data_protection_url_lookup_service.h", + ] + + deps = [ + "//base", + "//components/keyed_service/core", + "//components/safe_browsing/core/browser/realtime:url_lookup_service_base", + "//components/safe_browsing/core/common/proto:realtimeapi_proto", + "//components/sessions:session_id", + "//url", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ "data_protection_url_lookup_service_unittest.cc" ] + deps = [ + ":data_protection", + "//base", + "//base/test:test_support", + "//components/safe_browsing/core/browser/realtime:test_support", + "//testing/gmock", + "//testing/gtest", + "//url", + ] +}
diff --git a/components/enterprise/data_protection/DEPS b/components/enterprise/data_protection/DEPS new file mode 100644 index 0000000..2b1aaee --- /dev/null +++ b/components/enterprise/data_protection/DEPS
@@ -0,0 +1,15 @@ +include_rules = [ + "+components/keyed_service/core", + "+components/safe_browsing/core/browser/realtime", + "+components/safe_browsing/core/common", + "+components/sessions/core", + "+net", + "+services/network/public", + "+url", +] + +specific_include_rules = { + ".*unittest\.cc": [ + "+services/network:test_support", + ], +}
diff --git a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.cc b/components/enterprise/data_protection/data_protection_url_lookup_service.cc similarity index 71% rename from chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.cc rename to components/enterprise/data_protection/data_protection_url_lookup_service.cc index 9073f3b..d1e26cc 100644 --- a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.cc +++ b/components/enterprise/data_protection/data_protection_url_lookup_service.cc
@@ -1,19 +1,13 @@ -// Copyright 2025 The Chromium Authors +// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h" +#include "components/enterprise/data_protection/data_protection_url_lookup_service.h" -#include "base/feature_list.h" #include "base/metrics/histogram_functions.h" -#include "base/no_destructor.h" #include "base/task/sequenced_task_runner.h" #include "base/time/time.h" -#include "chrome/browser/enterprise/data_protection/data_protection_features.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "components/safe_browsing/core/common/proto/realtimeapi.pb.h" -#include "content/public/browser/browser_context.h" namespace { @@ -118,38 +112,6 @@ return base::Time::Now() > verdict.expiry_time; } -// ==================================================== -// DataProtectionUrlLookupServiceFactory implementation -// ==================================================== - -DataProtectionUrlLookupServiceFactory::DataProtectionUrlLookupServiceFactory() - : ProfileKeyedServiceFactory("DataProtectionUrlLookupService", - ProfileSelections::BuildForRegularProfile()) {} - -DataProtectionUrlLookupServiceFactory:: - ~DataProtectionUrlLookupServiceFactory() = default; - -// static -DataProtectionUrlLookupServiceFactory* -DataProtectionUrlLookupServiceFactory::GetInstance() { - static base::NoDestructor<DataProtectionUrlLookupServiceFactory> instance; - return instance.get(); -} - -// static -DataProtectionUrlLookupService* -DataProtectionUrlLookupServiceFactory::GetForBrowserContext( - content::BrowserContext* context) { - return static_cast<DataProtectionUrlLookupService*>( - GetInstance()->GetServiceForBrowserContext(context, true)); -} - -std::unique_ptr<KeyedService> -DataProtectionUrlLookupServiceFactory::BuildServiceInstanceForBrowserContext( - content::BrowserContext* context) const { - return std::make_unique<DataProtectionUrlLookupService>(); -} - // static size_t DataProtectionUrlLookupService::GetVerdictCacheMaxSize() { return kVerdictCacheMaxSize;
diff --git a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h b/components/enterprise/data_protection/data_protection_url_lookup_service.h similarity index 64% rename from chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h rename to components/enterprise/data_protection/data_protection_url_lookup_service.h index ffbdeb27..e6cf7ce 100644 --- a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h +++ b/components/enterprise/data_protection/data_protection_url_lookup_service.h
@@ -1,23 +1,21 @@ -// Copyright 2025 The Chromium Authors +// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_ -#define CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_ +#ifndef COMPONENTS_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_ +#define COMPONENTS_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_ + #include <memory> #include <string> #include "base/containers/lru_cache.h" +#include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "base/time/time.h" -#include "chrome/browser/profiles/profile_keyed_service_factory.h" #include "components/keyed_service/core/keyed_service.h" #include "components/safe_browsing/core/browser/realtime/url_lookup_service_base.h" #include "components/sessions/core/session_id.h" - -namespace content { -class BrowserContext; -} // namespace content +#include "url/gurl.h" namespace enterprise_data_protection { @@ -79,27 +77,6 @@ base::WeakPtrFactory<DataProtectionUrlLookupService> weak_factory_{this}; }; -// ===================================== -// DataProtectionUrlLookupServiceFactory -// ===================================== - -class DataProtectionUrlLookupServiceFactory - : public ProfileKeyedServiceFactory { - public: - static DataProtectionUrlLookupServiceFactory* GetInstance(); - static DataProtectionUrlLookupService* GetForBrowserContext( - content::BrowserContext* context); - - private: - DataProtectionUrlLookupServiceFactory(); - ~DataProtectionUrlLookupServiceFactory() override; - friend base::NoDestructor<DataProtectionUrlLookupServiceFactory>; - - // BrowserContextKeyedServiceFactory: - std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext( - content::BrowserContext* context) const override; -}; - } // namespace enterprise_data_protection -#endif // CHROME_BROWSER_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_ +#endif // COMPONENTS_ENTERPRISE_DATA_PROTECTION_DATA_PROTECTION_URL_LOOKUP_SERVICE_H_
diff --git a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc b/components/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc similarity index 96% rename from chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc rename to components/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc index 3707b68..1a46d0f 100644 --- a/chrome/browser/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc +++ b/components/enterprise/data_protection/data_protection_url_lookup_service_unittest.cc
@@ -1,8 +1,8 @@ -// Copyright 2025 The Chromium Authors +// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/enterprise/data_protection/data_protection_url_lookup_service.h" +#include "components/enterprise/data_protection/data_protection_url_lookup_service.h" #include "base/functional/callback_helpers.h" #include "base/test/task_environment.h"
diff --git a/components/facilitated_payments/core/validation/pix_validator.rs b/components/facilitated_payments/core/validation/pix_validator.rs index e16c8f6e0..0a95f67 100644 --- a/components/facilitated_payments/core/validation/pix_validator.rs +++ b/components/facilitated_payments/core/validation/pix_validator.rs
@@ -84,13 +84,12 @@ detected_qr_code_type = Some(qr_code_type); } } - ADDITIONAL_DATA_FIELD_TEMPLATE_DATA_OBJECT_ID => { + ADDITIONAL_DATA_FIELD_TEMPLATE_DATA_OBJECT_ID // 4.8.1.1: If present, the Additional Data Field Template shall contain at // least 1 data object. - if !contains_valid_data_objects(next_data_object.value) { + if !contains_valid_data_objects(next_data_object.value) => { return Err(Error::EmptyAdditionalDataFieldTemplate); } - } CRC16_DATA_OBJECT_ID => { // 4.6.1.2 The CRC (ID "63") shall be the last data object in the QR Code if !next_rest.is_empty() {
diff --git a/components/file_access/BUILD.gn b/components/file_access/BUILD.gn index f08dcd09..9a2e8b8 100644 --- a/components/file_access/BUILD.gn +++ b/components/file_access/BUILD.gn
@@ -18,10 +18,9 @@ defines = [ "IS_FILE_ACCESS_IMPL" ] - deps = [ - "//base", - "//url", - ] + deps = [ "//base" ] + + public_deps = [ "//url" ] } source_set("unit_tests") {
diff --git a/components/invalidation/invalidation_listener_impl_unittest.cc b/components/invalidation/invalidation_listener_impl_unittest.cc index ca3b3bf2..805f7c6 100644 --- a/components/invalidation/invalidation_listener_impl_unittest.cc +++ b/components/invalidation/invalidation_listener_impl_unittest.cc
@@ -119,10 +119,9 @@ auto CountSpecificInvalidation(const std::string& payload, int64_t version, base::Time issue_timestamp) { - return std::count_if( - received_invalidations_.begin(), received_invalidations_.end(), - [&payload, &version, &issue_timestamp, - this](const DirectInvalidation& invalidation) { + return std::ranges::count_if( + received_invalidations_, [&payload, &version, &issue_timestamp, this]( + const DirectInvalidation& invalidation) { return invalidation.type() == GetType() && invalidation.payload() == payload && invalidation.version() == version &&
diff --git a/components/messages/android/BUILD.gn b/components/messages/android/BUILD.gn index 62e063e..ddd00cf 100644 --- a/components/messages/android/BUILD.gn +++ b/components/messages/android/BUILD.gn
@@ -91,12 +91,12 @@ deps = [ ":jni_headers", "//content/public/browser", - "//ui/android", ] public_deps = [ "//base", "//skia", + "//ui/android", ] }
diff --git a/components/multistep_filter/README.md b/components/multistep_filter/README.md new file mode 100644 index 0000000..ba3848a --- /dev/null +++ b/components/multistep_filter/README.md
@@ -0,0 +1,19 @@ +# Multistep Filter Component + +The Multistep Filter component provides functionality to suggest filters to users based on their navigation history. It aims to streamline user workflows by predicting and surfacing relevant filters for the current context, helping users narrow down results or navigate complex sites more efficiently. + +## Overview + +This directory contains the core business logic for the Multistep Filter feature. It is responsible for: +* Analyzing user navigation patterns. +* Generating contextual filter suggestions. +* Coordinating with the UI to display these suggestions. + +The logic here is platform-independent, while the UI and browser-specific integrations reside in `//chrome/browser/multistep_filter`. + +## Architecture Diagram +TODO: crbug.com/489738688 + +## Contact + +For questions or issues, please reach out to chrome-autofill-team@google.com.
diff --git a/components/offline_items_collection/core/android/offline_item_bridge_unittest.cc b/components/offline_items_collection/core/android/offline_item_bridge_unittest.cc index 1fbdeab6..8e82905a 100644 --- a/components/offline_items_collection/core/android/offline_item_bridge_unittest.cc +++ b/components/offline_items_collection/core/android/offline_item_bridge_unittest.cc
@@ -36,7 +36,10 @@ OfflineItem item; auto* env = AttachCurrentThread(); auto j_offline_item = OfflineItemBridge::CreateOfflineItem(env, item); - j_test()->testCreateDefaultOfflineItem(env, j_offline_item); + auto j_offline_item_converted = jni_zero::Cast< + org::chromium::components::offline_items_collection::JOfflineItem>( + env, std::move(j_offline_item)); + j_test()->testCreateDefaultOfflineItem(env, j_offline_item_converted); } } // namespace
diff --git a/components/omnibox/browser/aim_eligibility_service.cc b/components/omnibox/browser/aim_eligibility_service.cc index 8c1b0b59..3a711acf 100644 --- a/components/omnibox/browser/aim_eligibility_service.cc +++ b/components/omnibox/browser/aim_eligibility_service.cc
@@ -31,6 +31,7 @@ #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h" +#include "components/variations/net/variations_http_headers.h" #include "google_apis/gaia/google_service_auth_error.h" #include "net/base/load_flags.h" #include "net/base/url_util.h" @@ -1000,6 +1001,15 @@ request->url = request_url; ConfigureRequestCookiesAndCredentials(request.get(), use_oauth); + + // The user may be signed in or not. But we only care about experiment IDs + // from the variations server, which do not require the signed-in version of + // this method. + variations::AppendVariationsHeaderUnknownSignedIn( + request->url, + configuration_.is_off_the_record ? variations::InIncognito::kYes + : variations::InIncognito::kNo, + request.get()); // If mode is POST with Proto, set method to POST. if (GetServerEligibilityRequestMode() == ServerEligibilityRequestMode::kPostWithProto) {
diff --git a/components/omnibox/browser/aim_eligibility_service_unittest.cc b/components/omnibox/browser/aim_eligibility_service_unittest.cc index 1f72988..11bf556 100644 --- a/components/omnibox/browser/aim_eligibility_service_unittest.cc +++ b/components/omnibox/browser/aim_eligibility_service_unittest.cc
@@ -18,6 +18,7 @@ #include "components/search/search.h" #include "components/search_engines/search_engines_test_environment.h" #include "components/search_engines/template_url_service.h" +#include "components/variations/scoped_variations_ids_provider.h" #include "net/base/url_util.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" @@ -105,6 +106,8 @@ protected: base::test::TaskEnvironment task_environment_; + variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{ + variations::VariationsIdsProvider::Mode::kUseSignedInState}; search_engines::SearchEnginesTestEnvironment search_engines_test_environment_; network::TestURLLoaderFactory test_url_loader_factory_; std::unique_ptr<MockAimEligibilityServiceForInterception>
diff --git a/components/omnibox/browser/autocomplete_grouper_sections.cc b/components/omnibox/browser/autocomplete_grouper_sections.cc index 0200eb3..0aa0fdbd 100644 --- a/components/omnibox/browser/autocomplete_grouper_sections.cc +++ b/components/omnibox/browser/autocomplete_grouper_sections.cc
@@ -793,8 +793,8 @@ : ZpsSection(limit, std::move(groups), group_configs) {} void ZpsSectionWithMVTiles::InitFromMatches(ACMatches& matches) { - size_t tile_count = std::count_if( - matches.begin(), matches.end(), [](const AutocompleteMatch& m) { + size_t tile_count = + std::ranges::count_if(matches, [](const AutocompleteMatch& m) { return m.suggestion_group_id.value_or(omnibox::GROUP_INVALID) == omnibox::GROUP_MOBILE_MOST_VISITED; });
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn index 1b9d0e086..cdd3aa8 100644 --- a/components/optimization_guide/core/BUILD.gn +++ b/components/optimization_guide/core/BUILD.gn
@@ -299,6 +299,8 @@ "model_execution/on_device_model_adaptation_controller.h", "model_execution/on_device_model_adaptation_loader.cc", "model_execution/on_device_model_adaptation_loader.h", + "model_execution/on_device_model_classifier_controller.cc", + "model_execution/on_device_model_classifier_controller.h", "model_execution/on_device_model_component.cc", "model_execution/on_device_model_component.h", "model_execution/on_device_model_download_progress_manager.cc",
diff --git a/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.cc new file mode 100644 index 0000000..46aa0ad --- /dev/null +++ b/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.cc
@@ -0,0 +1,275 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/optimization_guide/core/model_execution/on_device_model_classifier_controller.h" + +#include "base/files/file_path.h" +#include "base/functional/bind.h" +#include "base/functional/callback_forward.h" +#include "base/functional/callback_helpers.h" +#include "base/metrics/histogram_functions.h" +#include "base/notreached.h" +#include "base/path_service.h" +#include "base/system/sys_info.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "base/trace_event/trace_event.h" +#include "components/optimization_guide/core/model_execution/on_device_model_component.h" +#include "components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h" +#include "components/optimization_guide/core/model_execution/on_device_model_metadata.h" +#include "components/optimization_guide/core/optimization_guide_constants.h" +#include "components/optimization_guide/core/optimization_guide_features.h" +#include "components/optimization_guide/core/optimization_guide_switches.h" +#include "components/optimization_guide/proto/model_quality_metadata.pb.h" +#include "components/optimization_guide/proto/text_safety_model_metadata.pb.h" +#include "components/optimization_guide/public/mojom/model_broker.mojom-shared.h" +#include "mojo/public/cpp/base/proto_wrapper.h" +#include "services/on_device_model/public/mojom/on_device_model.mojom.h" + +namespace optimization_guide { + +namespace { + +// Similar mapping as `GetBaseModelError` in +// components/optimization_guide/core/model_execution/on_device_model_service_controller.cc +// except for kReady. +OnDeviceModelEligibilityReason GetBaseModelError( + OnDeviceModelStatus on_device_model_status) { + switch (on_device_model_status) { + case OnDeviceModelStatus::kNotEligible: + return OnDeviceModelEligibilityReason::kModelNotEligible; + case OnDeviceModelStatus::kInsufficientDiskSpace: + return OnDeviceModelEligibilityReason::kInsufficientDiskSpace; + case OnDeviceModelStatus::kInstallNotComplete: + case OnDeviceModelStatus::kModelInstallerNotRegisteredForUnknownReason: + case OnDeviceModelStatus::kModelInstalledTooLate: + case OnDeviceModelStatus::kNotReadyForUnknownReason: + case OnDeviceModelStatus::kNoOnDeviceFeatureUsed: + return OnDeviceModelEligibilityReason::kModelToBeInstalled; + case OnDeviceModelStatus::kReady: + NOTREACHED(); + } +} + +void CloseFilesInBackground(on_device_model::ModelAssets assets) { + base::ThreadPool::PostTask(FROM_HERE, {base::MayBlock()}, + base::DoNothingWithBoundArgs(std::move(assets))); +} + +} // namespace + +class OnDeviceModelClassifierController::Solution + : public ModelBrokerImpl::Solution { + public: + explicit Solution(base::WeakPtr<OnDeviceModelClassifierController> controller) + : controller_(controller) { + adapter_ = base::MakeRefCounted<OnDeviceModelFeatureAdapter>( + CreateFeatureConfig()); + } + ~Solution() override = default; + + bool IsValid() const override { return !!controller_; } + + // Hardcoded dummy config. + mojom::ModelSolutionConfigPtr MakeConfig() const override { + auto config = mojom::ModelSolutionConfig::New(); + config->max_tokens = kOnDeviceModelMaxTokens; + config->feature_config = mojo_base::ProtoWrapper(CreateFeatureConfig()); + config->text_safety_config = + mojo_base::ProtoWrapper(proto::FeatureTextSafetyConfiguration()); + config->model_versions = + mojo_base::ProtoWrapper(proto::OnDeviceModelVersions()); + return config; + } + + const OnDeviceModelFeatureAdapter* GetAdapter() const override { + return adapter_.get(); + } + + void CreateSession( + mojo::PendingReceiver<on_device_model::mojom::Session> pending, + on_device_model::mojom::SessionParamsPtr params) override { + TRACE_EVENT("optimization_guide", + "OnDeviceModelClassifierController::Solution::CreateSession"); + if (controller_) { + controller_->GetOrCreateRemote()->StartSession(std::move(pending), + std::move(params)); + } + } + + void CreateTextSafetySession( + mojo::PendingReceiver<on_device_model::mojom::TextSafetySession> pending) + override { + // No text safety for classifier. + } + + void ReportHealthyCompletion() override { + TRACE_EVENT( + "optimization_guide", + "OnDeviceModelClassifierController::Solution::ReportHealthyCompletion"); + } + + private: + // Hardcoded minimum config for the classifier model. + static proto::OnDeviceModelExecutionFeatureConfig CreateFeatureConfig() { + proto::OnDeviceModelExecutionFeatureConfig config; + auto* input_config = config.mutable_input_config(); + input_config->set_request_base_name( + "optimization_guide.proto.ClassifyApiRequest"); + + // Hardcoded substitution for the classifier model. + auto* substitution = input_config->add_execute_substitutions(); + substitution->set_string_template("%s"); + + auto* sub_arg = substitution->add_substitutions(); + auto* candidate = sub_arg->add_candidates(); + candidate->mutable_proto_field()->add_proto_descriptors()->set_tag_number( + 1); + + auto* output_config = config.mutable_output_config(); + output_config->set_proto_type( + "optimization_guide.proto.ClassifyApiResponse"); + output_config->mutable_proto_field() + ->add_proto_descriptors() + ->set_tag_number(1); + config.set_feature( + proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_CLASSIFIER); + return config; + } + + base::WeakPtr<OnDeviceModelClassifierController> controller_; + scoped_refptr<OnDeviceModelFeatureAdapter> adapter_; +}; + +OnDeviceModelClassifierController::OnDeviceModelClassifierController( + PrefService& local_state, + base::SafeRef<PerformanceClassifier> performance_classifier, + UsageTracker& usage_tracker, + base::SafeRef<on_device_model::ServiceClient> service_client, + ModelBrokerImpl& model_broker_impl, + std::unique_ptr<OnDeviceModelComponentStateManager::Delegate> delegate) + : service_client_(service_client), + model_broker_impl_(model_broker_impl), + usage_tracker_(usage_tracker), + component_state_manager_(&local_state, + performance_classifier, + usage_tracker, + std::move(delegate)) { + component_state_manager_.AddObserver(this); +} + +OnDeviceModelClassifierController::~OnDeviceModelClassifierController() { + component_state_manager_.RemoveObserver(this); +} + +void OnDeviceModelClassifierController::StateChanged( + MaybeOnDeviceModelComponentState state) { + remote_.reset(); + if (state.has_value()) { + model_metadata_ = std::make_unique<OnDeviceModelMetadata>( + state->get().GetInstallDirectory(), + state->get().GetComponentVersion().GetString(), + state->get().GetBaseModelSpec(), proto::OnDeviceModelExecutionConfig()); + model_status_ = OnDeviceModelStatus::kReady; + } else { + model_metadata_.reset(); + model_status_ = state.error(); + } + UpdateSolution(); +} + +mojo::Remote<on_device_model::mojom::OnDeviceModel>& +OnDeviceModelClassifierController::GetOrCreateRemote() { + if (remote_) { + return remote_; + } + TRACE_EVENT("optimization_guide", + "OnDeviceModelClassifierController::" + "CreateRemote"); + service_client_->AddPendingUsage(); // Warm up the service. + on_device_model::ModelAssetPaths paths; + paths.weights = model_metadata_->model_path().Append(kWeightsFile); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&on_device_model::LoadModelAssets, paths), + base::BindOnce( + [](base::WeakPtr<OnDeviceModelClassifierController> self, + mojo::PendingReceiver<on_device_model::mojom::OnDeviceModel> + receiver, + on_device_model::ModelAssets assets) { + if (!self || !self->service_client_->is_bound()) { + if (self) { + self->service_client_->RemovePendingUsage(); + } + CloseFilesInBackground(std::move(assets)); + return; + } + TRACE_EVENT("optimization_guide", + "OnDeviceModelClassifierController::" + "OnModelAssetsLoaded"); + auto params = on_device_model::mojom::LoadModelParams::New(); + params->assets = std::move(assets); + params->max_tokens = kOnDeviceModelMaxTokens; + // Hardcoded because the model can only run on CPU. + params->backend_type = ml::ModelBackendType::kCpuBackend; + + self->service_client_->Get()->LoadModel( + std::move(params), std::move(receiver), base::DoNothing()); + self->service_client_->RemovePendingUsage(); + }, + weak_ptr_factory_.GetWeakPtr(), + remote_.BindNewPipeAndPassReceiver())); + + remote_.set_disconnect_with_reason_handler( + base::BindOnce(&OnDeviceModelClassifierController::OnDisconnect, + weak_ptr_factory_.GetWeakPtr())); + remote_.reset_on_idle_timeout(features::GetOnDeviceModelIdleTimeout()); + + return remote_; +} + +void OnDeviceModelClassifierController::OnDisconnect( + uint32_t reason, + const std::string& description) { + TRACE_EVENT("optimization_guide", + "OnDeviceModelClassifierController::OnDisconnect"); + remote_.reset(); + const bool is_idle = + reason == static_cast<uint32_t>( + on_device_model::ModelDisconnectReason::kIdleShutdown); + if (is_idle) { + return; + } + LOG(ERROR) << "TinyGemma model disconnected unexpectedly."; +} + +void OnDeviceModelClassifierController::UpdateSolution() { + model_broker_impl_->GetSolutionProvider(mojom::OnDeviceFeature::kClassifier) + .Update(GetSolution()); +} + +ModelBrokerImpl::MaybeSolution +OnDeviceModelClassifierController::GetSolution() { + // No adaptation or text safety for classifier, so component ready implies + // solution ready. + if (model_status_ == OnDeviceModelStatus::kReady) { + return std::make_unique<Solution>(weak_ptr_factory_.GetWeakPtr()); + } + auto error = GetBaseModelError(model_status_); + if (error != OnDeviceModelEligibilityReason::kModelToBeInstalled) { + // Device eligibility not determined yet or device ineligible takes + // precedence over feature usage. + return base::unexpected(error); + } + + if (!usage_tracker_->WasOnDeviceEligibleFeatureRecentlyUsed( + mojom::OnDeviceFeature::kClassifier)) { + return base::unexpected( + OnDeviceModelEligibilityReason::kNoOnDeviceFeatureUsed); + } + return base::unexpected(OnDeviceModelEligibilityReason::kModelToBeInstalled); +} + +} // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.h b/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.h new file mode 100644 index 0000000..d36849c0d --- /dev/null +++ b/components/optimization_guide/core/model_execution/on_device_model_classifier_controller.h
@@ -0,0 +1,71 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_CLASSIFIER_CONTROLLER_H_ +#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_CLASSIFIER_CONTROLLER_H_ + +#include <memory> + +#include "base/memory/raw_ref.h" +#include "base/memory/weak_ptr.h" +#include "components/optimization_guide/core/model_execution/model_broker_impl.h" +#include "components/optimization_guide/core/model_execution/on_device_model_component.h" +#include "components/optimization_guide/core/model_execution/on_device_model_metadata.h" +#include "services/on_device_model/public/cpp/service_client.h" +#include "services/on_device_model/public/mojom/on_device_model.mojom.h" + +class PrefService; + +namespace optimization_guide { + +class PerformanceClassifier; +class UsageTracker; + +// Controls the lifecycle of the classifier model, loading and unloading +// of the models, and executing them via the service. +// This class parallels the `OnDeviceModelServiceController` but downloads the +// feature-specific model for Classifier instead of the common base model. +class OnDeviceModelClassifierController + : public OnDeviceModelComponentStateManager::Observer { + public: + OnDeviceModelClassifierController( + PrefService& local_state, + base::SafeRef<PerformanceClassifier> performance_classifier, + UsageTracker& usage_tracker, + base::SafeRef<on_device_model::ServiceClient> service_client, + ModelBrokerImpl& model_broker_impl, + std::unique_ptr<OnDeviceModelComponentStateManager::Delegate> delegate); + ~OnDeviceModelClassifierController() override; + + // Updates the solution for Classifier feature. + void UpdateSolution(); + + private: + class Solution; + + // Observes `OnDeviceModelComponentStateManager`. + void StateChanged(MaybeOnDeviceModelComponentState state) override; + // Returns the solution for the Classifier feature. + ModelBrokerImpl::MaybeSolution GetSolution(); + + mojo::Remote<on_device_model::mojom::OnDeviceModel>& GetOrCreateRemote(); + void OnDisconnect(uint32_t reason, const std::string& description); + + base::SafeRef<on_device_model::ServiceClient> service_client_; + base::raw_ref<ModelBrokerImpl> model_broker_impl_; + base::raw_ref<UsageTracker> usage_tracker_; + OnDeviceModelComponentStateManager component_state_manager_; + + std::unique_ptr<OnDeviceModelMetadata> model_metadata_; + OnDeviceModelStatus model_status_ = + OnDeviceModelStatus::kNotReadyForUnknownReason; + mojo::Remote<on_device_model::mojom::OnDeviceModel> remote_; + + base::WeakPtrFactory<OnDeviceModelClassifierController> weak_ptr_factory_{ + this}; +}; + +} // namespace optimization_guide + +#endif // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_CLASSIFIER_CONTROLLER_H_
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal index db24682..0dda382 160000 --- a/components/optimization_guide/internal +++ b/components/optimization_guide/internal
@@ -1 +1 @@ -Subproject commit db2468268385dbd81bb71e8accf26a76f0e93b81 +Subproject commit 0dda382cde62bc88d92bf698a1e7d4dee0ede5c8
diff --git a/components/page_info/page_info_delegate.h b/components/page_info/page_info_delegate.h index 8b13b03..951ba35 100644 --- a/components/page_info/page_info_delegate.h +++ b/components/page_info/page_info_delegate.h
@@ -83,6 +83,8 @@ CreateCookieControlsController() = 0; virtual bool IsIsolatedWebApp() = 0; + virtual bool IsSubApp() = 0; + virtual bool HasSubApps() = 0; virtual void ShowSiteSettings(const GURL& site_url) = 0; virtual void ShowCookiesSettings() = 0; virtual void ShowAllSitesSettingsFilteredByRwsOwner(
diff --git a/components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h b/components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h index 0d90f511..1ce51ae9 100644 --- a/components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h +++ b/components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h
@@ -59,6 +59,56 @@ }; // LINT.ThenChange(//tools/metrics/histograms/metadata/password/enums.xml:ActorLoginAttemptLoginResult) +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. This enum should be kept in sync with +// ActorLoginAccountTypes in tools/metrics/histograms/metadata/actor/enums.xml. +// +// LINT.IfChange(ActorLoginAccountTypes) +enum class ActorLoginAccountTypes { + // No credential picker was shown because there were no credentials saved. + kNone = 0, + kPassword = 1, + kFederated = 2, + kPasswordAndFederated = 3, + kMaxValue = kPasswordAndFederated, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/enums.xml:ActorLoginAccountTypes) + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. This enum should be kept in sync with +// ActorLoginSelectedAccountType in +// tools/metrics/histograms/metadata/actor/enums.xml. +// +// LINT.IfChange(ActorLoginSelectedAccountType) +enum class ActorLoginSelectedAccountType { + kNone = 0, + kPassword = 1, + kFederated = 2, + kMaxValue = kFederated, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/enums.xml:ActorLoginSelectedAccountType) + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. This enum should be kept in sync with +// ActorLoginFederatedLoginResult in +// tools/metrics/histograms/metadata/actor/enums.xml. +// +// LINT.IfChange(ActorLoginFederatedLoginResult) +enum class ActorLoginFederatedLoginResult { + kSuccess = 0, + kAccountNotLoggedIn = 2, + kAccountIsSignUp = 3, + kAccountNotAvailable = 4, + kIdpReturnedError = 5, + kIdpNetworkError = 6, + kTokenRequestAborted = 7, + kFrameNotActive = 8, + kExpectedAccountNotPresent = 9, + kTimeout = 10, + kMaxValue = kTimeout, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/enums.xml:ActorLoginFederatedLoginResult) + void RecordGetCredentialsResult(const CredentialsOrError& result_or_error); void RecordAttemptLoginResult(const LoginStatusResultOrError& result_or_error);
diff --git a/components/password_manager/core/common/BUILD.gn b/components/password_manager/core/common/BUILD.gn index 9c94ec1..c4ee946750 100644 --- a/components/password_manager/core/common/BUILD.gn +++ b/components/password_manager/core/common/BUILD.gn
@@ -29,12 +29,12 @@ "//components/autofill/core/common:autofill_regex", "//components/autofill/core/common/mojom:mojo_types", "//sql", - "//url", ] public_deps = [ ":constants", ":features", + "//url", ] configs += [ "//build/config/compiler:wexit_time_destructors" ]
diff --git a/components/payments/content/secure_payment_confirmation_service.cc b/components/payments/content/secure_payment_confirmation_service.cc index f133a34..f7a83fc 100644 --- a/components/payments/content/secure_payment_confirmation_service.cc +++ b/components/payments/content/secure_payment_confirmation_service.cc
@@ -307,6 +307,7 @@ void SecurePaymentConfirmationService::IsBrowserBoundKeyHardwareSupported( base::OnceCallback<void(bool)> callback) { +#if !BUILDFLAG(IS_IOS) scoped_refptr<BrowserBoundKeyStore> bbk_store = test_browser_bound_key_store_ ? test_browser_bound_key_store_ @@ -322,6 +323,7 @@ base::BindOnce(&BrowserBoundKeyStore::GetDeviceSupportsHardwareKeys, bbk_store), std::move(callback)); +#endif } bool SecurePaymentConfirmationService::IsCurrentStateValid() const {
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc index 23dd49a..09b2b473 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.cc +++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -538,10 +538,13 @@ } // If we get a suspcious verdict from RTLookupResponse, we should get a - // second opinion on CSD side, so we skip the allowlist. We also check the - // command line flag if the allowlist should be skipped. + // second opinion on CSD side, so we skip the allowlist. If we get an + // explicit request to send a report from the user, we skip the allowlist. + // We also check the command line flag if the allowlist should be skipped. if (phishing_detection_request_type_ == safe_browsing::ClientSideDetectionType::FORCE_REQUEST || + phishing_detection_request_type_ == + safe_browsing::ClientSideDetectionType::USER_REPORT || ShouldSkipCSDAllowlist()) { OnAllowlistCheckDone(url, phishing_reason, /*match_allowlist=*/false); @@ -667,8 +670,11 @@ } // We want to limit the number of requests, but if we're dumping features - // for debugging, allow us to exceed the report limit. + // for debugging or processing an explicit request for a report from a user, + // allow us to exceed the report limit. if (!HasDebugFeatureDirectory() && csd_service_ && + phishing_detection_request_type_ != + ClientSideDetectionType::USER_REPORT && csd_service_->AtPhishingReportLimit()) { base::UmaHistogramExactLinear("SBClientPhishing.RequestTypeAtReportLimit", phishing_detection_request_type_,
diff --git a/components/safety_check/BUILD.gn b/components/safety_check/BUILD.gn index bc3cf71d..46797cab 100644 --- a/components/safety_check/BUILD.gn +++ b/components/safety_check/BUILD.gn
@@ -29,13 +29,15 @@ "url_constants.h", ] - public_deps = [ "//base" ] + public_deps = [ + "//base", + "//services/network/public/cpp", + ] deps = [ "//components/prefs", "//components/safe_browsing/core/common:safe_browsing_prefs", "//net", - "//services/network/public/cpp", ] }
diff --git a/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc b/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc index 92cca353..21b4c8d 100644 --- a/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc +++ b/components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc
@@ -71,7 +71,10 @@ EXPECT_CALL(tab_group_sync_service_, AddObserver(_)); bridge_ = std::make_unique<TabGroupSyncServiceAndroid>(&tab_group_sync_service_); - j_service_ = bridge_->GetJavaObject(); + auto j_service_raw = bridge_->GetJavaObject(); + j_service_ = jni_zero::Cast< + org::chromium::components::tab_group_sync::JTabGroupSyncService>( + AttachCurrentThread(), std::move(j_service_raw)); } void SetUpJavaTestObserver() { @@ -85,7 +88,9 @@ MockTabGroupSyncService tab_group_sync_service_; std::unique_ptr<TabGroupSyncServiceAndroid> bridge_; - base::android::ScopedJavaLocalRef<jobject> j_service_; + base::android::ScopedJavaLocalRef< + org::chromium::components::tab_group_sync::JTabGroupSyncService> + j_service_; base::android::ScopedJavaGlobalRef<JTabGroupSyncServiceAndroidUnitTest> j_test_; LocalTabGroupID test_tab_group_id_ = base::Token(4, 5); @@ -136,7 +141,10 @@ "creator_cache_guid", "last_updater_cache_guid"); group.AddTabLocally(tab3); auto j_group = TabGroupSyncConversionsBridge::CreateGroup(env, group); - j_test_->testSavedTabGroupConversionNativeToJava(env, j_group); + auto j_group_converted = + jni_zero::Cast<org::chromium::components::tab_group_sync::JSavedTabGroup>( + env, std::move(j_group)); + j_test_->testSavedTabGroupConversionNativeToJava(env, j_group_converted); } TEST_F(TabGroupSyncServiceAndroidTest, OnTabGroupAdded) { @@ -336,9 +344,16 @@ auto j_local_id_1 = TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id_1); + auto j_local_id_1_converted = jni_zero::Cast< + org::chromium::components::tab_group_sync::JLocalTabGroupId>( + env, std::move(j_local_id_1)); auto j_local_id_2 = TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, local_id_2); - j_test_->testGetGroupByLocalId(env, j_local_id_1, j_local_id_2); + auto j_local_id_2_converted = jni_zero::Cast< + org::chromium::components::tab_group_sync::JLocalTabGroupId>( + env, std::move(j_local_id_2)); + j_test_->testGetGroupByLocalId(env, j_local_id_1_converted, + j_local_id_2_converted); } TEST_F(TabGroupSyncServiceAndroidTest, GetDeletedGroupIds) { @@ -354,22 +369,26 @@ base::Uuid group_id = base::Uuid::GenerateRandomV4(); auto j_group_id = UuidToJavaString(env, group_id); + auto j_tab_group_id = + TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_); + auto j_tab_group_id_converted = jni_zero::Cast< + org::chromium::components::tab_group_sync::JLocalTabGroupId>( + env, std::move(j_tab_group_id)); + // Update the mapping. EXPECT_CALL( tab_group_sync_service_, UpdateLocalTabGroupMapping(Eq(group_id), Eq(test_tab_group_id_), Eq(OpeningSource::kAutoOpenedFromSync))); - j_test_->testUpdateLocalTabGroupMapping( - AttachCurrentThread(), j_group_id, - TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_)); + j_test_->testUpdateLocalTabGroupMapping(AttachCurrentThread(), j_group_id, + j_tab_group_id_converted); // Remove the mapping. EXPECT_CALL(tab_group_sync_service_, RemoveLocalTabGroupMapping(Eq(test_tab_group_id_), Eq(ClosingSource::kDeletedByUser))); - j_test_->testRemoveLocalTabGroupMapping( - AttachCurrentThread(), - TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_)); + j_test_->testRemoveLocalTabGroupMapping(AttachCurrentThread(), + j_tab_group_id_converted); } TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabId) { @@ -377,12 +396,16 @@ base::Uuid tab_id = base::Uuid::GenerateRandomV4(); auto j_tab_id = UuidToJavaString(env, tab_id); + auto j_tab_group_id = + TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_); + auto j_tab_group_id_converted = jni_zero::Cast< + org::chromium::components::tab_group_sync::JLocalTabGroupId>( + env, std::move(j_tab_group_id)); + EXPECT_CALL(tab_group_sync_service_, UpdateLocalTabId(Eq(test_tab_group_id_), Eq(tab_id), Eq(4))); - j_test_->testUpdateLocalTabId( - AttachCurrentThread(), - TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_), - j_tab_id, 4); + j_test_->testUpdateLocalTabId(AttachCurrentThread(), j_tab_group_id_converted, + j_tab_id, 4); } TEST_F(TabGroupSyncServiceAndroidTest, OnTabSelected) { @@ -392,22 +415,27 @@ ScopedJavaLocalRef<jstring> j_tab_title = base::android::ConvertUTF16ToJavaString(env, kTestTabTitle); + auto j_tab_group_id = + TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_); + auto j_tab_group_id_converted = jni_zero::Cast< + org::chromium::components::tab_group_sync::JLocalTabGroupId>( + env, std::move(j_tab_group_id)); EXPECT_CALL( tab_group_sync_service_, OnTabSelected(Eq(test_tab_group_id_), Eq(tab_id), Eq(kTestTabTitle))); - j_test_->testOnTabSelected( - AttachCurrentThread(), - TabGroupSyncConversionsBridge::ToJavaTabGroupId(env, test_tab_group_id_), - tab_id, j_tab_title); + j_test_->testOnTabSelected(AttachCurrentThread(), j_tab_group_id_converted, + tab_id, j_tab_title); // Select a tab that isn't part of a group. LocalTabID non_grouped_tab_id = 6; EXPECT_CALL(tab_group_sync_service_, OnTabSelected(Eq(std::nullopt), Eq(non_grouped_tab_id), Eq(kTestTabTitle))); - j_test_->testOnTabSelected(AttachCurrentThread(), - ScopedJavaLocalRef<jobject>(), non_grouped_tab_id, - j_tab_title); + j_test_->testOnTabSelected( + AttachCurrentThread(), + ScopedJavaLocalRef< + org::chromium::components::tab_group_sync::JLocalTabGroupId>(), + non_grouped_tab_id, j_tab_title); } TEST_F(TabGroupSyncServiceAndroidTest, UpdateArchivalStatus) {
diff --git a/components/services/app_service/public/cpp/BUILD.gn b/components/services/app_service/public/cpp/BUILD.gn index e7830bf..f9b0636 100644 --- a/components/services/app_service/public/cpp/BUILD.gn +++ b/components/services/app_service/public/cpp/BUILD.gn
@@ -43,13 +43,13 @@ public_deps = [ "//base", "//components/services/app_service/public/protos", + "//url", ] deps = [ ":macros", "//third_party/abseil-cpp:absl", "//ui/gfx", - "//url", ] }
diff --git a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java index 3177a80..3acf2210 100644 --- a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java +++ b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java
@@ -4,7 +4,6 @@ package org.chromium.components.thinwebview.internal; -import android.app.Activity; import android.content.Context; import android.view.View; import android.view.ViewGroup; @@ -58,11 +57,10 @@ ThinWebViewConstraints constraints, IntentRequestTracker intentRequestTracker) { super(context); - Activity activity = ContextUtils.activityFromContext(context); - if (activity != null) { + if (ContextUtils.activityFromContext(context) != null) { mWindowAndroid = - ActivityWindowAndroid.create( - activity, + new ActivityWindowAndroid( + context, /* listenToActivityState= */ true, intentRequestTracker, /* insetObserver= */ null,
diff --git a/components/user_education/common/feature_promo/feature_promo_specification.cc b/components/user_education/common/feature_promo/feature_promo_specification.cc index 3b56988f..4ba1f12 100644 --- a/components/user_education/common/feature_promo/feature_promo_specification.cc +++ b/components/user_education/common/feature_promo/feature_promo_specification.cc
@@ -102,7 +102,6 @@ "IPH_PriceTrackingInSidePanel", "IPH_ReadingListDiscovery", "IPH_ReadingListInSidePanel", - "IPH_ResumptionRail", "IPH_TabSearch", }); return kAllowedPromoNames.contains(promo_feature.name);
diff --git a/components/value_store/value_store.h b/components/value_store/value_store.h index bf2a954..ebc34c9 100644 --- a/components/value_store/value_store.h +++ b/components/value_store/value_store.h
@@ -8,10 +8,12 @@ #include <stddef.h> #include <memory> +#include <optional> #include <string> #include <utility> #include <vector> +#include "base/functional/callback.h" #include "base/values.h" #include "components/value_store/value_store_change.h" @@ -21,6 +23,8 @@ class ValueStore { public: // Status codes returned from storage methods. + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. enum StatusCode { OK, @@ -42,8 +46,14 @@ // Any other error. OTHER_ERROR, + + // Add new values above this line. + STATUS_CODE_MAX, }; + // Status codes returned from attempting to restore a database. + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. enum BackingStoreRestoreStatus { // No restore attempted. RESTORE_NONE, @@ -57,6 +67,8 @@ VALUE_RESTORE_DELETE_SUCCESS, // Corrupted value cannot be deleted. VALUE_RESTORE_DELETE_FAILURE, + // Add new values above this line. + RESTORE_STATUS_MAX, }; // The status (result) of an operation on a ValueStore.
diff --git a/components/visited_url_ranking/internal/url_grouping/tab_event_tracker_impl.cc b/components/visited_url_ranking/internal/url_grouping/tab_event_tracker_impl.cc index e7ff8a2..8b13e887 100644 --- a/components/visited_url_ranking/internal/url_grouping/tab_event_tracker_impl.cc +++ b/components/visited_url_ranking/internal/url_grouping/tab_event_tracker_impl.cc
@@ -119,11 +119,10 @@ it != tab_id_selection_map_.end()) { const std::vector<TabSelection>& selection_list = it->second; const auto time_now = base::Time::Now(); - return std::count_if(selection_list.begin(), selection_list.end(), - [=](const TabSelection selection) { - return time_now - selection.time <= - kSelectionTimeWindow; - }); + return std::ranges::count_if( + selection_list, [=](const TabSelection selection) { + return time_now - selection.time <= kSelectionTimeWindow; + }); } } return 0;
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc index 902876c5..cd182f2 100644 --- a/content/browser/accessibility/web_contents_accessibility_android.cc +++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -1299,6 +1299,41 @@ return ui::kAXAndroidInvalidViewId; } +size_t WebContentsAccessibilityAndroid::GetAccessibilityTreeSizeForTesting( + JNIEnv* env) { + BrowserAccessibilityManagerAndroid* root_manager = + GetRootBrowserAccessibilityManager(); + if (!root_manager) { + return 0; + } + + size_t count = 0; + std::vector<ui::BrowserAccessibilityManager*> managers_to_process; + managers_to_process.push_back(root_manager); + + while (!managers_to_process.empty()) { + ui::BrowserAccessibilityManager* manager = managers_to_process.back(); + managers_to_process.pop_back(); + + if (!manager->ax_tree()) { + continue; + } + + count += manager->ax_tree()->size(); + + for (const ui::AXTreeID& child_tree_id : + manager->ax_tree()->GetAllChildTreeIds()) { + ui::BrowserAccessibilityManager* child_manager = + ui::BrowserAccessibilityManager::FromID(child_tree_id); + if (child_manager) { + managers_to_process.push_back(child_manager); + } + } + } + + return count; +} + bool WebContentsAccessibilityAndroid::IsNodeValid(JNIEnv* env, int32_t unique_id) { return GetAXFromUniqueID(unique_id) != nullptr;
diff --git a/content/browser/accessibility/web_contents_accessibility_android.h b/content/browser/accessibility/web_contents_accessibility_android.h index fdade97e..49eb929 100644 --- a/content/browser/accessibility/web_contents_accessibility_android.h +++ b/content/browser/accessibility/web_contents_accessibility_android.h
@@ -153,6 +153,7 @@ // Tree methods. int32_t GetRootId(JNIEnv* env); + size_t GetAccessibilityTreeSizeForTesting(JNIEnv* env); bool IsNodeValid(JNIEnv* env, int32_t id); void HitTest(JNIEnv* env, int32_t x, int32_t y);
diff --git a/content/browser/attribution_reporting/attribution_os_level_manager_android.cc b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc index 69d3f25..6e716616 100644 --- a/content/browser/attribution_reporting/attribution_os_level_manager_android.cc +++ b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
@@ -184,7 +184,9 @@ std::vector<ScopedJavaLocalRef<jobject>> registration_urls = base::ToVector( registration.registration_items, [env](const attribution_reporting::OsRegistrationItem& item) { - return url::GURLAndroid::FromNativeGURL(env, item.url); + ScopedJavaLocalRef<jobject> ret = + url::GURLAndroid::FromNativeGURL(env, item.url); + return ret; }); auto top_level_origin = url::GURLAndroid::FromNativeGURL( env, registration.top_level_origin.GetURL()); @@ -282,7 +284,9 @@ std::vector<ScopedJavaLocalRef<jobject>> j_origins = base::ToVector(origins, [env](const url::Origin& origin) { - return url::GURLAndroid::FromNativeGURL(env, origin.GetURL()); + ScopedJavaLocalRef<jobject> ret = + url::GURLAndroid::FromNativeGURL(env, origin.GetURL()); + return ret; }); int request_id = next_callback_id_++;
diff --git a/content/browser/indexed_db/instance/bucket_context.cc b/content/browser/indexed_db/instance/bucket_context.cc index 96af697..f9e1f14 100644 --- a/content/browser/indexed_db/instance/bucket_context.cc +++ b/content/browser/indexed_db/instance/bucket_context.cc
@@ -973,6 +973,7 @@ const std::string& message) { CHECK(!status.ok()); + LOG(ERROR) << " got status " << status.ToString(); if (status.IsIOError()) { quota_manager_proxy_->OnClientWriteFailed(bucket_info_.storage_key); }
diff --git a/content/browser/indexed_db/instance/connection_coordinator.cc b/content/browser/indexed_db/instance/connection_coordinator.cc index 608cf9a..cedd66f 100644 --- a/content/browser/indexed_db/instance/connection_coordinator.cc +++ b/content/browser/indexed_db/instance/connection_coordinator.cc
@@ -8,7 +8,6 @@ #include <map> #include <set> #include <string> -#include <tuple> #include <type_traits> #include <utility> #include <vector> @@ -59,7 +58,6 @@ kPendingNoConnections, kPendingLocks, kPendingTransactionComplete, - kError, kDone, }; } // namespace @@ -121,10 +119,7 @@ // removed from the queue. virtual void OnForceClose(const std::string& message) && = 0; - RequestState state() const { return state_; } - - // Relevant if state() is kError. - Status status() const { return saved_status_; } + const StatusOr<RequestState>& state() const { return state_; } protected: mojo::AssociatedRemote<blink::mojom::IDBFactoryClient> TakeFactoryClient() { @@ -147,7 +142,7 @@ std::move(next_step)); } - RequestState state_ = RequestState::kNotStarted; + StatusOr<RequestState> state_ = RequestState::kNotStarted; BucketContextHandle bucket_context_handle_; // This is safe because Database owns this object. @@ -169,8 +164,6 @@ // Used to ensure `factory_client_->Blocked()` is only called once. bool blocked_called_ = false; - Status saved_status_; - PartitionedLockHolder lock_receiver_; // The total duration of synchronous work done for this request, including any @@ -237,8 +230,8 @@ void InitDatabase(bool has_connections) { base::ElapsedTimer timer; - saved_status_ = db_->OpenInternal(); - if (saved_status_.ok()) { + Status open_status = db_->OpenInternal(); + if (open_status.ok()) { if (bucket_context_handle_->IsUsingSqlite()) { // The SQLite backing store itself surfaces data loss info only at the // database level, but `pending_->data_loss_info` will already contain @@ -261,7 +254,7 @@ } TakeFactoryClient()->Error(blink::mojom::IDBException::kUnknownError, message); - state_ = RequestState::kError; + state_ = base::unexpected(open_status); tasks_available_callback_.Run(); return; } @@ -519,8 +512,7 @@ "Internal error opening backing store for indexedDB.deleteDatabase."; TakeFactoryClient()->Error(blink::mojom::IDBException::kUnknownError, base::ASCIIToUTF16(error_message)); - state_ = RequestState::kError; - saved_status_ = exists.error(); + state_ = base::unexpected(exists.error()); if (exists.error().IsCorruption()) { bucket_context_handle_->HandleBackingStoreCorruption(error_message); } @@ -546,12 +538,12 @@ void InitDatabase(bool has_connections) { ScopedTimeAccumulator accumulator(synchronous_duration_); base::ScopedClosureRunner scoped_tasks_available(tasks_available_callback_); - saved_status_ = db_->OpenInternal(); - if (!saved_status_.ok()) { + Status open_status = db_->OpenInternal(); + if (!open_status.ok()) { TakeFactoryClient()->Error(blink::mojom::IDBException::kUnknownError, u"Internal error creating database backend " u"for indexedDB.deleteDatabase."); - state_ = RequestState::kError; + state_ = base::unexpected(open_status); return; } @@ -589,7 +581,6 @@ base::ScopedClosureRunner scoped_tasks_available(tasks_available_callback_); if (old_version.has_value()) { - saved_status_ = Status::OK(); TakeFactoryClient()->DeleteSuccess(old_version.value()); state_ = RequestState::kDone; LogDuration(synchronous_duration_ += timer.Elapsed(), @@ -598,10 +589,9 @@ } else { // TODO(jsbell): Consider including sanitized leveldb status // message. - saved_status_ = old_version.error(); TakeFactoryClient()->Error(blink::mojom::IDBException::kUnknownError, u"Internal error deleting database."); - state_ = RequestState::kError; + state_ = base::unexpected(old_version.error()); } } @@ -712,10 +702,10 @@ request_queue_.front()->UpgradeTransactionFinished(committed); } -std::tuple<ConnectionCoordinator::ExecuteTaskResult, Status> +StatusOr<ConnectionCoordinator::ExecuteTaskResult> ConnectionCoordinator::ExecuteTask(bool has_connections) { if (request_queue_.empty()) { - return {ExecuteTaskResult::kDone, Status()}; + return ExecuteTaskResult::kDone; } auto& request = request_queue_.front(); @@ -724,37 +714,39 @@ DCHECK(request->state() != RequestState::kNotStarted); } - switch (request->state()) { + StatusOr<RequestState> state = request->state(); + if (!state.has_value()) { + // Move `request_to_discard` out of `request_queue_` then + // `request_queue_.pop()`. We do this because `request_to_discard`'s dtor + // calls OnConnectionClosedDuringUpgrade and OnNoConnections, which + // interact with `request_queue_` assuming the queue no longer holds + // `request_to_discard`. + std::unique_ptr<ConnectionRequest> request_to_discard = + std::move(request_queue_.front()); + request_queue_.pop(); + request_to_discard.reset(); + return base::unexpected(state.error()); + } + + switch (state.value()) { case RequestState::kNotStarted: NOTREACHED(); case RequestState::kPendingNoConnections: case RequestState::kPendingLocks: case RequestState::kPendingTransactionComplete: - return {ExecuteTaskResult::kPendingAsyncWork, Status()}; + return ExecuteTaskResult::kPendingAsyncWork; case RequestState::kDone: { // Move `request_to_discard` out of `request_queue_` then // `request_queue_.pop()`. We do this because `request_to_discard`'s dtor // calls OnConnectionClosedDuringUpgrade and OnNoConnections, which // interact with `request_queue_` assuming the queue no longer holds // `request_to_discard`. - auto request_to_discard = std::move(request_queue_.front()); + std::unique_ptr<ConnectionRequest> request_to_discard = + std::move(request_queue_.front()); request_queue_.pop(); request_to_discard.reset(); - return {request_queue_.empty() ? ExecuteTaskResult::kDone - : ExecuteTaskResult::kMoreTasks, - Status::OK()}; - } - case RequestState::kError: { - Status status = request->status(); - // Move `request_to_discard` out of `request_queue_` then - // `request_queue_.pop()`. We do this because `request_to_discard`'s dtor - // calls OnConnectionClosedDuringUpgrade and OnNoConnections, which - // interact with `request_queue_` assuming the queue no longer holds - // `request_to_discard`. - auto request_to_discard = std::move(request_queue_.front()); - request_queue_.pop(); - request_to_discard.reset(); - return {ExecuteTaskResult::kError, status}; + return request_queue_.empty() ? ExecuteTaskResult::kDone + : ExecuteTaskResult::kMoreTasks; } } NOTREACHED();
diff --git a/content/browser/indexed_db/instance/connection_coordinator.h b/content/browser/indexed_db/instance/connection_coordinator.h index 78c9131..a5136aa 100644 --- a/content/browser/indexed_db/instance/connection_coordinator.h +++ b/content/browser/indexed_db/instance/connection_coordinator.h
@@ -6,7 +6,6 @@ #define CONTENT_BROWSER_INDEXED_DB_INSTANCE_CONNECTION_COORDINATOR_H_ #include <memory> -#include <tuple> #include "base/containers/queue.h" #include "base/functional/callback_forward.h" @@ -63,14 +62,10 @@ // There are tasks but they are waiting on async work to complete. No more // calls to ExecuteTask() are necessary. kPendingAsyncWork, - // There was an error executing a task - see the status. The offending task - // was removed, and the caller can choose to continue executing tasks if - // they want. - kError, // There are no more tasks to run. kDone, }; - std::tuple<ExecuteTaskResult, Status> ExecuteTask(bool has_connections); + StatusOr<ExecuteTaskResult> ExecuteTask(bool has_connections); bool HasTasks() const { return !request_queue_.empty(); }
diff --git a/content/browser/indexed_db/instance/database.cc b/content/browser/indexed_db/instance/database.cc index 4d88f90..bcb26d0c 100644 --- a/content/browser/indexed_db/instance/database.cc +++ b/content/browser/indexed_db/instance/database.cc
@@ -307,16 +307,15 @@ Status Database::RunTasks() { // First execute any pending tasks in the connection coordinator. - ConnectionCoordinator::ExecuteTaskResult task_state; - Status status; - do { - std::tie(task_state, status) = + while (true) { + StatusOr<ConnectionCoordinator::ExecuteTaskResult> task_state = connection_coordinator_.ExecuteTask(!connections_.empty()); - } while (task_state == ConnectionCoordinator::ExecuteTaskResult::kMoreTasks); - - if (task_state == ConnectionCoordinator::ExecuteTaskResult::kError) { - CHECK(!status.ok()); - return status; + if (!task_state.has_value()) { + return task_state.error(); + } + if (task_state != ConnectionCoordinator::ExecuteTaskResult::kMoreTasks) { + break; + } } bool transactions_removed = true;
diff --git a/content/browser/service_worker/service_worker_synthetic_response_manager.cc b/content/browser/service_worker/service_worker_synthetic_response_manager.cc index 3f454faf..99e4e73 100644 --- a/content/browser/service_worker/service_worker_synthetic_response_manager.cc +++ b/content/browser/service_worker/service_worker_synthetic_response_manager.cc
@@ -245,6 +245,7 @@ ServiceWorkerClient::CreateNetworkURLLoaderFactoryType:: kSyntheticNetworkRequest, storage_partition, request); + is_initiated_by_prefetch_ = service_worker_client->is_initiated_by_prefetch(); StartRequest( GlobalRequestID::MakeBrowserInitiated().request_id, @@ -600,6 +601,8 @@ did_start_synthetic_response_); SCOPED_CRASH_KEY_BOOL("SWSR", "is_network_service_mode", IsServiceWorkerSyntheticResponseNetworkService()); + SCOPED_CRASH_KEY_BOOL("SWSR", "is_initiated_by_prefetch", + is_initiated_by_prefetch_); base::debug::DumpWithoutCrashing(); if (auto callback = std::exchange(stream_callback_, {})) { callback->OnAborted();
diff --git a/content/browser/service_worker/service_worker_synthetic_response_manager.h b/content/browser/service_worker/service_worker_synthetic_response_manager.h index fbcb760b..e51b84c5 100644 --- a/content/browser/service_worker/service_worker_synthetic_response_manager.h +++ b/content/browser/service_worker/service_worker_synthetic_response_manager.h
@@ -159,6 +159,7 @@ std::optional<ServiceWorkerSyntheticResponseDataPipeConnector> data_pipe_connector_; bool did_start_synthetic_response_ = false; + bool is_initiated_by_prefetch_ = false; base::TimeTicks request_start_time_; base::TimeTicks response_received_time_;
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc index 0a1be4d..9f461e3e 100644 --- a/content/browser/web_contents/web_contents_impl_unittest.cc +++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -3477,22 +3477,28 @@ TEST_F(WebContentsImplTest, IgnoreInputEvents) { // By default, input events should not be ignored. EXPECT_FALSE(contents()->ShouldIgnoreInputEvents()); + EXPECT_FALSE(contents()->ShouldIgnoreA11yInputEvents()); + std::optional<WebContents::ScopedIgnoreInputEvents> ignore_1 = contents()->IgnoreInputEvents(std::nullopt); EXPECT_TRUE(contents()->ShouldIgnoreInputEvents()); + EXPECT_TRUE(contents()->ShouldIgnoreA11yInputEvents()); // A second request to ignore should continue to ignore events. WebContents::ScopedIgnoreInputEvents ignore_2 = contents()->IgnoreInputEvents(std::nullopt); EXPECT_TRUE(contents()->ShouldIgnoreInputEvents()); + EXPECT_TRUE(contents()->ShouldIgnoreA11yInputEvents()); // Releasing one of them should not change anything. ignore_1.reset(); EXPECT_TRUE(contents()->ShouldIgnoreInputEvents()); + EXPECT_TRUE(contents()->ShouldIgnoreA11yInputEvents()); // Move construction should not allow input. WebContents::ScopedIgnoreInputEvents ignore_3(std::move(ignore_2)); EXPECT_TRUE(contents()->ShouldIgnoreInputEvents()); + EXPECT_TRUE(contents()->ShouldIgnoreA11yInputEvents()); { // Cannot create an empty `ScopedIgnoreInputEvents`, so get a new one and @@ -3501,11 +3507,13 @@ contents()->IgnoreInputEvents(std::nullopt); ignore_4 = std::move(ignore_3); EXPECT_TRUE(contents()->ShouldIgnoreInputEvents()); + EXPECT_TRUE(contents()->ShouldIgnoreA11yInputEvents()); // `ignore_4` goes out of scope. } // Now input should be allowed. EXPECT_FALSE(contents()->ShouldIgnoreInputEvents()); + EXPECT_FALSE(contents()->ShouldIgnoreA11yInputEvents()); } TEST_F(WebContentsImplTest, IgnoreInputEvents_IgnoreA11yInputEvents) { @@ -3514,11 +3522,14 @@ EXPECT_FALSE(contents()->ShouldIgnoreA11yInputEvents()); // Create two requests with different a11y input settings. - std::optional<WebContents::ScopedIgnoreInputEvents> ignore_input_only = - contents()->IgnoreInputEvents(std::nullopt); + // The default 1-argument call now ignores both regular input and a11y input. std::optional<WebContents::ScopedIgnoreInputEvents> - ignore_input_and_a11y_input = contents()->IgnoreInputEvents( - std::nullopt, /*should_ignore_a11y_input=*/true); + ignore_input_and_a11y_input = contents()->IgnoreInputEvents(std::nullopt); + + // To test ignoring ONLY regular input, we must explicitly pass false. + std::optional<WebContents::ScopedIgnoreInputEvents> ignore_input_only = + contents()->IgnoreInputEvents(std::nullopt, + /*should_ignore_a11y_input=*/false); // With both requests active, both input and a11y input should be ignored. EXPECT_TRUE(contents()->ShouldIgnoreInputEvents());
diff --git a/content/browser/webid/identity_credential_source_impl.cc b/content/browser/webid/identity_credential_source_impl.cc index 80cf4ec..b2f7279 100644 --- a/content/browser/webid/identity_credential_source_impl.cc +++ b/content/browser/webid/identity_credential_source_impl.cc
@@ -129,6 +129,11 @@ /*filter_accounts_callback=*/base::DoNothing()); } +bool IdentityCredentialSourceImpl::HasPendingRequest() { + RequestPageData* page_data = GetPageData(render_frame_host().GetPage()); + return page_data && page_data->PendingWebIdentityRequest(); +} + bool IdentityCredentialSourceImpl::SelectAccount( const url::Origin& idp_origin, const std::string& account_id) {
diff --git a/content/browser/webid/identity_credential_source_impl.h b/content/browser/webid/identity_credential_source_impl.h index 7ab836f..429e7ba 100644 --- a/content/browser/webid/identity_credential_source_impl.h +++ b/content/browser/webid/identity_credential_source_impl.h
@@ -39,6 +39,8 @@ const std::vector<GURL>& embedder_requested_idps, GetIdentityCredentialSuggestionsCallback callback) override; + bool HasPendingRequest() override; + bool SelectAccount(const url::Origin& idp_origin, const std::string& account_id) override;
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn index e74a7583..dfd08d8 100644 --- a/content/public/android/BUILD.gn +++ b/content/public/android/BUILD.gn
@@ -291,6 +291,7 @@ "java/src/org/chromium/content/browser/accessibility/AccessibilityNodeInfoUtils.java", "java/src/org/chromium/content/browser/accessibility/AssistDataBuilder.java", "java/src/org/chromium/content/browser/accessibility/AutoDisableAccessibilityHandler.java", + "java/src/org/chromium/content/browser/accessibility/FakeAndroidCache.java", "java/src/org/chromium/content/browser/accessibility/ViewStructureBuilder.java", "java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityDelegate.java", "java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java", @@ -675,6 +676,7 @@ "javatests/src/org/chromium/content/browser/accessibility/AccessibilityEventDispatcherTest.java", "javatests/src/org/chromium/content/browser/accessibility/AssistViewStructureTest.java", "javatests/src/org/chromium/content/browser/accessibility/FakeAconfigFlaggedApiDelegate.java", + "javatests/src/org/chromium/content/browser/accessibility/FakeAndroidCacheTest.java", "javatests/src/org/chromium/content/browser/accessibility/TestViewStructure.java", "javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityE2ETest.java", "javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/FakeAndroidCache.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/FakeAndroidCache.java new file mode 100644 index 0000000..2423183 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/FakeAndroidCache.java
@@ -0,0 +1,145 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.accessibility; + +import android.os.Build; +import android.os.Parcel; +import android.util.SparseArray; + +import androidx.annotation.Nullable; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; + +import org.jni_zero.JNINamespace; + +import org.chromium.build.annotations.NullMarked; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A fake cache for {@link AccessibilityNodeInfoCompat} objects. This is used to simulate the + * behavior of the Android framework's cache. This must be used with Android Tiramisu and above. + */ +@JNINamespace("content") +@NullMarked +public class FakeAndroidCache { + private final SparseArray<CachedNodeState> mCache = new SparseArray<>(); + private final WebContentsAccessibilityImpl mWebContentsAccessibilityImpl; + // Only for testing + private int mStaleNodeCount; + + public FakeAndroidCache(WebContentsAccessibilityImpl webContentsAccessibilityImpl) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + throw new UnsupportedOperationException( + "FakeAndroidCache is only available on Android Tiramisu and above."); + } + mWebContentsAccessibilityImpl = webContentsAccessibilityImpl; + } + + private static class CachedNodeState { + // A safe copy of the node's state at the time of caching. + final @Nullable byte[] mNodeInfoParcelData; + + // The virtual view IDs of the children at the time of caching. + final List<Integer> mChildIds; + + private CachedNodeState(AccessibilityNodeInfoCompat mNodeInfo, List<Integer> childIds) { + Parcel nodeParcel = Parcel.obtain(); + mNodeInfo.unwrap().writeToParcel(nodeParcel, 0); + mNodeInfoParcelData = nodeParcel.marshall(); + mChildIds = new ArrayList<>(childIds); + } + } + + // Validate accessibility node info throughout the fake android cache. + public void validateAccessibility() { + for (int i = 0; i < mCache.size(); i++) { + int virtualViewId = mCache.keyAt(i); + CachedNodeState cachedState = mCache.valueAt(i); + // Build a completely fresh node for comparison. + AccessibilityNodeInfoCompat freshInfo = + mWebContentsAccessibilityImpl.buildFreshAccessibilityNodeInfo(virtualViewId); + if (freshInfo == null) { + continue; + } + if (freshInfo.getUniqueId() == null) { + throw new IllegalArgumentException("Unique ID for node info should not be null."); + } + // Avoid comparing against the root node given its event handing. + if (freshInfo + .getUniqueId() + .equals( + String.valueOf( + mWebContentsAccessibilityImpl.getCurrentRootIdForTesting()))) { + continue; + } + + Parcel freshInfoParcel = Parcel.obtain(); + freshInfo.unwrap().writeToParcel(freshInfoParcel, 0); + if (!Arrays.equals(freshInfoParcel.marshall(), cachedState.mNodeInfoParcelData)) { + mStaleNodeCount++; + } + } + } + + /** + * Adds a node to the fake Android cache for testing. + * + * @param virtualViewId The virtual view id of the node. + */ + public void addNode(int virtualViewId, AccessibilityNodeInfoCompat nodeInfo) { + if (nodeInfo == null) { + return; + } + int[] childIds = mWebContentsAccessibilityImpl.getChildIdsForTesting(virtualViewId); + List<Integer> childIdList = new ArrayList<>(childIds != null ? childIds.length : 0); + if (childIds != null) { + for (int id : childIds) { + childIdList.add(id); + } + } + mCache.put(virtualViewId, new CachedNodeState(nodeInfo, childIdList)); + } + + // Clear the entire fake cache. + private void clear() { + mCache.clear(); + } + + /** + * Recursively clear this node and all its descendants from the fake cache. + * + * @param virtualViewId The virtual view id of the node. + * @param recursive Indicate if all of the node's children should be cleared. + */ + public void clearNode(int virtualViewId, boolean recursive) { + CachedNodeState cachedNodeState = mCache.get(virtualViewId); + if (cachedNodeState != null) { + mCache.remove(virtualViewId); + if (recursive) { + for (int childId : cachedNodeState.mChildIds) { + clearNode(childId, true); + } + } + } else { + if (recursive) { + // According to Android's AccessibilityCache, if a node is not found, children might + // be + // present so we clear all of the cache. + clear(); + } + } + } + + /** + * Returns the number of stale nodes in the fake Android cache. + * + * @return The number of stale nodes. + */ + public int getStaleNodeCountForTesting() { + return mStaleNodeCount; + } +}
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java index 7134da7..2096b53 100644 --- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
@@ -594,6 +594,14 @@ return WebContentsAccessibilityImplJni.get().getRootId(mNativeObj); } + public long getAccessibilityTreeSizeForTesting() { + if (!isNativeInitialized()) return 0; + assert isRootManagerConnected() + : "Accessibility root manager should be connected when the native object is" + + " initialized."; + return WebContentsAccessibilityImplJni.get().getAccessibilityTreeSizeForTesting(mNativeObj); + } + public int getMaxContentChangedEventsToFireForTesting() { return WebContentsAccessibilityImplJni.get() .getMaxContentChangedEventsToFireForTesting(mNativeObj); @@ -1011,6 +1019,38 @@ } /** + * Builds a fresh node for comparison with a cached node. + * + * <p>Note: This is a temporary method to validate the accessibility node cache. + * + * @param virtualViewId The virtual view id of the node to build. + * @return The fresh node or null if the node is not valid. + */ + public @Nullable AccessibilityNodeInfoCompat buildFreshAccessibilityNodeInfo( + int virtualViewId) { + if (!isAccessibilityEnabled() + || !isFrameInfoInitialized() + || !WebContentsAccessibilityImplJni.get().isNodeValid(mNativeObj, virtualViewId)) { + return null; + } + final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(mView); + info.setPackageName(mContext.getPackageName()); + info.setSource(mView, virtualViewId); + + if (virtualViewId == mCurrentRootId) { + info.setParent(mView); + } + + if (WebContentsAccessibilityImplJni.get() + .populateAccessibilityNodeInfo(mNativeObj, info, virtualViewId)) { + return info; + } else { + info.recycle(); + return null; + } + } + + /** * Deep equality check of two {@link AccessibilityNodeInfoCompat} nodes. * * <p>Requires API level 33 (Tiramisu) for deprecation of {@link @@ -1047,6 +1087,10 @@ return Arrays.equals(aParcel.marshall(), bParcel.marshall()); } + public int getCurrentRootIdForTesting() { + return mCurrentRootId; + } + @Override public @Nullable AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { if (!isAccessibilityEnabled()) { @@ -1101,19 +1145,9 @@ // Check cache freshness by drawing from C++ (Finch experiment). if (ContentFeatureList.enabledAccessibilityCheckJavaNodeCacheFreshness()) { final AccessibilityNodeInfoCompat freshNode = - AccessibilityNodeInfoCompat.obtain(mView); - freshNode.setPackageName(mContext.getPackageName()); - freshNode.setSource(mView, virtualViewId); - - if (virtualViewId == mCurrentRootId) { - freshNode.setParent(mView); - } - - if (WebContentsAccessibilityImplJni.get() - .populateAccessibilityNodeInfo(mNativeObj, freshNode, virtualViewId)) { - if (nodesAreEqual(cachedNode, freshNode)) { - mHistogramRecorder.incrementNodeWasFreshInCache(); - } + buildFreshAccessibilityNodeInfo(virtualViewId); + if (freshNode != null && nodesAreEqual(cachedNode, freshNode)) { + mHistogramRecorder.incrementNodeWasFreshInCache(); } // If node is still in the cache when it's not in C++, treat as stale } @@ -2598,6 +2632,8 @@ int getRootId(long nativeWebContentsAccessibilityAndroid); + long getAccessibilityTreeSizeForTesting(long nativeWebContentsAccessibilityAndroid); + boolean isNodeValid(long nativeWebContentsAccessibilityAndroid, int id); boolean isAutofillPopupNode(long nativeWebContentsAccessibilityAndroid, int id);
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/FakeAndroidCacheTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/FakeAndroidCacheTest.java new file mode 100644 index 0000000..7ab314f --- /dev/null +++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/FakeAndroidCacheTest.java
@@ -0,0 +1,199 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.accessibility; + +import android.os.Build; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.Batch; +import org.chromium.base.test.util.MinAndroidSdkLevel; + +/** Test suite for {@link FakeAndroidCache}. */ +@RunWith(BaseJUnit4ClassRunner.class) +@Batch(Batch.UNIT_TESTS) +@MinAndroidSdkLevel(Build.VERSION_CODES.TIRAMISU) +public class FakeAndroidCacheTest { + @Mock private WebContentsAccessibilityImpl mWebContentsAccessibility; + private FakeAndroidCache mFakeAndroidCache; + private final int mFirstNodeId = 1; + private final int mSecondNodeId = 2; + private final int mThirdNodeId = 3; + private final int mFourthNodeId = 4; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Mockito.when(mWebContentsAccessibility.getChildIdsForTesting(Mockito.anyInt())) + .thenReturn(new int[] {}); + Mockito.when(mWebContentsAccessibility.getCurrentRootIdForTesting()).thenReturn(-1); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(Mockito.anyInt())) + .thenReturn(null); + mFakeAndroidCache = new FakeAndroidCache(mWebContentsAccessibility); + } + + @Test + @SmallTest + public void testAddNodeAndRemoveIt() { + // Create a test node and remove it from the cache. + AccessibilityNodeInfoCompat testNode = + fillEmptyAccessibilityNodeInfoCompat("testNode", String.valueOf(mFirstNodeId)); + mFakeAndroidCache.addNode(mFirstNodeId, testNode); + mFakeAndroidCache.clearNode(mFirstNodeId, /* recursive= */ false); + + // To make sure its not in the cache, we make sure to mark is as stale by updating the + // expected node to be returned if ever built again. + AccessibilityNodeInfoCompat testNodeUpdated = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated", String.valueOf(mFirstNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mFirstNodeId)) + .thenReturn(testNodeUpdated); + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(0, mFakeAndroidCache.getStaleNodeCountForTesting()); + } + + @Test + @SmallTest + public void testDetectStaleNode() { + // Create a test node and add it to the cache. + AccessibilityNodeInfoCompat testNode = + fillEmptyAccessibilityNodeInfoCompat("testNode", String.valueOf(mFirstNodeId)); + mFakeAndroidCache.addNode(mFirstNodeId, testNode); + + // Make stale by making the expectation return a different node. + AccessibilityNodeInfoCompat testNodeUpdated = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated", String.valueOf(mFirstNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mFirstNodeId)) + .thenReturn(testNodeUpdated); + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(1, mFakeAndroidCache.getStaleNodeCountForTesting()); + } + + @Test + @SmallTest + public void testNotStaleChildNode() { + // Create a test node and add it to the cache. + AccessibilityNodeInfoCompat testNode1 = + fillEmptyAccessibilityNodeInfoCompat("testNode1", String.valueOf(mFirstNodeId)); + Mockito.when(mWebContentsAccessibility.getChildIdsForTesting(mFirstNodeId)) + .thenReturn(new int[] {mSecondNodeId}); + mFakeAndroidCache.addNode(mFirstNodeId, testNode1); + + AccessibilityNodeInfoCompat testNode2 = + fillEmptyAccessibilityNodeInfoCompat("testNode2", String.valueOf(mSecondNodeId)); + mFakeAndroidCache.addNode(mSecondNodeId, testNode2); + + mFakeAndroidCache.clearNode(mFirstNodeId, /* recursive= */ true); + + // If recreated, it should show up as stale when validating by returning a different node. + AccessibilityNodeInfoCompat testNodeUpdated2 = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated2", String.valueOf(mSecondNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mSecondNodeId)) + .thenReturn(testNodeUpdated2); + + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(0, mFakeAndroidCache.getStaleNodeCountForTesting()); + } + + @Test + @SmallTest + public void testDetectStaleChildNode() { + // Create a test node and add it to the cache. + AccessibilityNodeInfoCompat testNode1 = + fillEmptyAccessibilityNodeInfoCompat("testNode1", String.valueOf(mFirstNodeId)); + Mockito.when(mWebContentsAccessibility.getChildIdsForTesting(mFirstNodeId)) + .thenReturn(new int[] {mSecondNodeId}); + mFakeAndroidCache.addNode(mFirstNodeId, testNode1); + + AccessibilityNodeInfoCompat testNode2 = + fillEmptyAccessibilityNodeInfoCompat("testNode2", String.valueOf(mSecondNodeId)); + mFakeAndroidCache.addNode(mSecondNodeId, testNode2); + + mFakeAndroidCache.clearNode(mFirstNodeId, /* recursive= */ false); + + // Make stale by making the expectation return a different node. + AccessibilityNodeInfoCompat testNodeUpdated2 = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated2", String.valueOf(mSecondNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mSecondNodeId)) + .thenReturn(testNodeUpdated2); + + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(1, mFakeAndroidCache.getStaleNodeCountForTesting()); + } + + @Test + @SmallTest + public void testComplexTree() { + // Create a test node and add it to the cache. + AccessibilityNodeInfoCompat testNode1 = + fillEmptyAccessibilityNodeInfoCompat("testNode1", String.valueOf(mFirstNodeId)); + Mockito.when(mWebContentsAccessibility.getChildIdsForTesting(mFirstNodeId)) + .thenReturn(new int[] {mSecondNodeId}); + mFakeAndroidCache.addNode(mFirstNodeId, testNode1); + + AccessibilityNodeInfoCompat testNode2 = + fillEmptyAccessibilityNodeInfoCompat("testNode2", String.valueOf(mSecondNodeId)); + Mockito.when(mWebContentsAccessibility.getChildIdsForTesting(mSecondNodeId)) + .thenReturn(new int[] {mThirdNodeId, mFourthNodeId}); + mFakeAndroidCache.addNode(mSecondNodeId, testNode2); + + AccessibilityNodeInfoCompat testNode3 = + fillEmptyAccessibilityNodeInfoCompat("testNode3", String.valueOf(mThirdNodeId)); + mFakeAndroidCache.addNode(mThirdNodeId, testNode3); + + AccessibilityNodeInfoCompat testNode4 = + fillEmptyAccessibilityNodeInfoCompat("testNode4", String.valueOf(mFourthNodeId)); + mFakeAndroidCache.addNode(mFourthNodeId, testNode4); + + mFakeAndroidCache.clearNode(mSecondNodeId, /* recursive= */ false); + + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(0, mFakeAndroidCache.getStaleNodeCountForTesting()); + + // We then recursive clear node1, and expect for grand children to be cleared too. + mFakeAndroidCache.clearNode(mFirstNodeId, /* recursive= */ true); + + // We make the grand children return a mismatch if recreated when validating since they + // should have been cleared from the cache. + AccessibilityNodeInfoCompat testNodeUpdated3 = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated3", String.valueOf(mThirdNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mThirdNodeId)) + .thenReturn(testNodeUpdated3); + AccessibilityNodeInfoCompat testNodeUpdated4 = + fillEmptyAccessibilityNodeInfoCompat( + "testNodeUpdated4", String.valueOf(mFourthNodeId)); + Mockito.when(mWebContentsAccessibility.buildFreshAccessibilityNodeInfo(mFourthNodeId)) + .thenReturn(testNodeUpdated4); + + // There should be no stale nodes, we cleared all the cache. + mFakeAndroidCache.validateAccessibility(); + Assert.assertEquals(0, mFakeAndroidCache.getStaleNodeCountForTesting()); + } + + private AccessibilityNodeInfoCompat fillEmptyAccessibilityNodeInfoCompat( + String text, String uniqueId) { + // Obtain an empty AccessibilityNodeInfo object. + AccessibilityNodeInfoCompat nodeInfo = + new AccessibilityNodeInfoCompat(AccessibilityNodeInfo.obtain()); + nodeInfo.setText(text); + nodeInfo.setUniqueId(uniqueId); + return nodeInfo; + } +}
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java index 23a15fd2..9edeb75 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTest.java
@@ -3767,6 +3767,43 @@ histogramWatcher.assertExpected(); } + @Test + @SmallTest + public void testAccessibilityTreeSizeWithIframe() throws Throwable { + // Iframe content with 12 additional nodes + final String iframeContent = + "<html><body><h1>Iframe content</h1>" + + "<p>Node 1</p><p>Node 2</p><p>Node 3</p>" + + "<p>Node 4</p><p>Node 5</p><p>Node 6</p>" + + "<p>Node 7</p><p>Node 8</p><p>Node 9</p>" + + "<p>Node 10</p><p>Node 11</p>" + + "</body></html>"; + // Main page content with an iframe + final String html = + "<html><body><p>Main content</p>" + + "<iframe src='data:text/html," + + iframeContent + + "'></iframe>" + + "</body></html>"; + + setupTestWithHTML(html); + + // Wait for a node in the main frame to ensure it's loaded. + waitForNodeMatching(sTextMatcher, "Main content"); + + // Wait for nodes in the iframe to ensure it's loaded. + waitForNodeMatching(sTextMatcher, "Iframe content"); + waitForNodeMatching(sTextMatcher, "Node 11"); + + // Get the total size of the accessibility tree. + long treeSize = + ThreadUtils.runOnUiThreadBlocking( + () -> mActivityTestRule.mWcax.getAccessibilityTreeSizeForTesting()); + // 28 nodes in the iframe + 5 node in the main frame = 33 nodes. + Assert.assertTrue( + "Tree size should be greater than 15, but was " + treeSize, treeSize == 33L); + } + private void assertActionsContainNoScrolls(AccessibilityNodeInfoCompat nodeInfo) { Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_FORWARD)); Assert.assertFalse(nodeInfo.getActionList().contains(ACTION_SCROLL_BACKWARD));
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h index 7877643..73aaf2b 100644 --- a/content/public/browser/web_contents.h +++ b/content/public/browser/web_contents.h
@@ -1587,18 +1587,22 @@ // ui::Events will be always ignored without asking the callback. The given // callback will be invoked only while the returned ScopedIgnoreInputEvents // alives. + // By default, this also blocks all interactive accessibility actions, + // treating them as standard user input, while still permitting hit testing + // for screenreaders. using WebInputEventAuditCallback = base::RepeatingCallback<bool(const blink::WebInputEvent&)>; [[nodiscard]] inline ScopedIgnoreInputEvents IgnoreInputEvents( std::optional<WebInputEventAuditCallback> audit_callback) { return IgnoreInputEvents(std::move(audit_callback), - /*should_ignore_a11y_input=*/false); + /*should_ignore_a11y_input=*/true); } // If `should_ignore_a11y_input` is true, this also blocks all // accessibility actions from interacting with the WebContents, other than the // hit test. - // TODO(crbug.com/452693512): Consider ignoring a11y input events as the - // default behavior for ignoring input events in general. + // TODO(crbug.com/452693512): Remove this overloaded method and the + // `should_ignore_a11y_input` parameter once all callers have been migrated to + // the 1-argument version. [[nodiscard]] virtual ScopedIgnoreInputEvents IgnoreInputEvents( std::optional<WebInputEventAuditCallback> audit_callback, bool should_ignore_a11y_input) = 0;
diff --git a/content/public/browser/webid/identity_credential_source.h b/content/public/browser/webid/identity_credential_source.h index 63656970..8059da79 100644 --- a/content/public/browser/webid/identity_credential_source.h +++ b/content/public/browser/webid/identity_credential_source.h
@@ -51,6 +51,9 @@ const std::vector<GURL>& embedder_requested_idps, GetIdentityCredentialSuggestionsCallback callback) = 0; + // Returns whether there is a pending FedCM request on the page. + virtual bool HasPendingRequest() = 0; + // Selects the account with the given `account_id` from `idp_origin`. // Returns false if such an account is not found or there is no dialog. virtual bool SelectAccount(const url::Origin& idp_origin,
diff --git a/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java index 69eed4e..8460f17 100644 --- a/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java +++ b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java
@@ -65,7 +65,7 @@ mShellManager = (ShellManager) findViewById(getShellManagerViewId()); IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, /* listenToActivityState= */ true, intentRequestTracker,
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java index fdfa35f..fd50339 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
@@ -66,7 +66,7 @@ final boolean listenToActivityState = true; mIntentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - ActivityWindowAndroid.create( + new ActivityWindowAndroid( this, listenToActivityState, mIntentRequestTracker,
diff --git a/extensions/browser/api/storage/storage_frontend.cc b/extensions/browser/api/storage/storage_frontend.cc index 8adf0cf..d816e03 100644 --- a/extensions/browser/api/storage/storage_frontend.cc +++ b/extensions/browser/api/storage/storage_frontend.cc
@@ -16,6 +16,7 @@ #include "base/lazy_instance.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" +#include "base/metrics/histogram_functions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" @@ -33,6 +34,7 @@ #include "extensions/common/api/storage.h" #include "extensions/common/extension_id.h" #include "extensions/common/mojom/context_type.mojom.h" +#include "third_party/leveldatabase/env_chromium.h" using content::BrowserContext; using content::BrowserThread; @@ -45,6 +47,48 @@ base::LazyInstance<BrowserContextKeyedAPIFactory<StorageFrontend>>:: DestructorAtExit g_factory = LAZY_INSTANCE_INITIALIZER; +// Logs a database error or a restore/repair attempt for a local storage +// operation. +void LogLocalErrorOrRestore( + StorageFrontend::ExtensionsDatabaseOperation operation, + const ValueStore::Status& status, + StorageAreaNamespace storage_area) { + if (storage_area != StorageAreaNamespace::kLocal) { + return; + } + + std::string operation_name; + switch (operation) { + case StorageFrontend::ExtensionsDatabaseOperation::kGet: + operation_name = "get"; + break; + case StorageFrontend::ExtensionsDatabaseOperation::kSet: + operation_name = "set"; + break; + case StorageFrontend::ExtensionsDatabaseOperation::kRemove: + operation_name = "remove"; + break; + case StorageFrontend::ExtensionsDatabaseOperation::kClear: + operation_name = "clear"; + break; + } + + base::UmaHistogramEnumeration( + base::StringPrintf("Extensions.Database.Local.StatusCodeByOperation.%s", + operation_name.c_str()), + status.code, value_store::ValueStore::STATUS_CODE_MAX); + + if (status.restore_status != value_store::ValueStore::RESTORE_NONE) { + base::UmaHistogramEnumeration("Extensions.Database.Local.RestoreStatus", + status.restore_status, + value_store::ValueStore::RESTORE_STATUS_MAX); + } + + if (!status.ok()) { + base::UmaHistogramEnumeration("Extensions.Database.Local.ErrorByOperation", + operation); + } +} events::HistogramValue StorageAreaToEventHistogram( StorageAreaNamespace storage_area) { switch (storage_area) { @@ -57,25 +101,32 @@ case StorageAreaNamespace::kSession: return events::STORAGE_SESSION_ON_CHANGE; case StorageAreaNamespace::kInvalid: + default: NOTREACHED(); } } void GetKeysWithValueStore( + StorageAreaNamespace storage_area, base::OnceCallback<void(ValueStore::ReadResult)> callback, ValueStore* store) { ValueStore::ReadResult result = store->GetKeys(); + LogLocalErrorOrRestore(StorageFrontend::ExtensionsDatabaseOperation::kGet, + result.status(), storage_area); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); } void GetWithValueStore( + StorageAreaNamespace storage_area, std::optional<std::vector<std::string>> keys, base::OnceCallback<void(ValueStore::ReadResult)> callback, ValueStore* store) { ValueStore::ReadResult result = keys.has_value() ? store->Get(keys.value()) : store->Get(); + LogLocalErrorOrRestore(StorageFrontend::ExtensionsDatabaseOperation::kGet, + result.status(), storage_area); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); @@ -92,27 +143,36 @@ } void SetWithValueStore( + StorageAreaNamespace storage_area, const base::DictValue& values, base::OnceCallback<void(ValueStore::WriteResult)> callback, ValueStore* store) { ValueStore::WriteResult result = store->Set(ValueStore::DEFAULTS, values); + LogLocalErrorOrRestore(StorageFrontend::ExtensionsDatabaseOperation::kSet, + result.status(), storage_area); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); } void RemoveWithValueStore( + StorageAreaNamespace storage_area, std::vector<std::string> keys, base::OnceCallback<void(ValueStore::WriteResult)> callback, ValueStore* store) { ValueStore::WriteResult result = store->Remove(keys); + LogLocalErrorOrRestore(StorageFrontend::ExtensionsDatabaseOperation::kRemove, + result.status(), storage_area); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); } void ClearWithValueStore( + StorageAreaNamespace storage_area, base::OnceCallback<void(ValueStore::WriteResult)> callback, ValueStore* store) { ValueStore::WriteResult result = store->Clear(); + LogLocalErrorOrRestore(StorageFrontend::ExtensionsDatabaseOperation::kClear, + result.status(), storage_area); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(result))); } @@ -296,7 +356,7 @@ RunWithStorage( extension, settings_namespace, - base::BindOnce(&GetWithValueStore, std::move(keys), + base::BindOnce(&GetWithValueStore, storage_area, std::move(keys), base::BindOnce(&StorageFrontend::OnReadFinished, weak_factory_.GetWeakPtr(), extension->id(), storage_area, std::move(callback)))); @@ -338,8 +398,9 @@ base::OnceCallback<void(ValueStore::ReadResult)> test = base::BindOnce(&StorageFrontend::OnReadKeysFinished, weak_factory_.GetWeakPtr(), std::move(callback)); - RunWithStorage(extension, settings_namespace, - base::BindOnce(&GetKeysWithValueStore, std::move(test))); + RunWithStorage( + extension, settings_namespace, + base::BindOnce(&GetKeysWithValueStore, storage_area, std::move(test))); } void StorageFrontend::GetBytesInUse( @@ -420,7 +481,7 @@ RunWithStorage( extension, settings_namespace, - base::BindOnce(&SetWithValueStore, std::move(values), + base::BindOnce(&SetWithValueStore, storage_area, std::move(values), base::BindOnce(&StorageFrontend::OnWriteFinished, weak_factory_.GetWeakPtr(), extension->id(), storage_area, std::move(callback)))); @@ -461,7 +522,7 @@ RunWithStorage( extension, settings_namespace, - base::BindOnce(&RemoveWithValueStore, keys, + base::BindOnce(&RemoveWithValueStore, storage_area, keys, base::BindOnce(&StorageFrontend::OnWriteFinished, weak_factory_.GetWeakPtr(), extension->id(), storage_area, std::move(callback)))); @@ -502,7 +563,7 @@ RunWithStorage( extension, settings_namespace, - base::BindOnce(&ClearWithValueStore, + base::BindOnce(&ClearWithValueStore, storage_area, base::BindOnce(&StorageFrontend::OnWriteFinished, weak_factory_.GetWeakPtr(), extension->id(), storage_area, std::move(callback))));
diff --git a/extensions/browser/api/storage/storage_frontend.h b/extensions/browser/api/storage/storage_frontend.h index 2c37556..909f9e5 100644 --- a/extensions/browser/api/storage/storage_frontend.h +++ b/extensions/browser/api/storage/storage_frontend.h
@@ -64,6 +64,16 @@ std::optional<base::DictValue> data; }; + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum class ExtensionsDatabaseOperation { + kGet = 0, + kSet = 1, + kRemove = 2, + kClear = 3, + kMaxValue = kClear, + }; + // Returns the current instance for `context`. static StorageFrontend* Get(content::BrowserContext* context);
diff --git a/extensions/browser/api/storage/storage_frontend_unittest.cc b/extensions/browser/api/storage/storage_frontend_unittest.cc index aa68cb6..83ec1afe 100644 --- a/extensions/browser/api/storage/storage_frontend_unittest.cc +++ b/extensions/browser/api/storage/storage_frontend_unittest.cc
@@ -11,6 +11,7 @@ #include "base/functional/bind.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" +#include "base/test/metrics/histogram_tester.h" #include "components/value_store/value_store.h" #include "components/value_store/value_store_factory_impl.h" #include "content/public/browser/browser_context.h" @@ -19,6 +20,8 @@ #include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/storage/settings_namespace.h" #include "extensions/browser/api/storage/settings_test_util.h" +#include "extensions/browser/api/storage/storage_area_namespace.h" +#include "extensions/browser/api/storage/storage_frontend.h" #include "extensions/browser/extensions_test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -220,4 +223,77 @@ local_storage->Set(DEFAULTS, "WillError", megabyte).status().ok()); } +// Tests that a successful extension storage operation correctly emits success +// metrics for the underlying database status. +TEST_F(ExtensionSettingsFrontendTest, EmitUmaLevelDBMetrics) { + base::HistogramTester histogram_tester; + + const std::string id = "ext"; + scoped_refptr<const Extension> extension = + settings_test_util::AddExtensionWithId(browser_context(), id, + Manifest::Type::kExtension); + + base::RunLoop run_loop; + base::DictValue values; + values.Set("foo", "bar"); + + frontend_->Set(extension, StorageAreaNamespace::kLocal, std::move(values), + base::BindOnce( + [](base::OnceClosure quit_closure, + StorageFrontend::ResultStatus status) { + EXPECT_TRUE(status.success); + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure())); + run_loop.Run(); + + // The Set operation should have succeeded and emitted LevelDB UMA. + histogram_tester.ExpectUniqueSample( + "Extensions.Database.Local.StatusCodeByOperation.set", + /*sample=*/value_store::ValueStore::StatusCode::OK, + /*expected_bucket_count=*/1); +} + +// Tests that a failed extension storage operation (e.g., due to exceeding the +// quota limit) correctly emits an error metric for the high-level operation +// that failed. +TEST_F(ExtensionSettingsFrontendTest, SettingsQuotaExceededEmitsErrorMetric) { + base::HistogramTester histogram_tester; + + const std::string id = "ext"; + scoped_refptr<const Extension> extension = + settings_test_util::AddExtensionWithId(browser_context(), id, + Manifest::Type::kExtension); + + base::RunLoop run_loop; + base::DictValue values; + // Local quota is 10MB. Setting 11MB should fail. + base::ListValue megabytes; + base::Value megabyte = settings_test_util::CreateMegabyte(); + for (int i = 0; i < 11; ++i) { + megabytes.Append(megabyte.Clone()); + } + values.Set("too_big", megabytes.Clone()); + + frontend_->Set(extension, StorageAreaNamespace::kLocal, std::move(values), + base::BindOnce( + [](base::OnceClosure quit_closure, + StorageFrontend::ResultStatus status) { + EXPECT_FALSE(status.success); + std::move(quit_closure).Run(); + }, + run_loop.QuitClosure())); + run_loop.Run(); + + // The Set operation should have failed and emitted error metrics. + histogram_tester.ExpectUniqueSample( + "Extensions.Database.Local.ErrorByOperation", + /*sample=*/StorageFrontend::ExtensionsDatabaseOperation::kSet, + /*expected_bucket_count=*/1); + histogram_tester.ExpectUniqueSample( + "Extensions.Database.Local.StatusCodeByOperation.set", + /*sample=*/value_store::ValueStore::StatusCode::QUOTA_EXCEEDED, + /*expected_bucket_count=*/1); +} + } // namespace extensions
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h index 3155f91..68e11da 100644 --- a/extensions/browser/api/web_request/web_request_api.h +++ b/extensions/browser/api/web_request/web_request_api.h
@@ -343,7 +343,7 @@ const ExtensionId& extension_id, const std::string& sub_event_name); - // Internal implemntation of MaybeProxyURLLoaderFactory that returns a + // Internal implementation of MaybeProxyURLLoaderFactory that returns a // detailed reason, ProxyDecision, to tell why the proxy is used. ProxyDecision MaybeProxyURLLoaderFactoryInternal( content::BrowserContext* browser_context,
diff --git a/ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h b/ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h index d9a5d44..147f520 100644 --- a/ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h +++ b/ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h
@@ -7,8 +7,10 @@ #import <Foundation/Foundation.h> -// Accessibility identifier for the Autofill AI Save Entity TableView. +// Accessibility identifier for the Autofill AI Save Entity table view. extern NSString* const kAutofillAISaveEntityTableViewId; + +// Accessibility identifier for the Autofill AI Save Entity close button. extern NSString* const kAutofillAISaveEntityCancelButtonId; #endif // IOS_CHROME_BROWSER_AUTOFILL_AUTOFILL_AI_PUBLIC_AUTOFILL_AI_CONSTANTS_H_
diff --git a/ios/chrome/browser/autofill/autofill_ai/test/BUILD.gn b/ios/chrome/browser/autofill/autofill_ai/test/BUILD.gn new file mode 100644 index 0000000..beff44f --- /dev/null +++ b/ios/chrome/browser/autofill/autofill_ai/test/BUILD.gn
@@ -0,0 +1,22 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("eg2_tests") { + configs += [ "//build/config/ios:xctest_config" ] + testonly = true + sources = [ "autofill_ai_egtest.mm" ] + deps = [ + "//components/autofill/core/browser", + "//components/strings", + "//ios/chrome/app/strings", + "//ios/chrome/browser/authentication/test:eg_test_support+eg2", + "//ios/chrome/browser/autofill/autofill_ai/public", + "//ios/chrome/browser/autofill/ui_bundled:eg_test_support+eg2", + "//ios/chrome/browser/shared/public/features", + "//ios/chrome/browser/signin/model:fake_system_identity", + "//ios/chrome/test/earl_grey:eg_test_support+eg2", + "//ios/testing/earl_grey:eg_test_support+eg2", + ] + frameworks = [ "UIKit.framework" ] +}
diff --git a/ios/chrome/browser/autofill/autofill_ai/test/autofill_ai_egtest.mm b/ios/chrome/browser/autofill/autofill_ai/test/autofill_ai_egtest.mm new file mode 100644 index 0000000..00cdddb --- /dev/null +++ b/ios/chrome/browser/autofill/autofill_ai/test/autofill_ai_egtest.mm
@@ -0,0 +1,83 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "components/autofill/core/common/autofill_features.h" +#import "components/strings/grit/components_strings.h" +#import "ios/chrome/browser/authentication/test/signin_earl_grey.h" +#import "ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h" +#import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h" +#import "ios/chrome/browser/shared/public/features/features.h" +#import "ios/chrome/browser/signin/model/fake_system_identity.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_earl_grey_ui.h" +#import "ios/chrome/test/earl_grey/chrome_matchers.h" +#import "ios/chrome/test/earl_grey/chrome_matchers_app_interface.h" +#import "ios/chrome/test/earl_grey/chrome_test_case.h" +#import "ios/testing/earl_grey/earl_grey_test.h" +#import "ui/base/l10n/l10n_util.h" + +namespace { + +// Save button on the infobar. +id<GREYMatcher> infoBarSaveButton() { + return chrome_test_util::ButtonWithAccessibilityLabel( + l10n_util::GetNSString(IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL)); +} + +// UITableView to show the new entity and the old entity if there is one. +id<GREYMatcher> entitiesView() { + return grey_accessibilityID(kAutofillAISaveEntityTableViewId); +} + +// Save button on the save entity view controller. +id<GREYMatcher> saveEntityButton() { + return chrome_test_util::ButtonWithAccessibilityLabel( + l10n_util::GetNSString(IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL)); +} + +} // namespace + +@interface AutofillAIEGTest : ChromeTestCase +@end + +@implementation AutofillAIEGTest + +- (AppLaunchConfiguration)appConfigurationForTestCase { + AppLaunchConfiguration config; + config.features_enabled.push_back( + autofill::features::kAutofillAiWithDataSchema); + return config; +} + +- (void)setUp { + [super setUp]; + [SigninEarlGrey signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]]; +} + +// Tests the infobar banner shows and can be tapped. Once tapped, the detailed +// save entity UI is shown. +- (void)testSaveEntityUI { + // Simulate a trigger for the infobar. + [AutofillAppInterface showAutofillAiSaveEntityBubble]; + + // Tap the banner. + [[EarlGrey selectElementWithMatcher:infoBarSaveButton()] + performAction:grey_tap()]; + + // Verify the detailed UI. + [[EarlGrey selectElementWithMatcher:entitiesView()] + assertWithMatcher:grey_notNil()]; + + // Tap the final Save button. + [[EarlGrey selectElementWithMatcher:saveEntityButton()] + performAction:grey_tap()]; + + // Verify the detailed UI is gone. + [[EarlGrey selectElementWithMatcher:entitiesView()] + assertWithMatcher:grey_nil()]; +} + +@end
diff --git a/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h b/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h index f8ebf1b0..994e102 100644 --- a/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h +++ b/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h
@@ -160,6 +160,9 @@ // Sets the CVC storage preference. + (void)setPaymentCvcStorageEnabled:(BOOL)enabled; +// Triggers the Autofill AI save entity bubble. ++ (void)showAutofillAiSaveEntityBubble; + @end #endif // IOS_CHROME_BROWSER_AUTOFILL_UI_BUNDLED_AUTOFILL_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.mm b/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.mm index 06a3972..1d6d07a9 100644 --- a/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.mm +++ b/ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.mm
@@ -11,11 +11,14 @@ #import "base/strings/sys_string_conversions.h" #import "base/strings/utf_string_conversions.h" #import "base/test/ios/wait_util.h" +#import "base/uuid.h" #import "components/application_locale_storage/application_locale_storage.h" #import "components/autofill/core/browser/data_manager/addresses/address_data_manager.h" #import "components/autofill/core/browser/data_manager/payments/payments_data_manager.h" #import "components/autofill/core/browser/data_manager/personal_data_manager.h" #import "components/autofill/core/browser/data_model/addresses/autofill_profile_test_api.h" +#import "components/autofill/core/browser/data_model/addresses/autofill_structured_address_component.h" +#import "components/autofill/core/browser/field_types.h" #import "components/autofill/core/browser/form_import/form_data_importer.h" #import "components/autofill/core/browser/form_import/payments/payments_form_data_importer.h" #import "components/autofill/core/browser/foundations/autofill_client.h" @@ -25,6 +28,7 @@ #import "components/autofill/core/browser/payments/payments_network_interface.h" #import "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h" #import "components/autofill/core/browser/test_utils/autofill_test_utils.h" +#import "components/autofill/core/browser/test_utils/entity_data_test_utils.h" #import "components/autofill/core/common/autofill_prefs.h" #import "components/autofill/ios/browser/autofill_driver_ios.h" #import "components/autofill/ios/browser/autofill_java_script_feature.h" @@ -723,4 +727,17 @@ return personalDataManager; } ++ (void)showAutofillAiSaveEntityBubble { + autofill::EntityInstance entity = autofill::test::GetVehicleEntityInstance(); + autofill::ChromeAutofillClientIOS* client = + static_cast<autofill::ChromeAutofillClientIOS*>( + autofill::AutofillClientIOS::FromWebState( + chrome_test_util::GetCurrentWebState())); + if (client) { + client->ShowEntityImportBubble(std::move(entity), std::nullopt, + /*save_is_synchronous=*/true, + base::DoNothing()); + } +} + @end
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index c120307..bf1f5e9 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -2734,6 +2734,15 @@ {"ios-tab-reminders", flag_descriptions::kIOSTabRemindersName, flag_descriptions::kIOSTabRemindersDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(send_tab_to_self::kIOSTabReminders)}, + {"gemini-maps-rich-ui", flag_descriptions::kGeminiMapsRichUIName, + flag_descriptions::kGeminiMapsRichUIDescription, flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kGeminiMapsRichUI)}, + {"gemini-unary-migration", flag_descriptions::kGeminiUnaryMigrationName, + flag_descriptions::kGeminiUnaryMigrationDescription, flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kGeminiUnaryMigration)}, + {"gemini-binary-migration", flag_descriptions::kGeminiBinaryMigrationName, + flag_descriptions::kGeminiBinaryMigrationDescription, flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kGeminiBinaryMigration)}, }); bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 9369be1e..805b7de 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -650,6 +650,10 @@ const char kGeminiBackendMigrationDescription[] = "Enables the backend migration for Gemini."; +const char kGeminiBinaryMigrationName[] = "Gemini Binary Migration"; +const char kGeminiBinaryMigrationDescription[] = + "Enables the binary network migration for Gemini."; + const char kGeminiChatPersistenceName[] = "Gemini Chat Persistence"; const char kGeminiChatPersistenceDescription[] = "Enables improvements to Gemini Chat persistence."; @@ -686,6 +690,10 @@ const char kGeminiLoadingStateRedesignDescription[] = "Enables the redesigned UI for the floaty's loading state."; +const char kGeminiMapsRichUIName[] = "Gemini Maps Rich UI"; +const char kGeminiMapsRichUIDescription[] = + "Enables the rich Maps UI in Gemini."; + const char kGeminiNavigationPromoName[] = "GeminiNavigationPromo"; const char kGeminiNavigationPromoDescription[] = "Enables the automatic promo for Gemini on navigation."; @@ -707,6 +715,10 @@ const char kGeminiRichAPCExtractionDescription[] = "Enables rich APC extraction for Gemini."; +const char kGeminiUnaryMigrationName[] = "Gemini Unary Migration"; +const char kGeminiUnaryMigrationDescription[] = + "Enables the unary network migration for Gemini."; + const char kGeminiUpdatedEligibilityName[] = "Gemini Updated Eligibility"; const char kGeminiUpdatedEligibilityDescription[] = "Enables the updated eligibility checks for Gemini users.";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index bbd5e9f..a588b41 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -399,6 +399,9 @@ extern const char kGeminiBackendMigrationName[]; extern const char kGeminiBackendMigrationDescription[]; +extern const char kGeminiBinaryMigrationName[]; +extern const char kGeminiBinaryMigrationDescription[]; + extern const char kGeminiChatPersistenceName[]; extern const char kGeminiChatPersistenceDescription[]; @@ -426,6 +429,9 @@ extern const char kGeminiLoadingStateRedesignName[]; extern const char kGeminiLoadingStateRedesignDescription[]; +extern const char kGeminiMapsRichUIName[]; +extern const char kGeminiMapsRichUIDescription[]; + extern const char kGeminiNavigationPromoName[]; extern const char kGeminiNavigationPromoDescription[]; @@ -441,6 +447,9 @@ extern const char kGeminiRichAPCExtractionName[]; extern const char kGeminiRichAPCExtractionDescription[]; +extern const char kGeminiUnaryMigrationName[]; +extern const char kGeminiUnaryMigrationDescription[]; + extern const char kGeminiUpdatedEligibilityName[]; extern const char kGeminiUpdatedEligibilityDescription[];
diff --git a/ios/chrome/browser/fullscreen/ui_bundled/fullscreen_mediator.mm b/ios/chrome/browser/fullscreen/ui_bundled/fullscreen_mediator.mm index 06838342..037fc9b 100644 --- a/ios/chrome/browser/fullscreen/ui_bundled/fullscreen_mediator.mm +++ b/ios/chrome/browser/fullscreen/ui_bundled/fullscreen_mediator.mm
@@ -263,6 +263,7 @@ } else if (model_->progress() == 1) { RecordFullscreenExitMode(); } + [mediator->resizer_ forceToUpdateToProgress:final_progress]; for (auto& observer : mediator->observers_) { observer.FullscreenDidAnimate(mediator->controller_, style); }
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h index cdf1056..9a9b9b1b 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h
@@ -224,8 +224,8 @@ gemini::FloatyUpdateSource source, bool is_presented); - // Returns true if the floaty is temporarily hidden. - bool IsFloatyTemporarilyHidden() const; + // Returns true if the floaty has active hiding sources. + bool DoesFloatyHaveActiveHidingSources() const; // Returns true if the floaty is only hidden by the keyboard. bool IsOnlyHiddenByKeyboard() const; @@ -286,6 +286,10 @@ // Whether the floaty is currently invoked. bool is_floaty_invoked_ = false; + // Whether the floaty is temporarily hidden. Used to hide the floaty without + // triggering logic related to ending floaty persistence. + bool is_floaty_temporarily_hidden_ = false; + // Records when the floaty was last hidden. Prevents the floaty from // reappearing too soon, particularly after a // `HideFloatyIfInvoked()` call during parent/child view
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm index c88ac611..bd790a3b1 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm
@@ -239,7 +239,7 @@ // If the floaty is expanded or temporarily hidden, the floaty should not be // re-shown on keyboard updates. if (last_shown_view_state_ == ios::provider::GeminiViewState::kExpanded || - IsFloatyTemporarilyHidden()) { + is_floaty_temporarily_hidden_) { return; } @@ -351,8 +351,8 @@ source == gemini::FloatyUpdateSource::ForcedFromQueryResponse; // Re-show the floaty if a user receives a query response. - return IsFloatyTemporarilyHidden() ? !is_source_query_response - : is_source_query_response; + return is_floaty_temporarily_hidden_ ? !is_source_query_response + : is_source_query_response; } GeminiPageContext* GeminiBrowserAgent::CreateGeminiPageContext( @@ -380,7 +380,7 @@ void GeminiBrowserAgent::UpdateForTraitCollection( UITraitCollection* traitCollection) { - if (IsFloatyTemporarilyHidden()) { + if (is_floaty_temporarily_hidden_) { return; } @@ -600,7 +600,7 @@ // shown underneath the Gemini floaty, don't clean up and reset internal // Gemini properties. Clean up should occur if a user taps the floaty to // dismiss it or the browser agent destructing. - if (IsFloatyTemporarilyHidden()) { + if (is_floaty_temporarily_hidden_) { return; } @@ -624,19 +624,20 @@ case gemini::FloatyUpdateSource::Unknown: case gemini::FloatyUpdateSource::ContextMenu: case gemini::FloatyUpdateSource::WebContextMenu: - return false; - case gemini::FloatyUpdateSource::ViewTransition: - case gemini::FloatyUpdateSource::WebNavigation: - case gemini::FloatyUpdateSource::TabGrid: - case gemini::FloatyUpdateSource::ForcedFromScroll: - case gemini::FloatyUpdateSource::Overlay: case gemini::FloatyUpdateSource::IneligibleSite: case gemini::FloatyUpdateSource::ForcedFromQueryResponse: - case gemini::FloatyUpdateSource::Snackbar: - case gemini::FloatyUpdateSource::Alert: + case gemini::FloatyUpdateSource::TabGrid: case gemini::FloatyUpdateSource::Banner: - case gemini::FloatyUpdateSource::Keyboard: + case gemini::FloatyUpdateSource::Alert: + case gemini::FloatyUpdateSource::Snackbar: + case gemini::FloatyUpdateSource::Overlay: + case gemini::FloatyUpdateSource::ForcedFromScroll: + case gemini::FloatyUpdateSource::WebNavigation: case gemini::FloatyUpdateSource::GestureIph: + return false; + case gemini::FloatyUpdateSource::ViewTransition: + case gemini::FloatyUpdateSource::Keyboard: + return true; } } @@ -644,24 +645,22 @@ void GeminiBrowserAgent::HideFloatyIfInvoked( bool animated, gemini::FloatyUpdateSource source) { + UpdateActiveTabHelperWithPresentedSource(source, /*is_presented=*/true); + if (!is_floaty_invoked_) { return; } floaty_hidden_timestamp_ = base::TimeTicks::Now(); - - UpdateActiveTabHelperWithPresentedSource(source, /*is_presented=*/true); - - bool was_temporarily_hidden = IsFloatyTemporarilyHidden(); - if (ShouldSourceReshowFloaty(source)) { active_hiding_sources_.insert(source); } - if (was_temporarily_hidden) { + if (is_floaty_temporarily_hidden_) { return; } + is_floaty_temporarily_hidden_ = true; ios::provider::GeminiViewState current_view_state = ios::provider::GetCurrentGeminiViewState(); SetLastShownViewState(current_view_state); @@ -706,13 +705,13 @@ if (is_web_navigation) { active_hiding_sources_.clear(); } - - if (IsFloatyTemporarilyHidden()) { + if (DoesFloatyHaveActiveHidingSources()) { return; } RecordGeminiViewStateHiddenToShown(last_shown_view_state_); RecordFloatyShownFromSource(source); + is_floaty_temporarily_hidden_ = false; // Exit fullscreen to prepare floaty for incoming response stream. if (source == gemini::FloatyUpdateSource::ForcedFromQueryResponse) { @@ -784,7 +783,7 @@ // therefore we should force-show the floaty if invoked. Uses the command // handler to do eligibility checks outside of this browser agent before // showing the floaty. - if (IsFloatyTemporarilyHidden()) { + if (is_floaty_temporarily_hidden_) { id<BWGCommands> gemini_handler = HandlerForProtocol(browser_->GetCommandDispatcher(), BWGCommands); [gemini_handler @@ -819,7 +818,7 @@ void GeminiBrowserAgent::FullscreenProgressUpdated( FullscreenController* controller, CGFloat progress) { - if (!is_floaty_invoked_ || IsFloatyTemporarilyHidden()) { + if (!is_floaty_invoked_ || is_floaty_temporarily_hidden_) { return; } @@ -856,7 +855,7 @@ } } -bool GeminiBrowserAgent::IsFloatyTemporarilyHidden() const { +bool GeminiBrowserAgent::DoesFloatyHaveActiveHidingSources() const { return !active_hiding_sources_.empty(); }
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent_unittest.mm b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent_unittest.mm index be1a83c..fdd5a93b 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent_unittest.mm +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent_unittest.mm
@@ -155,7 +155,7 @@ // Getter for `is_floaty_temporarily_hidden_`. bool IsFloatyTemporarilyHidden() { - return !gemini_browser_agent_->active_hiding_sources_.empty(); + return gemini_browser_agent_->is_floaty_temporarily_hidden_; } // Getter for `last_shown_view_state_`. @@ -168,11 +168,6 @@ gemini_browser_agent_->is_floaty_invoked_ = is_invoked; } - // Add a source to `active_hiding_sources_`. - void AddActiveHidingSource(gemini::FloatyUpdateSource source) { - gemini_browser_agent_->active_hiding_sources_.insert(source); - } - // Clear `active_hiding_sources_`. void ClearActiveHidingSources() { gemini_browser_agent_->active_hiding_sources_.clear(); @@ -550,7 +545,8 @@ // dismiss it. TEST_F(GeminiBrowserAgentTest, TestDismissFloatyWhenTemporarilyHidden) { SetIsFloatyInvoked(true); - AddActiveHidingSource(gemini::FloatyUpdateSource::ViewTransition); + gemini_browser_agent_->HideFloatyIfInvoked( + /*animated=*/true, /*source=*/gemini::FloatyUpdateSource::ViewTransition); gemini_browser_agent_->DismissFloaty();
diff --git a/ios/chrome/browser/intelligence/features/features.h b/ios/chrome/browser/intelligence/features/features.h index 8d17af89..28ab149 100644 --- a/ios/chrome/browser/intelligence/features/features.h +++ b/ios/chrome/browser/intelligence/features/features.h
@@ -355,4 +355,22 @@ BASE_DECLARE_FEATURE(kGeminiFloatyAllPages); bool IsGeminiFloatyAllPagesEnabled(); +// Enables the GeminiMapsRichUI feature. +BASE_DECLARE_FEATURE(kGeminiMapsRichUI); + +// Returns true if the GeminiMapsRichUI feature is enabled. +bool IsGeminiMapsRichUIEnabled(); + +// Enables the GeminiUnaryMigration feature. +BASE_DECLARE_FEATURE(kGeminiUnaryMigration); + +// Returns true if the GeminiUnaryMigration feature is enabled. +bool IsGeminiUnaryMigrationEnabled(); + +// Enables the GeminiBinaryMigration feature. +BASE_DECLARE_FEATURE(kGeminiBinaryMigration); + +// Returns true if the GeminiBinaryMigration feature is enabled. +bool IsGeminiBinaryMigrationEnabled(); + #endif // IOS_CHROME_BROWSER_INTELLIGENCE_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/intelligence/features/features.mm b/ios/chrome/browser/intelligence/features/features.mm index 2483ef80..8cdeda7 100644 --- a/ios/chrome/browser/intelligence/features/features.mm +++ b/ios/chrome/browser/intelligence/features/features.mm
@@ -600,3 +600,21 @@ bool IsGeminiFloatyAllPagesEnabled() { return base::FeatureList::IsEnabled(kGeminiFloatyAllPages); } + +BASE_FEATURE(kGeminiMapsRichUI, base::FEATURE_DISABLED_BY_DEFAULT); + +bool IsGeminiMapsRichUIEnabled() { + return base::FeatureList::IsEnabled(kGeminiMapsRichUI); +} + +BASE_FEATURE(kGeminiUnaryMigration, base::FEATURE_DISABLED_BY_DEFAULT); + +bool IsGeminiUnaryMigrationEnabled() { + return base::FeatureList::IsEnabled(kGeminiUnaryMigration); +} + +BASE_FEATURE(kGeminiBinaryMigration, base::FEATURE_DISABLED_BY_DEFAULT); + +bool IsGeminiBinaryMigrationEnabled() { + return base::FeatureList::IsEnabled(kGeminiBinaryMigration); +}
diff --git a/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm b/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm index 8931d00..5b2ba75b 100644 --- a/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm +++ b/ios/chrome/browser/location_bar/badge/coordinator/location_bar_badge_mediator.mm
@@ -201,6 +201,8 @@ didStartNavigation:(web::NavigationContext*)navigationContext { // Do not modify badge state if the navigation is on the same document. if (!navigationContext->IsSameDocument()) { + _promoStartTimer = nil; + _promoEndTimer = nil; [self.consumer hideBadge]; } } @@ -675,10 +677,6 @@ BOOL eligibleTimeWindow = timeSinceLastShown >= base::Hours(kGeminiContextualCueChipSlidingWindow); - if (IsAskGeminiChipIgnoreCriteria()) { - return YES; - } - // If the promo timers have already started, do not allow the chip to show to // avoid calling `ShouldTriggerHelpUI()` when the chip is in the process of // being displayed. @@ -686,6 +684,10 @@ return NO; } + if (IsAskGeminiChipIgnoreCriteria()) { + return YES; + } + return isPageEligible && isConsentEligible && eligibleTimeWindow && _tracker->WouldTriggerHelpUI( feature_engagement::kIPHiOSGeminiContextualCueChip);
diff --git a/ios/chrome/browser/location_bar/badge/ui/location_bar_badge_view_controller.mm b/ios/chrome/browser/location_bar/badge/ui/location_bar_badge_view_controller.mm index 0f4b4224..e7065ecf 100644 --- a/ios/chrome/browser/location_bar/badge/ui/location_bar_badge_view_controller.mm +++ b/ios/chrome/browser/location_bar/badge/ui/location_bar_badge_view_controller.mm
@@ -736,7 +736,9 @@ } - (void)transitionToSmallEntrypoint { - [self collapseBadgeContainer]; + if ([_badgeConfig isContextualPanelEntrypointBadge]) { + [self collapseBadgeContainer]; + } } - (void)transitionToContextualPanelOpenedState:(BOOL)opened { @@ -831,6 +833,7 @@ [self.view layoutIfNeeded]; [self refreshVoiceOverBoundingBoxIfFocused]; + _badgeConfig = nil; } - (void)collapseBadgeContainer {
diff --git a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_coordinator.mm b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_coordinator.mm index 70b3d24..9d8f41b7 100644 --- a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_coordinator.mm +++ b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_coordinator.mm
@@ -281,6 +281,7 @@ } _viewController = nil; + [_mediator disconnect]; _mediator = nil; }
diff --git a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.h b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.h index bf797992..c2b38018 100644 --- a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.h +++ b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.h
@@ -68,6 +68,9 @@ // Reserves a plus address. - (void)reservePlusAddress; +// Disconnects the mediator. +- (void)disconnect; + @end #endif // IOS_CHROME_BROWSER_PLUS_ADDRESSES_COORDINATOR_PLUS_ADDRESS_BOTTOM_SHEET_MEDIATOR_H_
diff --git a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.mm b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.mm index 796563c..3bc9da7e 100644 --- a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.mm +++ b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator.mm
@@ -56,7 +56,7 @@ url::Origin _mainFrameOrigin; // The reserved plus address, which is then eligible for confirmation. NSString* _reservedPlusAddress; - raw_ptr<UrlLoadingBrowserAgent, DanglingUntriaged> _urlLoader; + raw_ptr<UrlLoadingBrowserAgent> _urlLoader; BOOL _incognito; // The delegate for this mediator. @@ -276,4 +276,10 @@ } } +- (void)disconnect { + _plusAddressService = nullptr; + _plusAddressSettingService = nullptr; + _urlLoader = nullptr; +} + @end
diff --git a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator_unittest.mm b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator_unittest.mm index a0dedf8..2baa2f75 100644 --- a/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator_unittest.mm +++ b/ios/chrome/browser/plus_addresses/coordinator/plus_address_bottom_sheet_mediator_unittest.mm
@@ -66,6 +66,11 @@ PlusAddressBottomSheetMediator* mediator() { return mediator_; } FakeUrlLoadingBrowserAgent* url_loader() { return url_loader_.get(); } + void TearDown() override { + [mediator_ disconnect]; + PlatformTest::TearDown(); + } + id consumer_; private:
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn index 5fa19bb..fcfb384 100644 --- a/ios/chrome/test/earl_grey2/BUILD.gn +++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -192,6 +192,7 @@ deps = [ "//ios/chrome/app/application_delegate:eg2_tests", "//ios/chrome/browser/autofill/authentication/test:eg2_tests", + "//ios/chrome/browser/autofill/autofill_ai/test:eg2_tests", "//ios/chrome/browser/autofill/form_input_accessory/test:eg2_tests", "//ios/chrome/browser/autofill/ui_bundled/bottom_sheet:eg2_tests", "//ios/chrome/browser/autofill/ui_bundled/progress_dialog:eg2_tests",
diff --git a/ios_internal b/ios_internal index 41d2c40..9dd3170 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit 41d2c40a3c313310de29d05825893caea562da5d +Subproject commit 9dd3170ffb37ac42975a3e61ab8dc264b944ff63
diff --git a/media/formats/mp4/aac.cc b/media/formats/mp4/aac.cc index fd3600c..1005d47 100644 --- a/media/formats/mp4/aac.cc +++ b/media/formats/mp4/aac.cc
@@ -171,15 +171,15 @@ return std::min(2 * frequency_, 48000u); } -ChannelLayout AAC::GetChannelLayout(bool sbr_in_mimetype) const { +ChannelLayoutConfig AAC::GetChannelLayout(bool sbr_in_mimetype) const { // Check for implicit signalling of HE-AAC and indicate stereo output // if the mono channel configuration is signalled. // See ISO 14496-3:2009 Section 1.6.5.3 for details about this special casing. if (sbr_in_mimetype && channel_config_ == 1) { - return CHANNEL_LAYOUT_STEREO; + return ChannelLayoutConfig::Stereo(); } - return channel_layout_; + return ChannelLayoutConfig::FromLayout(channel_layout_); } base::HeapArray<uint8_t> AAC::CreateAdtsFromEsds(
diff --git a/media/formats/mp4/aac.h b/media/formats/mp4/aac.h index 982a9da..5d33541 100644 --- a/media/formats/mp4/aac.h +++ b/media/formats/mp4/aac.h
@@ -56,7 +56,7 @@ // signalled in the mimetype. (ie mp4a.40.5 in the codecs parameter). // Returns the channel_layout value that should used in an // AudioDecoderConfig. - ChannelLayout GetChannelLayout(bool sbr_in_mimetype) const; + ChannelLayoutConfig GetChannelLayout(bool sbr_in_mimetype) const; // Converts a raw AAC frame into an AAC frame with an ADTS header. Allocates // new memory and copies the data from `buffer`, with the appropriate ADTS
diff --git a/media/formats/mp4/aac_unittest.cc b/media/formats/mp4/aac_unittest.cc index 0cde2ac68..04e7a23 100644 --- a/media/formats/mp4/aac_unittest.cc +++ b/media/formats/mp4/aac_unittest.cc
@@ -62,7 +62,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 44100); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(false), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); } @@ -72,7 +72,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 48000); EXPECT_EQ(aac_.GetOutputSamplesPerSecond(true), 48000); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(false), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); } @@ -87,12 +87,12 @@ // Test w/o implicit SBR. EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 24000); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_MONO); + EXPECT_EQ(aac_.GetChannelLayout(false), ChannelLayoutConfig::Mono()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); // Test implicit SBR. EXPECT_EQ(aac_.GetOutputSamplesPerSecond(true), 48000); - EXPECT_EQ(aac_.GetChannelLayout(true), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(true), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); } @@ -104,12 +104,12 @@ // Test w/o implicit SBR. EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 24000); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(false), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); // Test implicit SBR. EXPECT_EQ(aac_.GetOutputSamplesPerSecond(true), 48000); - EXPECT_EQ(aac_.GetChannelLayout(true), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(true), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); } @@ -118,7 +118,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 48000); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_5_1_BACK); + EXPECT_EQ(aac_.GetChannelLayout(false), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_5_1_BACK>()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kUnknown); } @@ -220,7 +221,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(aac_.GetOutputSamplesPerSecond(false), 48000); - EXPECT_EQ(aac_.GetChannelLayout(false), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(aac_.GetChannelLayout(false), ChannelLayoutConfig::Stereo()); EXPECT_EQ(aac_.GetProfile(), AudioCodecProfile::kXHE_AAC); // ADTS conversion should do nothing since xHE-AAC can't be represented with
diff --git a/media/formats/mp4/ac3.cc b/media/formats/mp4/ac3.cc index 83eb68c..5b7745b6 100644 --- a/media/formats/mp4/ac3.cc +++ b/media/formats/mp4/ac3.cc
@@ -72,8 +72,8 @@ return channel_count_; } -ChannelLayout AC3::GetChannelLayout() const { - return channel_layout_; +ChannelLayoutConfig AC3::GetChannelLayout() const { + return ChannelLayoutConfig::FromLayout(channel_layout_); } } // namespace mp4
diff --git a/media/formats/mp4/ac3.h b/media/formats/mp4/ac3.h index d55a3e1..d96313b4 100644 --- a/media/formats/mp4/ac3.h +++ b/media/formats/mp4/ac3.h
@@ -36,7 +36,7 @@ bool Parse(const std::vector<uint8_t>& data, MediaLog* media_log); uint32_t GetChannelCount() const; - ChannelLayout GetChannelLayout() const; + ChannelLayoutConfig GetChannelLayout() const; private: // The channel count stored in the compressed audio stream.
diff --git a/media/formats/mp4/ac3_unittest.cc b/media/formats/mp4/ac3_unittest.cc index 08711267..1ca420f 100644 --- a/media/formats/mp4/ac3_unittest.cc +++ b/media/formats/mp4/ac3_unittest.cc
@@ -54,7 +54,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 1u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_MONO); + EXPECT_EQ(ac3_.GetChannelLayout(), ChannelLayoutConfig::Mono()); } TEST_F(AC3Test, ChannelLayout_Stereo_Test) { @@ -62,7 +62,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 2u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(ac3_.GetChannelLayout(), ChannelLayoutConfig::Stereo()); } TEST_F(AC3Test, ChannelLayout_Surround_Test) { @@ -70,7 +70,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 3u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_SURROUND); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_SURROUND>()); } TEST_F(AC3Test, ChannelLayout_2Point1_Test) { @@ -78,7 +79,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 3u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_2POINT1); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_2POINT1>()); } TEST_F(AC3Test, ChannelLayout_2_2_Test) { @@ -86,7 +88,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 4u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_2_2); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_2_2>()); } TEST_F(AC3Test, ChannelLayout_4_0_Test) { @@ -94,7 +97,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 4u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_4_0); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_4_0>()); } TEST_F(AC3Test, ChannelLayout_5_0_Test) { @@ -102,7 +106,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 5u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_5_0); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_5_0>()); } TEST_F(AC3Test, ChannelLayout_5_1_Test) { @@ -110,7 +115,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(ac3_.GetChannelCount(), 6u); - EXPECT_EQ(ac3_.GetChannelLayout(), CHANNEL_LAYOUT_5_1); + EXPECT_EQ(ac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_5_1>()); } } // namespace mp4
diff --git a/media/formats/mp4/eac3.cc b/media/formats/mp4/eac3.cc index d08222b33..a047d781 100644 --- a/media/formats/mp4/eac3.cc +++ b/media/formats/mp4/eac3.cc
@@ -146,8 +146,8 @@ return channel_count_; } -ChannelLayout EAC3::GetChannelLayout() const { - return channel_layout_; +ChannelLayoutConfig EAC3::GetChannelLayout() const { + return ChannelLayoutConfig::FromLayout(channel_layout_); } } // namespace mp4
diff --git a/media/formats/mp4/eac3.h b/media/formats/mp4/eac3.h index 1902cec7..e62afdc1 100644 --- a/media/formats/mp4/eac3.h +++ b/media/formats/mp4/eac3.h
@@ -36,7 +36,7 @@ bool Parse(const std::vector<uint8_t>& data, MediaLog* media_log); uint32_t GetChannelCount() const; - ChannelLayout GetChannelLayout() const; + ChannelLayoutConfig GetChannelLayout() const; private: // The channel count stored in the compressed audio stream.
diff --git a/media/formats/mp4/eac3_unittest.cc b/media/formats/mp4/eac3_unittest.cc index 245082a..38c56088 100644 --- a/media/formats/mp4/eac3_unittest.cc +++ b/media/formats/mp4/eac3_unittest.cc
@@ -53,7 +53,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 1u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_MONO); + EXPECT_EQ(eac3_.GetChannelLayout(), ChannelLayoutConfig::Mono()); } TEST_F(EAC3Test, ChannelLayout_Stereo_Test) { @@ -61,7 +61,7 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 2u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_STEREO); + EXPECT_EQ(eac3_.GetChannelLayout(), ChannelLayoutConfig::Stereo()); } TEST_F(EAC3Test, ChannelLayout_Surround_Test) { @@ -69,7 +69,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 3u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_SURROUND); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_SURROUND>()); } TEST_F(EAC3Test, ChannelLayout_2Point1_Test) { @@ -77,7 +78,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 3u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_2POINT1); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_2POINT1>()); } TEST_F(EAC3Test, ChannelLayout_2_2_Test) { @@ -85,7 +87,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 4u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_2_2); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_2_2>()); } TEST_F(EAC3Test, ChannelLayout_4_0_Test) { @@ -93,7 +96,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 4u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_4_0); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_4_0>()); } TEST_F(EAC3Test, ChannelLayout_5_0_Test) { @@ -101,7 +105,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 5u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_5_0); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_5_0>()); } TEST_F(EAC3Test, ChannelLayout_5_1_Test) { @@ -109,7 +114,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 6u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_5_1); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_5_1>()); } TEST_F(EAC3Test, ChannelLayout_7_1_Test) { @@ -117,7 +123,8 @@ EXPECT_TRUE(Parse(data)); EXPECT_EQ(eac3_.GetChannelCount(), 8u); - EXPECT_EQ(eac3_.GetChannelLayout(), CHANNEL_LAYOUT_7_1); + EXPECT_EQ(eac3_.GetChannelLayout(), + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_7_1>()); } } // namespace mp4
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc index 22ab12d..ad366f6 100644 --- a/media/formats/mp4/mp4_stream_parser.cc +++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -489,7 +489,7 @@ } AudioCodec codec = AudioCodec::kUnknown; - ChannelLayout channel_layout = CHANNEL_LAYOUT_NONE; + ChannelLayoutConfig channel_layout_config; int sample_per_second = 0; int codec_delay_in_frames = 0; base::TimeDelta seek_preroll; @@ -502,7 +502,8 @@ if (audio_format == FOURCC_OPUS) { codec = AudioCodec::kOpus; - channel_layout = GuessChannelLayout(entry.dops.channel_count); + channel_layout_config = + ChannelLayoutConfig::Guess(entry.dops.channel_count); sample_per_second = entry.dops.sample_rate; codec_delay_in_frames = entry.dops.codec_delay_in_frames; seek_preroll = entry.dops.seek_preroll; @@ -518,7 +519,7 @@ } codec = AudioCodec::kFLAC; - channel_layout = GuessChannelLayout(entry.channelcount); + channel_layout_config = ChannelLayoutConfig::Guess(entry.channelcount); sample_per_second = entry.samplerate; extra_data = entry.dfla.stream_info; #if BUILDFLAG(ENABLE_PLATFORM_IAMF_AUDIO) @@ -543,14 +544,15 @@ // AudioDecoderConfig. // TODO (crbug.com/1513779): Parse the bitstream to set the correct // values here. - channel_layout = CHANNEL_LAYOUT_STEREO; + channel_layout_config = ChannelLayoutConfig::Stereo(); sample_per_second = 48000; #endif // BUILDFLAG(ENABLE_PLATFORM_IAMF_AUDIO) #if BUILDFLAG(USE_PROPRIETARY_CODECS) #if BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO) } else if (audio_format == FOURCC_MHM1 || audio_format == FOURCC_MHA1) { codec = AudioCodec::kMpegHAudio; - channel_layout = CHANNEL_LAYOUT_BITSTREAM; + channel_layout_config = + ChannelLayoutConfig::FromLayout<CHANNEL_LAYOUT_BITSTREAM>(); sample_per_second = entry.samplerate; extra_data = entry.dfla.stream_info; #endif @@ -599,28 +601,32 @@ const AAC& aac = entry.esds.aac; codec = AudioCodec::kAAC; profile = aac.GetProfile(); - channel_layout = aac.GetChannelLayout(has_sbr_); + channel_layout_config = aac.GetChannelLayout(has_sbr_); sample_per_second = aac.GetOutputSamplesPerSecond(has_sbr_); extra_data = aac.codec_specific_data(); #if BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO) } else if (audio_type == kAC3) { codec = AudioCodec::kAC3; - channel_layout = entry.ac3.dac3.GetChannelLayout(); + channel_layout_config = entry.ac3.dac3.GetChannelLayout(); // Add an exception to allow using AudioSampleEntry's ChannelCount // temporarily when AC3SpecificBox('dac3') information is // not available. - if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) { - channel_layout = GuessChannelLayout(entry.channelcount); + if (channel_layout_config.channel_layout() == + CHANNEL_LAYOUT_UNSUPPORTED) { + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); } sample_per_second = entry.samplerate; } else if (audio_type == kEAC3) { codec = AudioCodec::kEAC3; - channel_layout = entry.eac3.dec3.GetChannelLayout(); + channel_layout_config = entry.eac3.dec3.GetChannelLayout(); // Add an exception to allow using AudioSampleEntry's ChannelCount // temporarily when EC3SpecificBox('dec3') information is // not available. - if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) { - channel_layout = GuessChannelLayout(entry.channelcount); + if (channel_layout_config.channel_layout() == + CHANNEL_LAYOUT_UNSUPPORTED) { + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); } sample_per_second = entry.samplerate; #endif @@ -630,24 +636,28 @@ // channel_layout and sample rate will be ignored on decoding. // Refer to E.4.1 AC4SampleEntry Box in // ETSI TS 103 190 - 2 V1 .2.1(2018 - 02) - channel_layout = GuessChannelLayout(entry.channelcount); + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); sample_per_second = entry.samplerate; extra_data = entry.ac4.dac4.StreamInfo(); #endif // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO) #if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO) } else if (audio_type == kDTS) { codec = AudioCodec::kDTS; - channel_layout = GuessChannelLayout(entry.channelcount); + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); sample_per_second = entry.samplerate; } else if (audio_type == kDTSX) { // HDMI versions pre HDMI 2.0 can only transmit 8 raw PCM channels. // In the case of a 5_1_4 stream we downmix to 5_1. codec = AudioCodec::kDTSXP2; - channel_layout = GuessChannelLayout(entry.channelcount); + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); sample_per_second = entry.samplerate; } else if (audio_type == kDTSE) { codec = AudioCodec::kDTSE; - channel_layout = GuessChannelLayout(entry.channelcount); + channel_layout_config = + ChannelLayoutConfig::Guess(entry.channelcount); sample_per_second = entry.samplerate; #endif } else { @@ -688,7 +698,7 @@ return false; } - audio_config.Initialize(codec, sample_format, channel_layout, + audio_config.Initialize(codec, sample_format, channel_layout_config, sample_per_second, extra_data, scheme, seek_preroll, codec_delay_in_frames);
diff --git a/media/mojo/mojom/video_frame_mojom_traits.cc b/media/mojo/mojom/video_frame_mojom_traits.cc index e97b860..1805cd41 100644 --- a/media/mojo/mojom/video_frame_mojom_traits.cc +++ b/media/mojo/mojom/video_frame_mojom_traits.cc
@@ -282,8 +282,11 @@ } const size_t num_planes = offsets.size(); - if (num_planes == 0 || num_planes > 3) { - DLOG(ERROR) << "Invalid number of planes: " << num_planes; + if (num_planes != strides.size() || + num_planes != media::VideoFrame::NumPlanes(format)) { + DLOG(ERROR) << "Invalid number of planes: offsets=" << num_planes + << ", strides=" << strides.size() + << ", format=" << VideoPixelFormatToString(format); return false; }
diff --git a/media/muxers/mp4_muxer_box_writer_unittest.cc b/media/muxers/mp4_muxer_box_writer_unittest.cc index b974c38..7595752 100644 --- a/media/muxers/mp4_muxer_box_writer_unittest.cc +++ b/media/muxers/mp4_muxer_box_writer_unittest.cc
@@ -786,8 +786,8 @@ int aac_frequency = aac.GetOutputSamplesPerSecond(false); EXPECT_EQ(kSampleFrequency, base::checked_cast<size_t>(aac_frequency)); - ChannelLayout channel_layout = aac.GetChannelLayout(false); - EXPECT_EQ(media::CHANNEL_LAYOUT_STEREO, channel_layout); + ChannelLayoutConfig channel_layout_config = aac.GetChannelLayout(false); + EXPECT_EQ(ChannelLayoutConfig::Stereo(), channel_layout_config); size_t adts_header_size; auto buffer = aac.CreateAdtsFromEsds({}, &adts_header_size);
diff --git a/net/cert/cert_status_flags_list.h b/net/cert/cert_status_flags_list.h index bad9f781..c82593d8 100644 --- a/net/cert/cert_status_flags_list.h +++ b/net/cert/cert_status_flags_list.h
@@ -23,6 +23,8 @@ CERT_STATUS_FLAG(UNABLE_TO_CHECK_REVOCATION, 1 << 5) CERT_STATUS_FLAG(REVOKED, 1 << 6) CERT_STATUS_FLAG(INVALID, 1 << 7) +// TODO(crbug.com/487349971): should CERT_STATUS_WEAK_SIGNATURE_ALGORITHM be +// deprecated? It's mostly unused now, but maybe we'll want to use it again? CERT_STATUS_FLAG(WEAK_SIGNATURE_ALGORITHM, 1 << 8) // 1 << 9 was used for CERT_STATUS_NOT_IN_DNS CERT_STATUS_FLAG(NON_UNIQUE_NAME, 1 << 10) @@ -36,6 +38,14 @@ CERT_STATUS_FLAG(IS_EV, 1 << 16) CERT_STATUS_FLAG(REV_CHECKING_ENABLED, 1 << 17) // Bit 18 was CERT_STATUS_IS_DNSSEC +// TODO(crbug.com/487349971): deprecate CERT_STATUS_SHA1_SIGNATURE_PRESENT. +// This was a non-error status to indicate the cases when a verification +// with a SHA-1 certificate could succeed when +// VERIFY_ENABLE_SHA1_LOCAL_ANCHORS was set. It no longer has a purpose now +// that SHA-1 is never allowed, and this status is no longer set, but still has +// references in UI layer code. (Or we could re-purpose this bit for a more +// general "weak signature present" non-error status if we need that in the +// future.) CERT_STATUS_FLAG(SHA1_SIGNATURE_PRESENT, 1 << 19) // Bit 20 was CERT_STATUS_CT_COMPLIANCE_FAILED CERT_STATUS_FLAG(KNOWN_INTERCEPTION_DETECTED, 1 << 21)
diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc index 29fb1c2..d12efbc 100644 --- a/net/cert/cert_verify_proc.cc +++ b/net/cert/cert_verify_proc.cc
@@ -261,9 +261,8 @@ // Inspects the signature algorithms in a single certificate |cert|. // -// * Sets |verify_result->has_sha1| to true if the certificate uses SHA1. -// -// Returns false if the signature algorithm was unknown or mismatched. +// Returns false if the signature algorithm was unknown, mismatched, or +// not allowed. [[nodiscard]] bool InspectSignatureAlgorithmForCert( const CRYPTO_BUFFER* cert, CertVerifyResult* verify_result) { @@ -285,10 +284,15 @@ return false; } - verify_result->has_sha1 = - verify_result->has_sha1 || - *cert_algorithm == bssl::SignatureAlgorithm::kRsaPkcs1Sha1 || - *cert_algorithm == bssl::SignatureAlgorithm::kEcdsaSha1; + if (*cert_algorithm == bssl::SignatureAlgorithm::kRsaPkcs1Sha1 || + *cert_algorithm == bssl::SignatureAlgorithm::kEcdsaSha1) { + // The underlying verifier has likely already failed due to the SHA-1 + // signature, double-checking here is mostly unnecessary. (The only case + // this is check is expected to be load-bearing is when cronet is running + // on an old Android (before Android 10) that allows SHA-1 signatures.) + return false; + } + return true; } @@ -324,8 +328,6 @@ return true; } - DCHECK(!verify_result->has_sha1); - // Fill in hash algorithms for the certificates, excluding the // final one (which is presumably the trust anchor; may be incorrect for // partial chains). @@ -525,33 +527,6 @@ rv = MapCertStatusToNetError(verify_result->cert_status); } - // Flag certificates using weak signature algorithms. - if (verify_result->has_sha1) { - // TODO(crbug.com/487349971): remove CERT_STATUS_SHA1_SIGNATURE_PRESENT. - // This is a non-error status to indicate the cases when a verification - // with a SHA-1 certificate could succeed when - // VERIFY_ENABLE_SHA1_LOCAL_ANCHORS was set. It no longer has a purpose - // now that SHA-1 is never allowed. - // TODO(crbug.com/487349971): remove the CertVerifyResult has_sha1 field - // and the CERT_STATUS_WEAK_SIGNATURE_ALGORITHM status. has_sha1 is only - // used to smuggle some state from InspectSignatureAlgorithmForCert up to - // here so that we can set a different status. We could just make - // InspectSignatureAlgorithmForCert fail on a SHA-1 sigalg instead of - // having a dedicated cert status code / error code for weak signature. In - // general, the underlying verifier has likely already failed due to the - // SHA-1 signature with a CERT_STATUS_INVALID error, so all we are doing - // here is adding an additional, lower priority error status. (The only - // case this is check should be load-bearing is when cronet is running on - // an ancient android version that wouldn't fail on SHA-1 itself.) - verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM | - CERT_STATUS_SHA1_SIGNATURE_PRESENT; - // Avoid replacing a more serious error, such as an OS/library failure, - // by ensuring that if verification failed, it failed with a certificate - // error. - if (rv == OK || IsCertificateError(rv)) - rv = MapCertStatusToNetError(verify_result->cert_status); - } - // Flag certificates using too long validity periods. if (verify_result->is_issued_by_known_root && HasTooLongValidity(*cert)) { verify_result->cert_status |= CERT_STATUS_VALIDITY_TOO_LONG;
diff --git a/net/cert/cert_verify_proc.h b/net/cert/cert_verify_proc.h index bf3b6708..23000c3c 100644 --- a/net/cert/cert_verify_proc.h +++ b/net/cert/cert_verify_proc.h
@@ -304,7 +304,6 @@ // Implementations are expected to fill in all applicable fields, excluding: // // * ocsp_result - // * has_sha1 // // which will be filled in by |Verify()|. If an error code is returned, // |verify_result->cert_status| should be non-zero, indicating an
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc index 0d417cf..f1c59e68 100644 --- a/net/cert/cert_verify_proc_unittest.cc +++ b/net/cert/cert_verify_proc_unittest.cc
@@ -1060,56 +1060,70 @@ // This fixture is for testing the verification of a certificate chain which // has some sort of mismatched signature algorithm (i.e. -// Certificate.signatureAlgorithm and TBSCertificate.algorithm are different). +// Certificate.signatureAlgorithm and TBSCertificate.algorithm are different), +// or unsupported signature algorithms. class CertVerifyProcInspectSignatureAlgorithmsTest : public ::testing::Test { protected: - // In the test setup, SHA384 is given special treatment as an unknown - // algorithm. - static constexpr bssl::DigestAlgorithm kUnknownDigestAlgorithm = - bssl::DigestAlgorithm::Sha384; + static constexpr uint8_t kMd2WithRSAEncryption[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x02, 0x05, 0x00}; + static constexpr uint8_t kMd4WithRSAEncryption[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x03, 0x05, 0x00}; + static constexpr uint8_t kMd5WithRSAEncryption[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x04, 0x05, 0x00}; + static constexpr uint8_t kSha1WithRSAEncryption[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00}; + static constexpr uint8_t kSha256WithRSAEncryption[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; + static constexpr uint8_t kEcdsaWithSha256[] = { + 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02}; + static constexpr uint8_t kUnknownDigestAlgorithm[] = { + 0x30, 0x0d, 0x06, 0x09, 0x8a, 0x87, 0x18, 0x46, + 0xd7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; struct CertParams { // Certificate.signatureAlgorithm - bssl::DigestAlgorithm cert_algorithm; + base::raw_span<const uint8_t> cert_algorithm; // TBSCertificate.algorithm - bssl::DigestAlgorithm tbs_algorithm; + base::raw_span<const uint8_t> tbs_algorithm; }; // Shorthand for VerifyChain() where only the leaf's parameters need // to be specified. [[nodiscard]] int VerifyLeaf(const CertParams& leaf_params) { - return VerifyChain( - {// Target - leaf_params, - // Intermediate - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}, - // Root - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}}); + return VerifyChain({// Target + leaf_params, + // Intermediate + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}, + // Root + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}}); } // Shorthand for VerifyChain() where only the intermediate's parameters need // to be specified. [[nodiscard]] int VerifyIntermediate(const CertParams& intermediate_params) { - return VerifyChain( - {// Target - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}, - // Intermediate - intermediate_params, - // Root - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}}); + return VerifyChain({// Target + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}, + // Intermediate + intermediate_params, + // Root + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}}); } // Shorthand for VerifyChain() where only the root's parameters need to be // specified. [[nodiscard]] int VerifyRoot(const CertParams& root_params) { - return VerifyChain( - {// Target - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}, - // Intermediate - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}, - // Root - root_params}); + return VerifyChain({// Target + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}, + // Intermediate + {kSha256WithRSAEncryption, kSha256WithRSAEncryption}, + // Root + root_params}); } // Manufactures a certificate chain where each certificate has the indicated @@ -1121,10 +1135,10 @@ std::vector<std::unique_ptr<CertBuilder>> builders = CertBuilder::CreateSimpleChain(chain_params.size()); for (size_t i = 0; i < chain_params.size(); i++) { - builders[i]->SetOuterSignatureAlgorithmTLV(base::as_string_view( - GetAlgorithmSequence(chain_params[i].cert_algorithm))); - builders[i]->SetTBSSignatureAlgorithmTLV(base::as_string_view( - GetAlgorithmSequence(chain_params[i].tbs_algorithm))); + builders[i]->SetOuterSignatureAlgorithmTLV( + base::as_string_view(chain_params[i].cert_algorithm)); + builders[i]->SetTBSSignatureAlgorithmTLV( + base::as_string_view(chain_params[i].tbs_algorithm)); } scoped_refptr<X509Certificate> chain = @@ -1144,135 +1158,125 @@ chain.get(), "www.example.com", /*ocsp_response=*/std::string(), /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource()); } - - private: - static base::span<const uint8_t> GetAlgorithmSequence( - bssl::DigestAlgorithm algorithm) { - switch (algorithm) { - case bssl::DigestAlgorithm::Sha1: - static const uint8_t kSha1WithRSAEncryption[] = { - 0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00}; - return kSha1WithRSAEncryption; - case bssl::DigestAlgorithm::Sha256: - static const uint8_t kSha256WithRSAEncryption[] = { - 0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; - return kSha256WithRSAEncryption; - case kUnknownDigestAlgorithm: - static const uint8_t kUnknownAlgorithm[] = { - 0x30, 0x0D, 0x06, 0x09, 0x8a, 0x87, 0x18, 0x46, - 0xd7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; - return kUnknownAlgorithm; - default: - NOTREACHED() << "Unsupported digest algorithm"; - } - } }; -// This is a control test to make sure that the test helper -// VerifyLeaf() works as expected. There is no actual mismatch in the -// algorithms used here. -// -// Also functions as a regression test for crbug.com/481168373, confirming that -// the SHA-1 status of the leaf is calculated properly when issued by a -// non-SHA-1 intermediate. -// -// Certificate.signatureAlgorithm: sha1WithRSASignature -// TBSCertificate.algorithm: sha1WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha1) { - int rv = - VerifyLeaf({bssl::DigestAlgorithm::Sha1, bssl::DigestAlgorithm::Sha1}); - ASSERT_THAT(rv, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); +// A leaf with matching, supported algorithms is allowed. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSupportedAlgorithms) { + EXPECT_THAT(VerifyLeaf({kSha256WithRSAEncryption, kSha256WithRSAEncryption}), + IsOk()); + EXPECT_THAT(VerifyLeaf({kEcdsaWithSha256, kEcdsaWithSha256}), IsOk()); } -// This is a control test to make sure that the test helper -// VerifyLeaf() works as expected. There is no actual mismatch in the -// algorithms used here. -// -// Certificate.signatureAlgorithm: sha256WithRSASignature -// TBSCertificate.algorithm: sha256WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha256) { - int rv = VerifyLeaf( - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha256}); - ASSERT_THAT(rv, IsOk()); +// A leaf with matching, but unsupported algorithms is an error. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + LeafUnsupportedAlgorithms) { + EXPECT_THAT(VerifyLeaf({kSha1WithRSAEncryption, kSha1WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kMd5WithRSAEncryption, kMd5WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kMd4WithRSAEncryption, kMd4WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kMd2WithRSAEncryption, kMd2WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kUnknownDigestAlgorithm, kUnknownDigestAlgorithm}), + IsError(ERR_CERT_INVALID)); } -// Mismatched signature algorithms in the leaf certificate. -// -// Certificate.signatureAlgorithm: sha1WithRSASignature -// TBSCertificate.algorithm: sha256WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha256) { - int rv = - VerifyLeaf({bssl::DigestAlgorithm::Sha1, bssl::DigestAlgorithm::Sha256}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// Mismatched signature algorithms in the leaf certificate should fail even if +// both algorithms are supported. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + LeafMismatchBetweenSupportedAlgorithms) { + EXPECT_THAT(VerifyLeaf({kEcdsaWithSha256, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kSha256WithRSAEncryption, kEcdsaWithSha256}), + IsError(ERR_CERT_INVALID)); } -// Mismatched signature algorithms in the leaf certificate. -// -// Certificate.signatureAlgorithm: sha256WithRSAEncryption -// TBSCertificate.algorithm: sha1WithRSASignature -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha1) { - int rv = - VerifyLeaf({bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha1}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// Mismatched signature algorithms in the leaf certificate should fail if +// one algorithm is unsupported or unknown. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + LeafMismatchWithUnsupportedAlgorithm) { + EXPECT_THAT(VerifyLeaf({kSha1WithRSAEncryption, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kSha256WithRSAEncryption, kSha1WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + + EXPECT_THAT(VerifyLeaf({kUnknownDigestAlgorithm, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyLeaf({kSha256WithRSAEncryption, kUnknownDigestAlgorithm}), + IsError(ERR_CERT_INVALID)); } -// Unrecognized signature algorithm in the leaf certificate. -// -// Certificate.signatureAlgorithm: sha256WithRSAEncryption -// TBSCertificate.algorithm: ? -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Unknown) { - int rv = VerifyLeaf({bssl::DigestAlgorithm::Sha256, kUnknownDigestAlgorithm}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// An intermediate with matching, supported algorithms is allowed. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + IntermediateSupportedAlgorithms) { + EXPECT_THAT( + VerifyIntermediate({kSha256WithRSAEncryption, kSha256WithRSAEncryption}), + IsOk()); + EXPECT_THAT(VerifyIntermediate({kEcdsaWithSha256, kEcdsaWithSha256}), IsOk()); } -// Unrecognized signature algorithm in the leaf certificate. -// -// Certificate.signatureAlgorithm: ? -// TBSCertificate.algorithm: sha256WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafUnknownSha256) { - int rv = VerifyLeaf({kUnknownDigestAlgorithm, bssl::DigestAlgorithm::Sha256}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// An intermediate with matching, but unsupported algorithms is an error. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + IntermediateUnsupportedAlgorithms) { + EXPECT_THAT( + VerifyIntermediate({kSha1WithRSAEncryption, kSha1WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kMd5WithRSAEncryption, kMd5WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kMd4WithRSAEncryption, kMd4WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kMd2WithRSAEncryption, kMd2WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kUnknownDigestAlgorithm, kUnknownDigestAlgorithm}), + IsError(ERR_CERT_INVALID)); } -// Mismatched signature algorithms in the intermediate certificate. -// -// Certificate.signatureAlgorithm: sha1WithRSASignature -// TBSCertificate.algorithm: sha256WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha1Sha256) { - int rv = VerifyIntermediate( - {bssl::DigestAlgorithm::Sha1, bssl::DigestAlgorithm::Sha256}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// Mismatched signature algorithms in the intermediate certificate should fail +// even if both algorithms are supported. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + IntermediateMismatchBetweenSupportedAlgorithms) { + EXPECT_THAT(VerifyIntermediate({kEcdsaWithSha256, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT(VerifyIntermediate({kSha256WithRSAEncryption, kEcdsaWithSha256}), + IsError(ERR_CERT_INVALID)); } -// Mismatched signature algorithms in the intermediate certificate. -// -// Certificate.signatureAlgorithm: sha256WithRSAEncryption -// TBSCertificate.algorithm: sha1WithRSASignature -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha256Sha1) { - int rv = VerifyIntermediate( - {bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha1}); - ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); +// Mismatched signature algorithms in the intermediate certificate should fail +// if one algorithm is unsupported or unknown. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, + IntermediateMismatchWithUnsupportedAlgorithm) { + EXPECT_THAT( + VerifyIntermediate({kSha1WithRSAEncryption, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kSha256WithRSAEncryption, kSha1WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + + EXPECT_THAT( + VerifyIntermediate({kUnknownDigestAlgorithm, kSha256WithRSAEncryption}), + IsError(ERR_CERT_INVALID)); + EXPECT_THAT( + VerifyIntermediate({kSha256WithRSAEncryption, kUnknownDigestAlgorithm}), + IsError(ERR_CERT_INVALID)); } -// Mismatched signature algorithms in the root certificate. -// -// Certificate.signatureAlgorithm: sha256WithRSAEncryption -// TBSCertificate.algorithm: sha1WithRSASignature -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootSha256Sha1) { - int rv = - VerifyRoot({bssl::DigestAlgorithm::Sha256, bssl::DigestAlgorithm::Sha1}); - ASSERT_THAT(rv, IsOk()); +// Mismatched signature algorithms in the root certificate are ignored. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootMismatch) { + EXPECT_THAT(VerifyRoot({kSha256WithRSAEncryption, kEcdsaWithSha256}), IsOk()); } -// Unrecognized signature algorithm in the root certificate. -// -// Certificate.signatureAlgorithm: ? -// TBSCertificate.algorithm: sha256WithRSAEncryption -TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootUnknownSha256) { - int rv = VerifyRoot({kUnknownDigestAlgorithm, bssl::DigestAlgorithm::Sha256}); - ASSERT_THAT(rv, IsOk()); +// Unrecognized or weak signature algorithms in the root certificate are +// ignored. +TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootUnknownAlgorithm) { + EXPECT_THAT(VerifyRoot({kUnknownDigestAlgorithm, kUnknownDigestAlgorithm}), + IsOk()); + EXPECT_THAT(VerifyRoot({kSha1WithRSAEncryption, kSha1WithRSAEncryption}), + IsOk()); } TEST(CertVerifyProcTest, TestHasTooLongValidity) { @@ -3010,7 +3014,6 @@ intermediate_sha256.get())); ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size()); - EXPECT_FALSE(verify_result.has_sha1); EXPECT_THAT(error, IsOk()); } else { EXPECT_NE(OK, error); @@ -3024,10 +3027,6 @@ // statuses. See https://crbug.com/1191795. return; } - EXPECT_TRUE(verify_result.cert_status & - CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); - EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); - EXPECT_TRUE(verify_result.has_sha1); } } @@ -5335,12 +5334,11 @@ } TEST(CertVerifyProcTest, RejectsPublicSHA1) { - scoped_refptr<X509Certificate> cert( - ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); - ASSERT_TRUE(cert); + auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); + leaf->SetSignatureAlgorithm(bssl::SignatureAlgorithm::kEcdsaSha1); + scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain(); CertVerifyResult result; - result.has_sha1 = true; result.is_issued_by_known_root = true; auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); @@ -5349,17 +5347,15 @@ int error = verify_proc->Verify( cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource()); - EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); - EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); + EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); } TEST(CertVerifyProcTest, RejectsPrivateSHA1) { - scoped_refptr<X509Certificate> cert( - ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); - ASSERT_TRUE(cert); + auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); + leaf->SetSignatureAlgorithm(bssl::SignatureAlgorithm::kEcdsaSha1); + scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain(); CertVerifyResult result; - result.has_sha1 = true; result.is_issued_by_known_root = false; auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); @@ -5368,8 +5364,7 @@ int error = verify_proc->Verify( cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), /*sct_list=*/std::string(), flags, &verify_result, NetLogWithSource()); - EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); - EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); + EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); } // Tests that a certificate chain with a SHA-1 leaf is rejected when verified @@ -5385,17 +5380,7 @@ CertVerifyResult verify_result; int flags = 0; int error = Verify(cert.get(), "www.example.com", flags, &verify_result); - if (VerifyProcTypeIsAndroidQOrLater()) { - EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); - } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { - // Old versions of android that don't reject SHA-1, where it is enforced in - // the CertVerifyProc wrapper. Only cronet supports android versions this - // old. - EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); - } else { - EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); - } - EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); + EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); } // Tests that a certificate chain with a SHA-1 intermediate is rejected when @@ -5412,217 +5397,36 @@ int flags = 0; int error = Verify(cert.get(), "www.example.com", flags, &verify_result); if (VerifyProcTypeIsAndroidQOrLater()) { - EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); // On Android >= version 10, the wrapped verifier fails at the // intermediate, so the returned partial chain ends there. The // InspectSignatureAlgorithmsInChain function does not check the last cert // in the returned chain since it doesn't want to check the algorithm on // the root certificate. Thus the result does not get the - // CERT_STATUS_WEAK_SIGNATURE_ALGORITHM status set. - } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { - // Old versions of android that don't reject SHA-1, where it is enforced in - // the CertVerifyProc wrapper. Only cronet supports android versions this - // old. - EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); - EXPECT_TRUE(verify_result.cert_status & - CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); + // CERT_STATUS_INVALID status set, and CertVerifyProcAndroid has marked it + // as AUTHORITY_INVALID. + EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); } else { EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); - EXPECT_TRUE(verify_result.cert_status & - CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); } } -enum ExpectedAlgorithms { - EXPECT_SHA1 = 1 << 0, - EXPECT_STATUS_INVALID = 1 << 1, -}; +// Tests that a certificate chain with a SHA-1 root is accepted when verified +// by the real verification implementation. (The self-signature on the trust +// anchor doesn't matter, so we don't care if it used a weak algorithm.) +TEST_P(CertVerifyProcInternalTest, Sha1Root) { + auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); -struct WeakDigestTestData { - const char* root_cert_filename; - const char* intermediate_cert_filename; - const char* ee_cert_filename; - int expected_algorithms; -}; + root->SetSignatureAlgorithm(bssl::SignatureAlgorithm::kEcdsaSha1); -const char* StringOrDefault(const char* str, const char* default_value) { - if (!str) - return default_value; - return str; -} + scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain(); + ScopedTestRoot scoped_test_root(root->GetX509Certificate()); -// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how -// to output the parameter that was passed. Without this, it will simply -// attempt to print out the first twenty bytes of the object, which depending -// on platform and alignment, may result in an invalid read. -void PrintTo(const WeakDigestTestData& data, std::ostream* os) { - *os << "root: " << StringOrDefault(data.root_cert_filename, "none") - << "; intermediate: " - << StringOrDefault(data.intermediate_cert_filename, "none") - << "; end-entity: " << data.ee_cert_filename; -} - -class CertVerifyProcWeakDigestTest - : public testing::TestWithParam<WeakDigestTestData> { - public: - CertVerifyProcWeakDigestTest() = default; - ~CertVerifyProcWeakDigestTest() override = default; -}; - -// Tests that the CertVerifyProc::Verify() properly surfaces the (weak) hash -// algorithms used in the chain. -TEST_P(CertVerifyProcWeakDigestTest, VerifyDetectsAlgorithm) { - WeakDigestTestData data = GetParam(); - base::FilePath certs_dir = GetTestCertsDirectory(); - - // Build |intermediates| as the full chain (including trust anchor). - std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; - - if (data.intermediate_cert_filename) { - scoped_refptr<X509Certificate> intermediate_cert = - ImportCertFromFile(certs_dir, data.intermediate_cert_filename); - ASSERT_TRUE(intermediate_cert); - intermediates.push_back(bssl::UpRef(intermediate_cert->cert_buffer())); - } - - if (data.root_cert_filename) { - scoped_refptr<X509Certificate> root_cert = - ImportCertFromFile(certs_dir, data.root_cert_filename); - ASSERT_TRUE(root_cert); - intermediates.push_back(bssl::UpRef(root_cert->cert_buffer())); - } - - scoped_refptr<X509Certificate> ee_cert = - ImportCertFromFile(certs_dir, data.ee_cert_filename); - ASSERT_TRUE(ee_cert); - - scoped_refptr<X509Certificate> ee_chain = X509Certificate::CreateFromBuffer( - bssl::UpRef(ee_cert->cert_buffer()), std::move(intermediates)); - ASSERT_TRUE(ee_chain); - - int flags = 0; CertVerifyResult verify_result; - - // Use a mock CertVerifyProc that returns success with a verified_cert of - // |ee_chain|. - // - // This is sufficient for the purposes of this test, as the checking for weak - // hash algorithms is done by CertVerifyProc::Verify(). - auto proc = base::MakeRefCounted<MockCertVerifyProc>(CertVerifyResult()); - int error = proc->Verify(ee_chain.get(), "127.0.0.1", - /*ocsp_response=*/std::string(), - /*sct_list=*/std::string(), flags, &verify_result, - NetLogWithSource()); - EXPECT_EQ(!!(data.expected_algorithms & EXPECT_SHA1), verify_result.has_sha1); - EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID), - !!(verify_result.cert_status & CERT_STATUS_INVALID)); - EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID), - error == ERR_CERT_INVALID); + int flags = 0; + int error = Verify(cert.get(), "www.example.com", flags, &verify_result); + EXPECT_THAT(error, IsOk()); } -// The signature algorithm of the root CA should not matter. -const WeakDigestTestData kVerifyRootCATestData[] = { - {"weak_digest_md5_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_SHA1}, - {"weak_digest_md4_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_SHA1}, - {"weak_digest_md2_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_SHA1}, -}; -INSTANTIATE_TEST_SUITE_P(VerifyRoot, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyRootCATestData)); - -// The signature algorithm of intermediates should be properly detected. -const WeakDigestTestData kVerifyIntermediateCATestData[] = { - {"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, - {"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, - {"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", - "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, -}; - -INSTANTIATE_TEST_SUITE_P(VerifyIntermediate, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyIntermediateCATestData)); - -// The signature algorithm of end-entity should be properly detected. -const WeakDigestTestData kVerifyEndEntityTestData[] = { - {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID}, - {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_md4_ee.pem", EXPECT_STATUS_INVALID}, - {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", - "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, -}; - -INSTANTIATE_TEST_SUITE_P(VerifyEndEntity, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyEndEntityTestData)); - -// Incomplete chains do not report the status of the intermediate. -// Note: really each of these tests should also expect the digest algorithm of -// the intermediate (included as a comment). However CertVerifyProc::Verify() is -// unable to distinguish that this is an intermediate and not a trust anchor, so -// this intermediate is treated like a trust anchor. -const WeakDigestTestData kVerifyIncompleteIntermediateTestData[] = { - {nullptr, "weak_digest_md5_intermediate.pem", "weak_digest_sha1_ee.pem", - EXPECT_SHA1}, - {nullptr, "weak_digest_md4_intermediate.pem", "weak_digest_sha1_ee.pem", - EXPECT_SHA1}, - {nullptr, "weak_digest_md2_intermediate.pem", "weak_digest_sha1_ee.pem", - EXPECT_SHA1}, -}; - -INSTANTIATE_TEST_SUITE_P( - MAYBE_VerifyIncompleteIntermediate, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyIncompleteIntermediateTestData)); - -// Incomplete chains should report the status of the end-entity. -// since the intermediate is treated as a trust anchor these should -// be still simply be invalid. -const WeakDigestTestData kVerifyIncompleteEETestData[] = { - {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md5_ee.pem", - EXPECT_STATUS_INVALID}, - {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md4_ee.pem", - EXPECT_STATUS_INVALID}, - {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md2_ee.pem", - EXPECT_STATUS_INVALID}, -}; - -INSTANTIATE_TEST_SUITE_P(VerifyIncompleteEndEntity, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyIncompleteEETestData)); - -// Md2, Md4, and Md5 are all considered invalid. -const WeakDigestTestData kVerifyMixedTestData[] = { - {"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", - "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, - {"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", - "weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID}, - {"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", - "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, -}; - -INSTANTIATE_TEST_SUITE_P(VerifyMixed, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyMixedTestData)); - -// The EE is a trusted certificate. Even though it uses weak hashes, these -// should not be reported. -const WeakDigestTestData kVerifyTrustedEETestData[] = { - {nullptr, nullptr, "weak_digest_md5_ee.pem", 0}, - {nullptr, nullptr, "weak_digest_md4_ee.pem", 0}, - {nullptr, nullptr, "weak_digest_md2_ee.pem", 0}, - {nullptr, nullptr, "weak_digest_sha1_ee.pem", 0}, -}; - -INSTANTIATE_TEST_SUITE_P(VerifyTrustedEE, - CertVerifyProcWeakDigestTest, - testing::ValuesIn(kVerifyTrustedEETestData)); - // Test fixture for verifying certificate names. class CertVerifyProcNameTest : public ::testing::Test { protected:
diff --git a/net/cert/cert_verify_result.cc b/net/cert/cert_verify_result.cc index 3e489fd..0b550e40 100644 --- a/net/cert/cert_verify_result.cc +++ b/net/cert/cert_verify_result.cc
@@ -28,7 +28,6 @@ void CertVerifyResult::Reset() { verified_cert = nullptr; cert_status = 0; - has_sha1 = false; is_issued_by_known_root = false; public_key_hashes.clear();
diff --git a/net/cert/cert_verify_result.h b/net/cert/cert_verify_result.h index 7d721d3..245443bc 100644 --- a/net/cert/cert_verify_result.h +++ b/net/cert/cert_verify_result.h
@@ -61,10 +61,6 @@ // chain. CertStatus cert_status; - // Hash algorithms used by the certificate chain, excluding the trust - // anchor. - bool has_sha1; - // If the certificate was successfully verified then this contains the // hashes for all of the SubjectPublicKeyInfos of the chain (target, // intermediates, and trust anchor)
diff --git a/net/data/ssl/certificates/weak_digest_md2_ee.pem b/net/data/ssl/certificates/weak_digest_md2_ee.pem deleted file mode 100644 index 6475ccc..0000000 --- a/net/data/ssl/certificates/weak_digest_md2_ee.pem +++ /dev/null
@@ -1,61 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 4 (0x4) - Signature Algorithm: md2WithRSAEncryption - Issuer: CN=Test Deprecated Digest Intermediate CA - Validity - Not Before: Oct 26 03:46:49 2011 GMT - Not After : Oct 23 03:46:49 2021 GMT - Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38: - 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9: - 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf: - 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13: - be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d: - fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee: - 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db: - 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57: - dc:d0:de:df:29:72:45:6f:e3 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD - X509v3 Authority Key Identifier: - keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Alternative Name: - IP Address:127.0.0.1 - Signature Algorithm: md2WithRSAEncryption - 87:d2:29:b3:6b:ba:36:99:ac:56:47:d8:7d:63:9e:74:a2:b5: - 42:5e:2b:96:08:f8:ab:e2:ce:ea:99:21:47:25:2c:55:f2:db: - 9d:d7:ed:d9:68:ba:09:90:b1:43:64:be:af:ef:9a:b4:10:86: - 99:85:7f:68:fe:aa:fd:d4:6a:f1:68:e9:8f:61:d8:46:21:e4: - 17:4c:89:db:82:37:36:8d:7f:93:4a:63:b1:da:ba:6b:19:ad: - 34:8b:f8:11:c3:25:14:2a:4e:7b:75:6b:03:79:c1:e5:1a:5b: - ff:b4:91:47:4f:48:91:68:33:c7:3e:a5:95:45:81:2b:0d:35: - 42:c4 ------BEGIN CERTIFICATE----- -MIICiDCCAfGgAwIBAgIBBDANBgkqhkiG9w0BAQIFADAxMS8wLQYDVQQDDCZUZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2 -NDlaFw0yMTEwMjMwMzQ2NDlaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp -Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB -MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB -AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI -6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN -DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME -GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI -KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQIFAAOBgQCH0imz -a7o2maxWR9h9Y550orVCXiuWCPir4s7qmSFHJSxV8tud1+3ZaLoJkLFDZL6v75q0 -EIaZhX9o/qr91GrxaOmPYdhGIeQXTInbgjc2jX+TSmOx2rprGa00i/gRwyUUKk57 -dWsDecHlGlv/tJFHT0iRaDPHPqWVRYErDTVCxA== ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md2_intermediate.pem b/net/data/ssl/certificates/weak_digest_md2_intermediate.pem deleted file mode 100644 index 2f2765d..0000000 --- a/net/data/ssl/certificates/weak_digest_md2_intermediate.pem +++ /dev/null
@@ -1,57 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 4 (0x4) - Signature Algorithm: md2WithRSAEncryption - Issuer: CN=Test Deprecated Digest Root CA - Validity - Not Before: Oct 26 03:46:49 2011 GMT - Not After : Oct 23 03:46:49 2021 GMT - Subject: CN=Test Deprecated Digest Intermediate CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30: - 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26: - 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63: - 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89: - 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd: - b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78: - 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58: - f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49: - 62:d4:53:b0:d8:9c:34:47:07 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - X509v3 Authority Key Identifier: - keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43 - - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - Signature Algorithm: md2WithRSAEncryption - 95:17:b3:5f:81:5b:9e:d6:e9:de:67:0e:a7:01:2f:b7:f8:db: - 13:25:6b:a3:15:2d:53:08:c6:20:65:9d:8f:e9:9e:e4:bc:87: - 78:59:f6:1f:f4:0e:85:c7:a8:c6:c8:6d:65:7e:b9:f4:73:9b: - 9f:70:2b:b2:0d:03:06:c5:52:5f:59:87:b5:2b:d0:5c:0d:ee: - 8f:40:cd:eb:95:f2:0e:f4:51:a8:e8:76:17:82:71:1a:d1:ea: - 99:54:e4:b7:75:27:54:76:36:6f:c0:4d:5d:fa:bb:98:08:1e: - d4:95:d1:9a:c7:35:83:d5:a1:79:2a:1f:78:b4:2b:de:17:93: - 0c:1b ------BEGIN CERTIFICATE----- -MIICMzCCAZygAwIBAgIBBDANBgkqhkiG9w0BAQIFADApMScwJQYDVQQDDB5UZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ5WhcNMjEx -MDIzMDM0NjQ5WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu -dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ -RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z -2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5 -+lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU -eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB -AgUAA4GBAJUXs1+BW57W6d5nDqcBL7f42xMla6MVLVMIxiBlnY/pnuS8h3hZ9h/0 -DoXHqMbIbWV+ufRzm59wK7INAwbFUl9Zh7Ur0FwN7o9AzeuV8g70UajodheCcRrR -6plU5Ld1J1R2Nm/ATV36u5gIHtSV0ZrHNYPVoXkqH3i0K94Xkwwb ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md2_root.pem b/net/data/ssl/certificates/weak_digest_md2_root.pem deleted file mode 100644 index 140174d..0000000 --- a/net/data/ssl/certificates/weak_digest_md2_root.pem +++ /dev/null
@@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICEjCCAXugAwIBAgIJAMq0TW/f2gFbMA0GCSqGSIb3DQEBAgUAMCkxJzAlBgNV -BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2 -NDlaFw0yMTEwMjMwMzQ2NDlaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE -aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj -BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv -HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00 -P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2 -gctDMA0GCSqGSIb3DQEBAgUAA4GBABnPJVnXJXtImcjcBj31JelbPkLgt8HHjxa+ -LOMNZKIc9d6KWdjMoTNz7Y9dAKiLAJmPp9QAKU4cu0voWRK27O8CjR9Ng7SpfuZ7 -bQ4P22TlcVViAq56+bz/DFRabwBAZtndoawyn04r4Lo/3n/nEONeVTIqsixjN5Au -0snKiMJj ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md4_ee.pem b/net/data/ssl/certificates/weak_digest_md4_ee.pem deleted file mode 100644 index 6ea4b257..0000000 --- a/net/data/ssl/certificates/weak_digest_md4_ee.pem +++ /dev/null
@@ -1,61 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 3 (0x3) - Signature Algorithm: md4WithRSAEncryption - Issuer: CN=Test Deprecated Digest Intermediate CA - Validity - Not Before: Oct 26 03:46:49 2011 GMT - Not After : Oct 23 03:46:49 2021 GMT - Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38: - 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9: - 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf: - 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13: - be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d: - fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee: - 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db: - 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57: - dc:d0:de:df:29:72:45:6f:e3 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD - X509v3 Authority Key Identifier: - keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Alternative Name: - IP Address:127.0.0.1 - Signature Algorithm: md4WithRSAEncryption - a5:f6:ae:83:a1:44:5a:dd:c4:91:a2:d6:88:d8:c6:d1:e5:6d: - c8:71:7a:43:3e:e2:ce:42:a4:7d:94:16:5d:0a:df:33:e3:ea: - c9:22:e3:52:9d:f7:72:3e:24:d5:78:38:67:9f:2d:46:cb:73: - c5:1f:eb:4b:02:5c:25:41:e0:c5:07:03:4c:4c:55:87:db:32: - d0:2e:3e:aa:d4:a6:69:75:12:75:2e:b6:98:24:0e:18:c4:1c: - 60:aa:c5:19:c1:1c:ad:ba:f4:c8:c0:55:2b:61:7d:a4:f4:c6: - 73:0d:61:7e:04:42:e2:69:8d:9c:9d:83:22:e4:cc:cc:3f:b5: - 2a:6d ------BEGIN CERTIFICATE----- -MIICiDCCAfGgAwIBAgIBAzANBgkqhkiG9w0BAQMFADAxMS8wLQYDVQQDDCZUZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2 -NDlaFw0yMTEwMjMwMzQ2NDlaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp -Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB -MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB -AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI -6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN -DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME -GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI -KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQMFAAOBgQCl9q6D -oURa3cSRotaI2MbR5W3IcXpDPuLOQqR9lBZdCt8z4+rJIuNSnfdyPiTVeDhnny1G -y3PFH+tLAlwlQeDFBwNMTFWH2zLQLj6q1KZpdRJ1LraYJA4YxBxgqsUZwRytuvTI -wFUrYX2k9MZzDWF+BELiaY2cnYMi5MzMP7UqbQ== ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md4_intermediate.pem b/net/data/ssl/certificates/weak_digest_md4_intermediate.pem deleted file mode 100644 index 2ba01dd..0000000 --- a/net/data/ssl/certificates/weak_digest_md4_intermediate.pem +++ /dev/null
@@ -1,57 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 3 (0x3) - Signature Algorithm: md4WithRSAEncryption - Issuer: CN=Test Deprecated Digest Root CA - Validity - Not Before: Oct 26 03:46:49 2011 GMT - Not After : Oct 23 03:46:49 2021 GMT - Subject: CN=Test Deprecated Digest Intermediate CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30: - 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26: - 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63: - 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89: - 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd: - b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78: - 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58: - f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49: - 62:d4:53:b0:d8:9c:34:47:07 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - X509v3 Authority Key Identifier: - keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43 - - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - Signature Algorithm: md4WithRSAEncryption - 7e:ca:14:3d:14:04:f4:a4:1a:cf:b5:c6:c7:c2:d3:e7:68:08: - 55:1f:fa:93:28:fa:34:aa:97:29:f7:31:6f:30:a4:25:bd:c5: - fe:28:3d:a9:92:b0:4f:ca:24:3f:7b:1a:16:2e:0d:08:73:8e: - ca:9f:50:da:e9:64:4f:bd:31:c4:72:89:98:8d:55:55:57:96: - 6a:e0:5e:00:12:07:8b:3a:30:06:9a:47:a5:94:39:74:a0:f7: - e1:00:48:2a:90:08:84:80:e3:6b:83:91:c6:74:d8:d9:c2:72: - c7:b9:6e:33:7f:38:46:c1:26:14:5c:1b:85:a3:aa:bb:72:a0: - 84:b2 ------BEGIN CERTIFICATE----- -MIICMzCCAZygAwIBAgIBAzANBgkqhkiG9w0BAQMFADApMScwJQYDVQQDDB5UZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ5WhcNMjEx -MDIzMDM0NjQ5WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu -dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ -RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z -2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5 -+lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU -eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB -AwUAA4GBAH7KFD0UBPSkGs+1xsfC0+doCFUf+pMo+jSqlyn3MW8wpCW9xf4oPamS -sE/KJD97GhYuDQhzjsqfUNrpZE+9McRyiZiNVVVXlmrgXgASB4s6MAaaR6WUOXSg -9+EASCqQCISA42uDkcZ02NnCcse5bjN/OEbBJhRcG4WjqrtyoISy ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md4_root.pem b/net/data/ssl/certificates/weak_digest_md4_root.pem deleted file mode 100644 index 4d5fc291..0000000 --- a/net/data/ssl/certificates/weak_digest_md4_root.pem +++ /dev/null
@@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICEjCCAXugAwIBAgIJAPqB3U0Vl/N1MA0GCSqGSIb3DQEBAwUAMCkxJzAlBgNV -BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2 -NDlaFw0yMTEwMjMwMzQ2NDlaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE -aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj -BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv -HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00 -P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2 -gctDMA0GCSqGSIb3DQEBAwUAA4GBAEvEn5YHixuMeYW3TpCVpyvNocToAlHiy5xt -iXVN9V31w8X7I7vcUAgqWQYtB0qngQ28akmiY+yyfYkWB3H8B0DCr0STFCbMq0c6 -Ydt5pV3lBQpHUKZFvv5moVVWPXr0f0smZI26KGalHgxdrFJnnP4bp6VhYt8G3KFA -h+nxg1RW ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md5_ee.pem b/net/data/ssl/certificates/weak_digest_md5_ee.pem deleted file mode 100644 index c5a1eb4..0000000 --- a/net/data/ssl/certificates/weak_digest_md5_ee.pem +++ /dev/null
@@ -1,61 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: md5WithRSAEncryption - Issuer: CN=Test Deprecated Digest Intermediate CA - Validity - Not Before: Oct 26 03:46:48 2011 GMT - Not After : Oct 23 03:46:48 2021 GMT - Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38: - 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9: - 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf: - 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13: - be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d: - fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee: - 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db: - 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57: - dc:d0:de:df:29:72:45:6f:e3 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD - X509v3 Authority Key Identifier: - keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Alternative Name: - IP Address:127.0.0.1 - Signature Algorithm: md5WithRSAEncryption - 5c:36:ba:dd:8c:ae:4c:2d:00:32:d9:ed:4d:1d:4b:07:52:28: - 9c:16:18:3f:38:02:9d:d7:8e:16:e6:4b:2d:8c:84:cc:b1:90: - 6c:b4:42:55:56:7c:e6:ec:15:2b:90:0b:7e:89:08:15:5a:11: - 0e:5d:1b:a3:cc:81:79:1e:ea:96:82:75:d8:14:96:0f:17:a5: - cd:50:fd:50:f0:5b:7f:03:54:b3:e3:b5:66:03:c8:00:1d:61: - 36:f3:78:2d:07:82:61:0a:fd:d9:7c:8a:fe:cb:e1:09:df:fb: - b6:2f:09:7b:0b:62:d8:27:18:4e:6e:fe:92:1b:1a:2b:7d:56: - e0:87 ------BEGIN CERTIFICATE----- -MIICiDCCAfGgAwIBAgIBAjANBgkqhkiG9w0BAQQFADAxMS8wLQYDVQQDDCZUZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2 -NDhaFw0yMTEwMjMwMzQ2NDhaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp -Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB -MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB -AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI -6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN -DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME -GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI -KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQQFAAOBgQBcNrrd -jK5MLQAy2e1NHUsHUiicFhg/OAKd144W5kstjITMsZBstEJVVnzm7BUrkAt+iQgV -WhEOXRujzIF5HuqWgnXYFJYPF6XNUP1Q8Ft/A1Sz47VmA8gAHWE283gtB4JhCv3Z -fIr+y+EJ3/u2Lwl7C2LYJxhObv6SGxorfVbghw== ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md5_intermediate.pem b/net/data/ssl/certificates/weak_digest_md5_intermediate.pem deleted file mode 100644 index 6192ffe..0000000 --- a/net/data/ssl/certificates/weak_digest_md5_intermediate.pem +++ /dev/null
@@ -1,57 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: md5WithRSAEncryption - Issuer: CN=Test Deprecated Digest Root CA - Validity - Not Before: Oct 26 03:46:48 2011 GMT - Not After : Oct 23 03:46:48 2021 GMT - Subject: CN=Test Deprecated Digest Intermediate CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30: - 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26: - 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63: - 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89: - 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd: - b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78: - 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58: - f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49: - 62:d4:53:b0:d8:9c:34:47:07 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - X509v3 Authority Key Identifier: - keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43 - - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - Signature Algorithm: md5WithRSAEncryption - a3:9d:4e:8b:42:7b:c2:3a:71:5c:7a:a9:ec:9b:da:04:a4:7d: - f2:53:ba:b5:97:97:21:ae:94:03:23:7e:75:0e:c7:cc:1f:57: - f2:76:ec:aa:bf:4f:2f:d1:2d:d2:3d:10:55:ce:a0:1c:93:b6: - 8a:b6:65:9b:67:7a:a6:2f:04:62:e9:31:69:f4:26:08:a3:41: - d0:11:3a:21:31:b6:32:5e:a0:4c:32:2d:ca:f8:a0:76:be:f2: - a1:bf:15:98:73:26:41:2d:d5:8e:63:e7:5e:ef:61:08:f0:9d: - fb:af:55:1e:37:9c:2a:13:f7:7e:ab:5c:f4:d5:f8:7c:a7:fb: - c0:42 ------BEGIN CERTIFICATE----- -MIICMzCCAZygAwIBAgIBAjANBgkqhkiG9w0BAQQFADApMScwJQYDVQQDDB5UZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ4WhcNMjEx -MDIzMDM0NjQ4WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu -dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ -RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z -2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5 -+lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU -eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB -BAUAA4GBAKOdTotCe8I6cVx6qeyb2gSkffJTurWXlyGulAMjfnUOx8wfV/J27Kq/ -Ty/RLdI9EFXOoByTtoq2ZZtneqYvBGLpMWn0JgijQdAROiExtjJeoEwyLcr4oHa+ -8qG/FZhzJkEt1Y5j517vYQjwnfuvVR43nCoT936rXPTV+Hyn+8BC ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_md5_root.pem b/net/data/ssl/certificates/weak_digest_md5_root.pem deleted file mode 100644 index cea1d70..0000000 --- a/net/data/ssl/certificates/weak_digest_md5_root.pem +++ /dev/null
@@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICEjCCAXugAwIBAgIJANhsW8HvYIVtMA0GCSqGSIb3DQEBBAUAMCkxJzAlBgNV -BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2 -NDhaFw0yMTEwMjMwMzQ2NDhaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE -aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj -BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv -HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00 -P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2 -gctDMA0GCSqGSIb3DQEBBAUAA4GBAC1qyqlaaPzmY78GXsw1MY2VbSNmGyRxWw3W -dJVSkdKv8jeeZnVT6JaiHzmM0zQ9E8x0szILJlJ3r9CNKiuXgpCvbaWqiWwytFny -8Mea/xS8FwIfPoxiOt/MdjvnfUWi1ukZaOy88rg5V7/mVdObTzu4VouD4qxhpdTa -QRn7eFqR ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_sha1_ee.pem b/net/data/ssl/certificates/weak_digest_sha1_ee.pem deleted file mode 100644 index 5368e62..0000000 --- a/net/data/ssl/certificates/weak_digest_sha1_ee.pem +++ /dev/null
@@ -1,61 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha1WithRSAEncryption - Issuer: CN=Test Deprecated Digest Intermediate CA - Validity - Not Before: Oct 26 03:46:48 2011 GMT - Not After : Oct 23 03:46:48 2021 GMT - Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1 - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38: - 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9: - 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf: - 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13: - be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d: - fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee: - 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db: - 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57: - dc:d0:de:df:29:72:45:6f:e3 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:FALSE - X509v3 Subject Key Identifier: - 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD - X509v3 Authority Key Identifier: - keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Alternative Name: - IP Address:127.0.0.1 - Signature Algorithm: sha1WithRSAEncryption - ab:a4:58:6a:d8:f4:87:00:11:45:23:ea:75:a9:0d:cd:87:73: - 0e:73:f2:97:d3:74:b0:cd:90:c9:45:83:03:c3:82:ee:2f:79: - 51:31:12:1c:39:a0:e2:45:f2:c2:4e:70:8c:e4:f3:af:15:4c: - be:5d:e7:c3:96:79:c8:a4:98:6d:37:8d:3f:9f:9e:89:32:ca: - a6:a7:e2:c8:f3:84:64:08:34:57:bd:10:22:96:78:39:b4:33: - dc:f2:db:83:ec:0c:20:58:ce:ba:98:44:dc:ca:a2:10:6c:5a: - d5:57:85:b9:3c:f0:48:99:98:e1:80:88:08:4c:cc:83:0d:40: - ff:8d ------BEGIN CERTIFICATE----- -MIICiDCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQUFADAxMS8wLQYDVQQDDCZUZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2 -NDhaFw0yMTEwMjMwMzQ2NDhaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp -Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB -MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB -AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI -6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN -DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME -GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI -KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOBgQCrpFhq -2PSHABFFI+p1qQ3Nh3MOc/KX03SwzZDJRYMDw4LuL3lRMRIcOaDiRfLCTnCM5POv -FUy+XefDlnnIpJhtN40/n56JMsqmp+LI84RkCDRXvRAilng5tDPc8tuD7AwgWM66 -mETcyqIQbFrVV4W5PPBImZjhgIgITMyDDUD/jQ== ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem b/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem deleted file mode 100644 index 478d116d..0000000 --- a/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem +++ /dev/null
@@ -1,57 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha1WithRSAEncryption - Issuer: CN=Test Deprecated Digest Root CA - Validity - Not Before: Oct 26 03:46:48 2011 GMT - Not After : Oct 23 03:46:48 2021 GMT - Subject: CN=Test Deprecated Digest Intermediate CA - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public Key: (1024 bit) - Modulus (1024 bit): - 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30: - 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26: - 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63: - 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89: - 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd: - b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78: - 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58: - f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49: - 62:d4:53:b0:d8:9c:34:47:07 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: critical - CA:TRUE - X509v3 Subject Key Identifier: - A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19 - X509v3 Authority Key Identifier: - keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43 - - X509v3 Key Usage: critical - Certificate Sign, CRL Sign - Signature Algorithm: sha1WithRSAEncryption - 4e:30:a8:25:da:ac:90:a9:5e:6c:23:7f:76:1e:2d:64:79:78: - 61:84:dc:06:12:43:72:a6:18:f1:f2:23:fa:e9:1f:de:3a:52: - 1c:ce:cd:f7:7e:3c:92:ce:7f:f3:1f:f5:bc:18:17:95:cb:57: - 34:f1:88:b1:c8:1f:51:e1:d3:3d:dd:17:c6:d4:af:f1:42:ec: - 85:d7:bf:16:22:e0:88:82:92:cc:94:89:e5:eb:9d:cc:fe:31: - 50:6f:ea:d8:70:f9:ef:6b:ca:3e:af:bd:61:42:33:ce:23:bf: - 50:5f:55:14:64:2b:f7:fd:a6:29:41:a8:65:c3:fa:c4:f0:c7: - 71:a5 ------BEGIN CERTIFICATE----- -MIICMzCCAZygAwIBAgIBATANBgkqhkiG9w0BAQUFADApMScwJQYDVQQDDB5UZXN0 -IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ4WhcNMjEx -MDIzMDM0NjQ4WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu -dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ -RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z -2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5 -+lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU -eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB -BQUAA4GBAE4wqCXarJCpXmwjf3YeLWR5eGGE3AYSQ3KmGPHyI/rpH946UhzOzfd+ -PJLOf/Mf9bwYF5XLVzTxiLHIH1Hh0z3dF8bUr/FC7IXXvxYi4IiCksyUieXrncz+ -MVBv6thw+e9ryj6vvWFCM84jv1BfVRRkK/f9pilBqGXD+sTwx3Gl ------END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/weak_digest_sha1_root.pem b/net/data/ssl/certificates/weak_digest_sha1_root.pem deleted file mode 100644 index a10f00911..0000000 --- a/net/data/ssl/certificates/weak_digest_sha1_root.pem +++ /dev/null
@@ -1,14 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICEjCCAXugAwIBAgIJAOojr7l1i8pcMA0GCSqGSIb3DQEBBQUAMCkxJzAlBgNV -BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2 -NDhaFw0yMTEwMjMwMzQ2NDhaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE -aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj -BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv -HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00 -P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2 -gctDMA0GCSqGSIb3DQEBBQUAA4GBAFfvM72mFeBd4HfP/U0HTmeQsPTorL01BRGe -kIbHSBfliYF5fTXbHHjXqvnmNvCwfjO1+HyCxg3opwmDS5DiwkT2XtqYeF80h8/X -J+hsdo+wJJiD0G8V3wOkBjlS5N3WaH3vhPikLkvmr2UzeeO3ORaaDUlRpzzOS2Pn -28TAE0Wq ------END CERTIFICATE-----
diff --git a/net/data/test_support_bundle_data.filelist b/net/data/test_support_bundle_data.filelist index 5f39a92..c172c2cc 100644 --- a/net/data/test_support_bundle_data.filelist +++ b/net/data/test_support_bundle_data.filelist
@@ -180,18 +180,6 @@ data/ssl/certificates/unittest.selfsigned.der data/ssl/certificates/verisign_intermediate_ca_2011.pem data/ssl/certificates/verisign_intermediate_ca_2016.pem -data/ssl/certificates/weak_digest_md2_ee.pem -data/ssl/certificates/weak_digest_md2_intermediate.pem -data/ssl/certificates/weak_digest_md2_root.pem -data/ssl/certificates/weak_digest_md4_ee.pem -data/ssl/certificates/weak_digest_md4_intermediate.pem -data/ssl/certificates/weak_digest_md4_root.pem -data/ssl/certificates/weak_digest_md5_ee.pem -data/ssl/certificates/weak_digest_md5_intermediate.pem -data/ssl/certificates/weak_digest_md5_root.pem -data/ssl/certificates/weak_digest_sha1_ee.pem -data/ssl/certificates/weak_digest_sha1_intermediate.pem -data/ssl/certificates/weak_digest_sha1_root.pem data/ssl/certificates/websocket_cacert.pem data/ssl/certificates/websocket_client_cert.p12 data/ssl/certificates/wildcard.pem
diff --git a/net/disk_cache/sql/sql_persistent_store_backend.cc b/net/disk_cache/sql/sql_persistent_store_backend.cc index 7a1964b..13f469f1 100644 --- a/net/disk_cache/sql/sql_persistent_store_backend.cc +++ b/net/disk_cache/sql/sql_persistent_store_backend.cc
@@ -12,6 +12,7 @@ #include <string> #include "base/containers/flat_set.h" +#include "base/debug/dump_without_crashing.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/memory/ref_counted.h" @@ -2612,10 +2613,21 @@ store_status_.total_size); } - // Intentionally DCHECK for performance. +#if DCHECK_IS_ON() // In debug builds, verify consistency by recalculating. - DCHECK_EQ(store_status_.entry_count, CalculateResourceEntryCount()); - DCHECK_EQ(store_status_.total_size, CalculateTotalSize()); + const int64_t actual_entry_count = CalculateResourceEntryCount(); + const int64_t actual_total_size = CalculateTotalSize(); + if (store_status_.entry_count != actual_entry_count || + store_status_.total_size != actual_total_size) { + base::debug::DumpWithoutCrashing(); + store_status_.entry_count = actual_entry_count; + meta_table_.SetValue(kSqlBackendMetaTableKeyEntryCount, + store_status_.entry_count); + store_status_.total_size = actual_total_size; + meta_table_.SetValue(kSqlBackendMetaTableKeyTotalSize, + store_status_.total_size); + } +#endif // DCHECK_IS_ON() // Attempt to commit the transaction. If it fails, revert the in-memory // store status to its state before the updates.
diff --git a/net/disk_cache/sql/sql_persistent_store_unittest.cc b/net/disk_cache/sql/sql_persistent_store_unittest.cc index f22e689..fb370a03 100644 --- a/net/disk_cache/sql/sql_persistent_store_unittest.cc +++ b/net/disk_cache/sql/sql_persistent_store_unittest.cc
@@ -11,6 +11,7 @@ #include <utility> #include "base/containers/flat_set.h" +#include "base/debug/dump_without_crashing.h" #include "base/feature_list.h" #include "base/files/file_path.h" #include "base/files/file_util.h" @@ -5832,4 +5833,84 @@ EXPECT_EQ(gone_count, 1); } +#if DCHECK_IS_ON() +TEST_F(SqlPersistentStoreTest, DetectAndFixEntryCountMetadataInconsistency) { + CreateAndCloseInitializedStore(); + + // Manually set the entry count to 1 to cause an inconsistency + // (the actual entry count is 0). + { + auto db_handle = ManuallyOpenDatabase(); + auto meta_table = ManuallyOpenMetaTable(db_handle.get()); + ASSERT_TRUE(meta_table->SetValue(kSqlBackendMetaTableKeyEntryCount, 1)); + } + + // Re-open the store. + CreateStore(); + ASSERT_EQ(Init(), SqlPersistentStore::Error::kOk); + + // The store loads the incorrect metadata. + EXPECT_EQ(GetEntryCount(), 1); + + // Set up the DumpWithoutCrashing callback. + static bool g_dump_called = false; + base::debug::SetDumpWithoutCrashingFunction([]() { g_dump_called = true; }); + + // Trigger a transaction that updates the store status. + const CacheEntryKey kKey("my-key"); + CreateEntryAndGetResId(kKey); + + // DumpWithoutCrashing should have been called due to the inconsistency. + EXPECT_TRUE(g_dump_called); + + // The metadata should be corrected to 1. + // We added 1 entry to an actually empty database. + EXPECT_EQ(GetEntryCount(), 1); + + // Clean up the callback and the throttling. + base::debug::SetDumpWithoutCrashingFunction(nullptr); + base::debug::ResetDumpWithoutCrashingThrottlingForTesting(); +} + +TEST_F(SqlPersistentStoreTest, DetectAndFixTotalSizeMetadataInconsistency) { + CreateAndCloseInitializedStore(); + + // Manually set the total size to 1000 to cause an inconsistency + // (the actual total size is 0). + { + auto db_handle = ManuallyOpenDatabase(); + auto meta_table = ManuallyOpenMetaTable(db_handle.get()); + ASSERT_TRUE(meta_table->SetValue(kSqlBackendMetaTableKeyTotalSize, 1000)); + } + + // Re-open the store. + CreateStore(); + ASSERT_EQ(Init(), SqlPersistentStore::Error::kOk); + + // The store loads the incorrect metadata. + EXPECT_EQ(GetSizeOfAllEntries(), 1000); + + // Set up the DumpWithoutCrashing callback. + static bool g_dump_called = false; + base::debug::SetDumpWithoutCrashingFunction([]() { g_dump_called = true; }); + + // Trigger a transaction that updates the store status. + const CacheEntryKey kKey("my-key"); + CreateEntryAndGetResId(kKey); + + // DumpWithoutCrashing should have been called due to the inconsistency. + EXPECT_TRUE(g_dump_called); + + // The metadata should be corrected to the actual size. + // We added 1 entry to an actually empty database. + // Total size is just the static resource size + key size. + EXPECT_EQ(GetSizeOfAllEntries(), + kSqlBackendStaticResourceSize + kKey.string().size()); + + // Clean up the callback and the throttling. + base::debug::SetDumpWithoutCrashingFunction(nullptr); + base::debug::ResetDumpWithoutCrashingThrottlingForTesting(); +} +#endif // DCHECK_IS_ON() + } // namespace disk_cache
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc index e384629a..6bf10ce46 100644 --- a/net/quic/quic_chromium_client_session.cc +++ b/net/quic/quic_chromium_client_session.cc
@@ -3084,10 +3084,18 @@ old_client = true; UMA_HISTOGRAM_COUNTS_1000("Net.QuicSession.MTCLandmarkDelta.OldClient", *server_landmark - *client_landmark); + base::UmaHistogramCounts1000( + HistogramNameForResumptionVariant( + "Net.QuicSession.MTCLandmarkDelta.OldClient", is_resumption), + *server_landmark - *client_landmark); } else { UMA_HISTOGRAM_COUNTS_1000( "Net.QuicSession.MTCLandmarkDelta.CurrentClient", *client_landmark - *server_landmark); + base::UmaHistogramCounts1000( + HistogramNameForResumptionVariant( + "Net.QuicSession.MTCLandmarkDelta.CurrentClient", is_resumption), + *client_landmark - *server_landmark); } } if (mtc_update_time_seconds != 0) { @@ -4012,12 +4020,13 @@ size_t handshake_bytes = crypto_stream_->crypto_bytes_read() + crypto_stream_->crypto_bytes_written(); - UMA_HISTOGRAM_COUNTS_100000("Net.QuicSession.TLSHandshakeBytes.MTC", - handshake_bytes); - base::UmaHistogramCounts10000( + base::UmaHistogramCustomCounts("Net.QuicSession.TLSHandshakeBytes.MTC2", + handshake_bytes, /*min=*/1, + /*exclusive_max=*/8000, /*buckets=*/100); + base::UmaHistogramCustomCounts( HistogramNameForResumptionVariant( - "Net.QuicSession.TLSHandshakeBytes.MTC", is_resumption), - handshake_bytes); + "Net.QuicSession.TLSHandshakeBytes.MTC2", is_resumption), + handshake_bytes, /*min=*/1, /*exclusive_max=*/8000, /*buckets=*/100); } // Indicate that the handshake is complete so that we can safely send pings
diff --git a/net/quic/quic_end_to_end_unittest.cc b/net/quic/quic_end_to_end_unittest.cc index 22e4056..c72e2e2 100644 --- a/net/quic/quic_end_to_end_unittest.cc +++ b/net/quic/quic_end_to_end_unittest.cc
@@ -406,6 +406,14 @@ histograms.ExpectTotalCount("Net.QuicSession.MTCLandmarkDelta.OldClient", 0); histograms.ExpectTotalCount("Net.QuicSession.MTCLandmarkDelta.CurrentClient", 0); + histograms.ExpectTotalCount( + "Net.QuicSession.MTCLandmarkDelta.OldClient.NewConnection", 0); + histograms.ExpectTotalCount( + "Net.QuicSession.MTCLandmarkDelta.CurrentClient.NewConnection", 0); + histograms.ExpectTotalCount( + "Net.QuicSession.MTCLandmarkDelta.OldClient.Resumption", 0); + histograms.ExpectTotalCount( + "Net.QuicSession.MTCLandmarkDelta.CurrentClient.Resumption", 0); histograms.ExpectTimeBucketCount("Net.QuicSession.MTCMetadataAge", kMtcUpdateAge, 1); @@ -456,11 +464,11 @@ // Should be logged, but we don't know what the exact value will be, so just // check that a sample is present. - histograms.ExpectTotalCount("Net.QuicSession.TLSHandshakeBytes.MTC", 1); + histograms.ExpectTotalCount("Net.QuicSession.TLSHandshakeBytes.MTC2", 1); histograms.ExpectTotalCount( - "Net.QuicSession.TLSHandshakeBytes.MTC.NewConnection", 1); + "Net.QuicSession.TLSHandshakeBytes.MTC2.NewConnection", 1); histograms.ExpectTotalCount( - "Net.QuicSession.TLSHandshakeBytes.MTC.Resumption", 0); + "Net.QuicSession.TLSHandshakeBytes.MTC2.Resumption", 0); } #endif // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
diff --git a/net/tools/cert_verify_tool/verify_using_cert_verify_proc.cc b/net/tools/cert_verify_tool/verify_using_cert_verify_proc.cc index c7e109bd..ebd0cd1 100644 --- a/net/tools/cert_verify_tool/verify_using_cert_verify_proc.cc +++ b/net/tools/cert_verify_tool/verify_using_cert_verify_proc.cc
@@ -61,8 +61,6 @@ void PrintCertVerifyResult(const net::CertVerifyResult& result) { PrintCertStatus(result.cert_status); - if (result.has_sha1) - std::cout << "has_sha1\n"; if (result.is_issued_by_known_root) std::cout << "is_issued_by_known_root\n";
diff --git a/remoting/DEPS b/remoting/DEPS index ac7e413..0e92972 100644 --- a/remoting/DEPS +++ b/remoting/DEPS
@@ -17,7 +17,6 @@ "+third_party/webrtc/p2p", "+third_party/webrtc/modules/desktop_capture", "+third_party/webrtc_overrides", - "+third_party/libjingle_xmpp/xmllite", "+ui/base/keycodes", "+ui/base/l10n", "+ui/base/resource",
diff --git a/remoting/base/DEPS b/remoting/base/DEPS index 433218c..64a2c83b 100644 --- a/remoting/base/DEPS +++ b/remoting/base/DEPS
@@ -10,7 +10,6 @@ "+third_party/breakpad", "+third_party/crashpad", "+third_party/google_trust_services", - "+third_party/grpc", "+third_party/protobuf", "+third_party/zlib", "+ui/base",
diff --git a/remoting/host/DEPS b/remoting/host/DEPS index 0867c06b..e6c5293 100644 --- a/remoting/host/DEPS +++ b/remoting/host/DEPS
@@ -25,11 +25,9 @@ "+services/device/wake_lock/power_save_blocker", "+services/network", "+sql", - "+third_party/grpc", "+third_party/jsoncpp", "+third_party/skia", "+third_party/webrtc", - "+third_party/libjingle_xmpp/xmpp", "+ui/aura", "+ui/accessibility/platform", "+ui/base",
diff --git a/remoting/host/daemon_process.cc b/remoting/host/daemon_process.cc index bad55b4..7ef7979f 100644 --- a/remoting/host/daemon_process.cc +++ b/remoting/host/daemon_process.cc
@@ -86,6 +86,7 @@ void DaemonProcess::OnWorkerProcessStopped() { desktop_session_manager_.reset(); host_status_observer_.reset(); + DeleteAllDesktopSessions(); } void DaemonProcess::OnAssociatedInterfaceRequest( @@ -212,6 +213,25 @@ desktop_sessions_.push_back(session.release()); } +void DaemonProcess::ReconnectDesktopSession( + int terminal_id, + mojom::DesktopSessionOptionsPtr options) { + DCHECK(caller_task_runner()->BelongsToCurrentThread()); + + auto it = std::ranges::find_if(desktop_sessions_, + [terminal_id](DesktopSession* session) { + return session->id() == terminal_id; + }); + + if (it == desktop_sessions_.end()) { + LOG(ERROR) << "Invalid terminal ID: " << terminal_id; + CrashNetworkProcess(FROM_HERE); + return; + } + VLOG(1) << "Daemon: reconnecting desktop session " << terminal_id; + (*it)->ReconnectNetworkChannel(*options); +} + void DaemonProcess::SetScreenResolution(int terminal_id, const ScreenResolution& resolution) { DCHECK(caller_task_runner()->BelongsToCurrentThread());
diff --git a/remoting/host/daemon_process.h b/remoting/host/daemon_process.h index 440c338a..b8384599 100644 --- a/remoting/host/daemon_process.h +++ b/remoting/host/daemon_process.h
@@ -7,11 +7,11 @@ #include <stdint.h> -#include <list> #include <memory> #include <string> #include "base/compiler_specific.h" +#include "base/containers/circular_deque.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/process/process.h" @@ -44,8 +44,8 @@ public HostStatusObserver, public mojom::DesktopSessionManager { public: - typedef std::list<raw_ptr<DesktopSession, CtnExperimental>> - DesktopSessionList; + using DesktopSessionList = + base::circular_deque<raw_ptr<DesktopSession, CtnExperimental>>; DaemonProcess(const DaemonProcess&) = delete; DaemonProcess& operator=(const DaemonProcess&) = delete; @@ -78,6 +78,9 @@ // mojom::DesktopSessionManager implementation. void CreateDesktopSession(int terminal_id, mojom::DesktopSessionOptionsPtr options) override; + void ReconnectDesktopSession( + int terminal_id, + mojom::DesktopSessionOptionsPtr options) override; void CloseDesktopSession(int terminal_id) override; void SetScreenResolution(int terminal_id, const ScreenResolution& resolution) override;
diff --git a/remoting/host/daemon_process_unittest.cc b/remoting/host/daemon_process_unittest.cc index b63a78a..6a56bf7 100644 --- a/remoting/host/daemon_process_unittest.cc +++ b/remoting/host/daemon_process_unittest.cc
@@ -44,6 +44,8 @@ ~FakeDesktopSession() override; void SetScreenResolution(const ScreenResolution& resolution) override {} + void ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) override {} }; class MockDaemonProcess : public DaemonProcess {
diff --git a/remoting/host/desktop_process.cc b/remoting/host/desktop_process.cc index ee4e98a..4e7cd10e 100644 --- a/remoting/host/desktop_process.cc +++ b/remoting/host/desktop_process.cc
@@ -13,6 +13,7 @@ #include "base/functional/callback_helpers.h" #include "base/logging.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" #include "base/message_loop/message_pump_type.h" #include "base/notreached.h" #include "base/task/current_thread.h" @@ -60,7 +61,13 @@ void DesktopProcess::OnNetworkProcessDisconnected() { DCHECK(caller_task_runner_->BelongsToCurrentThread()); - OnChannelError(); + if (desktop_agent_) { + desktop_agent_->Stop(); + desktop_agent_ = nullptr; + } + if (on_network_process_disconnected_callback_) { + std::move(on_network_process_disconnected_callback_).Run(); + } } void DesktopProcess::CrashNetworkProcess(const base::Location& location) { @@ -112,6 +119,7 @@ caller_task_runner_ = nullptr; input_task_runner_ = nullptr; io_task_runner_ = nullptr; + audio_task_runner_ = nullptr; desktop_environment_factory_.reset(); } @@ -129,6 +137,16 @@ mojo::PendingAssociatedReceiver<mojom::WorkerProcessControl> pending_receiver(std::move(handle)); worker_process_control_.Bind(std::move(pending_receiver)); + } else if (interface_name == mojom::DesktopProcessControl::Name_) { + if (desktop_process_control_.is_bound()) { + LOG(ERROR) << "Receiver already bound for associated interface: " + << mojom::DesktopProcessControl::Name_; + CrashProcess(__func__, __FILE__, __LINE__); + } + + mojo::PendingAssociatedReceiver<mojom::DesktopProcessControl> + pending_receiver(std::move(handle)); + desktop_process_control_.Bind(std::move(pending_receiver)); } else { LOG(ERROR) << "Received unexpected associated interface request: " << interface_name; @@ -145,26 +163,19 @@ desktop_environment_factory_ = std::move(desktop_environment_factory); // Launch the audio capturing thread. - scoped_refptr<AutoThreadTaskRunner> audio_task_runner; #if BUILDFLAG(IS_WIN) // On Windows the AudioCapturer requires COM, so we run a single-threaded // apartment, which requires a UI thread. - audio_task_runner = AutoThread::CreateWithLoopAndComInitTypes( + audio_task_runner_ = AutoThread::CreateWithLoopAndComInitTypes( "ChromotingAudioThread", caller_task_runner_, base::MessagePumpType::UI, AutoThread::COM_INIT_STA); #else // !BUILDFLAG(IS_WIN) - audio_task_runner = AutoThread::CreateWithType( + audio_task_runner_ = AutoThread::CreateWithType( "ChromotingAudioThread", caller_task_runner_, base::MessagePumpType::IO); #endif // !BUILDFLAG(IS_WIN) // Create a desktop agent. - desktop_agent_ = - new DesktopSessionAgent(audio_task_runner, caller_task_runner_, - input_task_runner_, io_task_runner_); - - // Initialize the agent and create an IPC channel to talk to it. - mojo::ScopedMessagePipeHandle desktop_pipe = - desktop_agent_->Initialize(weak_factory_.GetWeakPtr()); + mojo::ScopedMessagePipeHandle desktop_pipe = CreateDesktopAgent(); // Connect to the daemon. daemon_channel_ = IPC::ChannelProxy::Create( @@ -181,6 +192,16 @@ return true; } +void DesktopProcess::SetOnNetworkProcessDisconnectedCallbackForTesting( + base::OnceClosure callback) { + on_network_process_disconnected_callback_ = std::move(callback); +} + +void DesktopProcess::SetOnDesktopAgentCreatedCallbackForTesting( + base::OnceClosure callback) { + on_desktop_agent_created_callback_ = std::move(callback); +} + void DesktopProcess::CrashProcess(const std::string& function_name, const std::string& file_name, int line_number) { @@ -188,4 +209,31 @@ ::remoting::CrashProcess(function_name, file_name, line_number); } +void DesktopProcess::ReconnectNetworkChannel() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + if (desktop_agent_) { + LOG(ERROR) << "Cannot reconnect the network channel when the " + << "DesktopSessionAgent is still active."; + CrashProcess(__func__, __FILE__, __LINE__); + return; + } + desktop_session_request_handler_->ConnectDesktopChannel(CreateDesktopAgent()); +} + +mojo::ScopedMessagePipeHandle DesktopProcess::CreateDesktopAgent() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + desktop_agent_ = base::MakeRefCounted<DesktopSessionAgent>( + audio_task_runner_, caller_task_runner_, input_task_runner_, + io_task_runner_); + + if (on_desktop_agent_created_callback_) { + std::move(on_desktop_agent_created_callback_).Run(); + } + + // Initialize the agent and create an IPC channel to talk to it. + return desktop_agent_->Initialize(weak_factory_.GetWeakPtr()); +} + } // namespace remoting
diff --git a/remoting/host/desktop_process.h b/remoting/host/desktop_process.h index 60f1cb7..f492036 100644 --- a/remoting/host/desktop_process.h +++ b/remoting/host/desktop_process.h
@@ -11,6 +11,7 @@ #include <string> #include "base/compiler_specific.h" +#include "base/functional/callback_forward.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "ipc/ipc_listener.h" @@ -35,7 +36,8 @@ class DesktopProcess : public DesktopSessionAgent::Delegate, public IPC::Listener, - public mojom::WorkerProcessControl { + public mojom::WorkerProcessControl, + public mojom::DesktopProcessControl { public: DesktopProcess(scoped_refptr<AutoThreadTaskRunner> caller_task_runner, scoped_refptr<AutoThreadTaskRunner> input_task_runner, @@ -64,6 +66,9 @@ const std::string& file_name, int line_number) override; + // mojom::DesktopProcessControl implementation. + void ReconnectNetworkChannel() override; + // Injects Secure Attention Sequence. void InjectSas(); @@ -75,7 +80,18 @@ bool Start( std::unique_ptr<DesktopEnvironmentFactory> desktop_environment_factory); + // Sets a callback that will be run once the network process has disconnected + // for testing purposes. + void SetOnNetworkProcessDisconnectedCallbackForTesting( + base::OnceClosure callback); + + // Sets a callback that will be run once the desktop agent is created for + // testing purposes. + void SetOnDesktopAgentCreatedCallbackForTesting(base::OnceClosure callback); + private: + mojo::ScopedMessagePipeHandle CreateDesktopAgent(); + // Task runner on which public methods of this class should be called. scoped_refptr<AutoThreadTaskRunner> caller_task_runner_; @@ -85,6 +101,9 @@ // Used for IPC communication with Daemon process. scoped_refptr<AutoThreadTaskRunner> io_task_runner_; + // Used for audio capturing and encoding. + scoped_refptr<AutoThreadTaskRunner> audio_task_runner_; + // Factory used to create integration components for use by |desktop_agent_|. std::unique_ptr<DesktopEnvironmentFactory> desktop_environment_factory_; @@ -104,6 +123,11 @@ mojo::AssociatedReceiver<mojom::WorkerProcessControl> worker_process_control_{ this}; + mojo::AssociatedReceiver<mojom::DesktopProcessControl> + desktop_process_control_{this}; + + base::OnceClosure on_network_process_disconnected_callback_; + base::OnceClosure on_desktop_agent_created_callback_; base::WeakPtrFactory<DesktopProcess> weak_factory_{this}; };
diff --git a/remoting/host/desktop_process_unittest.cc b/remoting/host/desktop_process_unittest.cc index a3ee191..68bb6b4 100644 --- a/remoting/host/desktop_process_unittest.cc +++ b/remoting/host/desktop_process_unittest.cc
@@ -12,6 +12,7 @@ #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/location.h" +#include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_pump_type.h" @@ -159,6 +160,8 @@ pending_remote); protected: + raw_ptr<DesktopProcess> desktop_process_; + // The daemon's end of the daemon-to-desktop channel. std::unique_ptr<IPC::ChannelProxy> daemon_channel_; @@ -295,12 +298,14 @@ DesktopProcess desktop_process(ui_task_runner, io_task_runner_, io_task_runner_, std::move(pipe.handle1)); + desktop_process_ = &desktop_process; EXPECT_TRUE(desktop_process.Start(std::move(desktop_environment_factory))); daemon_channel_->GetRemoteAssociatedInterface(&worker_process_control_); ui_task_runner = nullptr; run_loop.Run(); + desktop_process_ = nullptr; } void DesktopProcessTest::RunDeathTest() {
diff --git a/remoting/host/desktop_session.h b/remoting/host/desktop_session.h index 9156a6e..32c235d 100644 --- a/remoting/host/desktop_session.h +++ b/remoting/host/desktop_session.h
@@ -7,6 +7,7 @@ #include "base/compiler_specific.h" #include "base/memory/raw_ptr.h" +#include "remoting/host/mojom/desktop_session.mojom.h" namespace remoting { @@ -25,6 +26,12 @@ // Changes the screen resolution of the desktop session. virtual void SetScreenResolution(const ScreenResolution& resolution) = 0; + // Requests the desktop process to reconnect the network channel, i.e. + // creating a new DesktopSessionAgent instance and passing a new desktop pipe + // to the network process via the daemon process. + virtual void ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) = 0; + int id() const { return id_; } protected:
diff --git a/remoting/host/desktop_session_proxy.cc b/remoting/host/desktop_session_proxy.cc index 3058e30..ca350a8 100644 --- a/remoting/host/desktop_session_proxy.cc +++ b/remoting/host/desktop_session_proxy.cc
@@ -7,6 +7,7 @@ #include <stddef.h> #include <memory> +#include <string_view> #include <utility> #include "base/compiler_specific.h" @@ -600,6 +601,13 @@ desktop_session_control_->SetUpUrlForwarder(); } +std::string_view DesktopSessionProxy::client_jid() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + return client_session_control_ ? client_session_control_->client_jid() + : std::string_view{}; +} + void DesktopSessionProxy::OnUrlForwarderStateChange( mojom::UrlForwarderState state) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/remoting/host/desktop_session_proxy.h b/remoting/host/desktop_session_proxy.h index 600e9b5..31035a6 100644 --- a/remoting/host/desktop_session_proxy.h +++ b/remoting/host/desktop_session_proxy.h
@@ -9,6 +9,7 @@ #include <map> #include <memory> #include <optional> +#include <string_view> #include <vector> #include "base/callback_list.h" @@ -193,6 +194,7 @@ void SetUpUrlForwarder( const UrlForwarderConfigurator::SetUpUrlForwarderCallback& callback); + std::string_view client_jid() const; uint32_t desktop_session_id() const { return desktop_session_id_; } private:
diff --git a/remoting/host/desktop_session_win.cc b/remoting/host/desktop_session_win.cc index 2487b70..aa35a3d 100644 --- a/remoting/host/desktop_session_win.cc +++ b/remoting/host/desktop_session_win.cc
@@ -22,6 +22,7 @@ #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "base/notimplemented.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_checker.h" @@ -708,6 +709,11 @@ daemon_process()->CrashNetworkProcess(FROM_HERE); } +void DesktopSessionWin::ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) { + NOTIMPLEMENTED(); +} + void DesktopSessionWin::CrashDesktopProcess(const base::Location& location) { DCHECK(caller_task_runner_->BelongsToCurrentThread());
diff --git a/remoting/host/desktop_session_win.h b/remoting/host/desktop_session_win.h index aedfe4e..566f4ffa 100644 --- a/remoting/host/desktop_session_win.h +++ b/remoting/host/desktop_session_win.h
@@ -111,6 +111,10 @@ void InjectSecureAttentionSequence() override; void CrashNetworkProcess() override; + // DesktopSession implementation. + void ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) override; + // Requests the desktop process to crash. void CrashDesktopProcess(const base::Location& location);
diff --git a/remoting/host/ftl_echo_message_listener.cc b/remoting/host/ftl_echo_message_listener.cc index d948f71..23498b95 100644 --- a/remoting/host/ftl_echo_message_listener.cc +++ b/remoting/host/ftl_echo_message_listener.cc
@@ -34,13 +34,10 @@ void FtlEchoMessageListener::OnSignalStrategyStateChange( SignalStrategy::State state) {} -bool FtlEchoMessageListener::OnSignalStrategyIncomingMessage( +bool FtlEchoMessageListener::OnSignalStrategyIncomingFtlMessage( const SignalingAddress& sender_address, - const SignalingMessage& message) { - const ftl::ChromotingMessage* request_message = - std::get_if<ftl::ChromotingMessage>(&message); - if (!request_message || !request_message->has_echo() || - !request_message->echo().has_message()) { + const ftl::ChromotingMessage& message) { + if (!message.has_echo() || !message.echo().has_message()) { return false; } @@ -57,7 +54,7 @@ return false; } - std::string request_message_payload(request_message->echo().message()); + std::string request_message_payload(message.echo().message()); HOST_LOG << "Handling echo message: '" << request_message_payload << "'"; std::string response_message_payload = @@ -65,8 +62,7 @@ ftl::ChromotingMessage response_message; response_message.mutable_echo()->set_message(response_message_payload); - signal_strategy_->SendMessage(sender_address, - SignalingMessage{response_message}); + signal_strategy_->SendFtlMessage(sender_address, std::move(response_message)); return true; }
diff --git a/remoting/host/ftl_echo_message_listener.h b/remoting/host/ftl_echo_message_listener.h index efb0bab..4f9be92 100644 --- a/remoting/host/ftl_echo_message_listener.h +++ b/remoting/host/ftl_echo_message_listener.h
@@ -34,9 +34,9 @@ // SignalStrategy::Listener interface. void OnSignalStrategyStateChange(SignalStrategy::State state) override; - bool OnSignalStrategyIncomingMessage( + bool OnSignalStrategyIncomingFtlMessage( const SignalingAddress& sender_address, - const SignalingMessage& message) override; + const ftl::ChromotingMessage& message) override; private: CheckAccessPermissionCallback check_access_permission_callback_;
diff --git a/remoting/host/ftl_echo_message_listener_unittest.cc b/remoting/host/ftl_echo_message_listener_unittest.cc index 10c257d..f7d37d6 100644 --- a/remoting/host/ftl_echo_message_listener_unittest.cc +++ b/remoting/host/ftl_echo_message_listener_unittest.cc
@@ -100,20 +100,17 @@ TEST_F(FtlEchoMessageListenerTest, EchoRequestFromOwnerHandled) { base::RunLoop run_loop; - EXPECT_CALL(signal_strategy_, SendMessage(_, _)) + EXPECT_CALL(signal_strategy_, SendFtlMessage(_, _)) .WillOnce([&](const SignalingAddress& destination_address, - SignalingMessage&& message) -> bool { + ftl::ChromotingMessage&& message) -> bool { std::string username; std::string registration_id; EXPECT_TRUE( destination_address.GetFtlInfo(&username, ®istration_id)); EXPECT_EQ(kOwnerEmail, username); EXPECT_EQ(kRegistrationId, registration_id); - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - EXPECT_TRUE(ftl_message); - EXPECT_TRUE(ftl_message->has_echo()); - EXPECT_EQ(kEchoMessagePayload, ftl_message->echo().message()); + EXPECT_TRUE(message.has_echo()); + EXPECT_EQ(kEchoMessagePayload, message.echo().message()); run_loop.Quit(); return true; @@ -121,9 +118,11 @@ ftl::ChromotingMessage message_proto = CreateEchoMessageWithPayload(kEchoMessagePayload); - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, kRegistrationId), - SignalingMessage{message_proto}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, + kRegistrationId), + message_proto); ASSERT_TRUE(is_handled); run_loop.Run(); @@ -132,29 +131,28 @@ TEST_F(FtlEchoMessageListenerTest, EchoRequestFromServiceRejected) { ftl::ChromotingMessage message_proto = CreateEchoMessageWithPayload(kEchoMessagePayload); - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress("not-ftl-address"), SignalingMessage{message_proto}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress("not-ftl-address"), message_proto); ASSERT_FALSE(is_handled); } TEST_F(FtlEchoMessageListenerTest, EchoRequestFromNonOwnerRejected) { ftl::ChromotingMessage message_proto = CreateEchoMessageWithPayload(kEchoMessagePayload); - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress::CreateFtlSignalingAddress(kUnknownEmail, - kRegistrationId), - SignalingMessage{message_proto}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress::CreateFtlSignalingAddress(kUnknownEmail, + kRegistrationId), + message_proto); ASSERT_FALSE(is_handled); } TEST_F(FtlEchoMessageListenerTest, SuperLongMessageIsTruncated) { base::RunLoop run_loop; - EXPECT_CALL(signal_strategy_, SendMessage(_, _)) - .WillOnce([&](Unused, SignalingMessage&& message) -> bool { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - EXPECT_TRUE(ftl_message); - EXPECT_EQ(kTruncatedMessagePayload, ftl_message->echo().message()); + EXPECT_CALL(signal_strategy_, SendFtlMessage(_, _)) + .WillOnce([&](Unused, ftl::ChromotingMessage&& message) -> bool { + EXPECT_EQ(kTruncatedMessagePayload, message.echo().message()); run_loop.Quit(); return true; @@ -162,39 +160,42 @@ ftl::ChromotingMessage message_proto = CreateEchoMessageWithPayload(kSuperLongMessagePayload); - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, kRegistrationId), - SignalingMessage{message_proto}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, + kRegistrationId), + message_proto); ASSERT_TRUE(is_handled); run_loop.Run(); } TEST_F(FtlEchoMessageListenerTest, EmptyMessageIsRejected) { - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, kRegistrationId), - SignalingMessage{ftl::ChromotingMessage()}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, + kRegistrationId), + ftl::ChromotingMessage()); ASSERT_FALSE(is_handled); } TEST_F(FtlEchoMessageListenerTest, EmptyMessagePayloadIsHandled) { base::RunLoop run_loop; - EXPECT_CALL(signal_strategy_, SendMessage(_, _)) - .WillOnce([&](Unused, SignalingMessage&& message) -> bool { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - EXPECT_TRUE(ftl_message); - EXPECT_TRUE(ftl_message->has_echo()); - EXPECT_TRUE(ftl_message->echo().message().empty()); + EXPECT_CALL(signal_strategy_, SendFtlMessage(_, _)) + .WillOnce([&](Unused, ftl::ChromotingMessage&& message) -> bool { + EXPECT_TRUE(message.has_echo()); + EXPECT_TRUE(message.echo().message().empty()); run_loop.Quit(); return true; }); ftl::ChromotingMessage message_proto = CreateEchoMessageWithPayload(""); - bool is_handled = ftl_echo_message_listener_->OnSignalStrategyIncomingMessage( - SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, kRegistrationId), - SignalingMessage{message_proto}); + bool is_handled = + ftl_echo_message_listener_->OnSignalStrategyIncomingFtlMessage( + SignalingAddress::CreateFtlSignalingAddress(kOwnerEmail, + kRegistrationId), + message_proto); ASSERT_TRUE(is_handled); run_loop.Run();
diff --git a/remoting/host/ftl_host_change_notification_listener.cc b/remoting/host/ftl_host_change_notification_listener.cc index 2d3a91cb..71c1d56b 100644 --- a/remoting/host/ftl_host_change_notification_listener.cc +++ b/remoting/host/ftl_host_change_notification_listener.cc
@@ -30,12 +30,10 @@ void FtlHostChangeNotificationListener::OnSignalStrategyStateChange( SignalStrategy::State state) {} -bool FtlHostChangeNotificationListener::OnSignalStrategyIncomingMessage( +bool FtlHostChangeNotificationListener::OnSignalStrategyIncomingFtlMessage( const SignalingAddress& sender_address, - const SignalingMessage& message) { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - if (!ftl_message || !ftl_message->has_status()) { + const ftl::ChromotingMessage& message) { + if (!message.has_status()) { return false; } // Status messages can only be sent by a backend server (i.e., SYSTEM). @@ -43,7 +41,7 @@ return false; } - switch (ftl_message->status().directory_state()) { + switch (message.status().directory_state()) { case ftl::HostStatusChangeMessage_DirectoryState_DELETED: // OnHostDeleted() may want delete |signal_strategy_|, but SignalStrategy // objects cannot be deleted from a Listener callback, so OnHostDeleted() @@ -55,7 +53,7 @@ return true; default: LOG(ERROR) << "Received unknown directory state: " - << ftl_message->status().directory_state(); + << message.status().directory_state(); return false; } }
diff --git a/remoting/host/ftl_host_change_notification_listener.h b/remoting/host/ftl_host_change_notification_listener.h index e866abe7..f661ed72 100644 --- a/remoting/host/ftl_host_change_notification_listener.h +++ b/remoting/host/ftl_host_change_notification_listener.h
@@ -38,9 +38,9 @@ // SignalStrategy::Listener interface. void OnSignalStrategyStateChange(SignalStrategy::State state) override; - bool OnSignalStrategyIncomingMessage( + bool OnSignalStrategyIncomingFtlMessage( const SignalingAddress& sender_address, - const SignalingMessage& message) override; + const ftl::ChromotingMessage& message) override; private: void OnHostDeleted();
diff --git a/remoting/host/ftl_host_change_notification_listener_unittest.cc b/remoting/host/ftl_host_change_notification_listener_unittest.cc index dab6f236..b4728a6f 100644 --- a/remoting/host/ftl_host_change_notification_listener_unittest.cc +++ b/remoting/host/ftl_host_change_notification_listener_unittest.cc
@@ -38,11 +38,11 @@ list->erase(arg0); } -SignalingMessage CreateMessageWithDirectoryState( +ftl::ChromotingMessage CreateMessageWithDirectoryState( ftl::HostStatusChangeMessage_DirectoryState state) { ftl::ChromotingMessage message; message.mutable_status()->set_directory_state(state); - return SignalingMessage{message}; + return message; } } // namespace @@ -96,10 +96,11 @@ run_loop.Quit(); }); bool is_handled = - ftl_host_change_notification_listener_->OnSignalStrategyIncomingMessage( - system_sender_address_, - CreateMessageWithDirectoryState( - ftl::HostStatusChangeMessage_DirectoryState_DELETED)); + ftl_host_change_notification_listener_ + ->OnSignalStrategyIncomingFtlMessage( + system_sender_address_, + CreateMessageWithDirectoryState( + ftl::HostStatusChangeMessage_DirectoryState_DELETED)); ASSERT_TRUE(is_handled); run_loop.Run(); } @@ -108,10 +109,11 @@ ReceiveNotificationThenDeleteObject_CallbackNotCalled) { EXPECT_CALL(mock_listener_, OnHostDeleted()).Times(0); bool is_handled = - ftl_host_change_notification_listener_->OnSignalStrategyIncomingMessage( - system_sender_address_, - CreateMessageWithDirectoryState( - ftl::HostStatusChangeMessage_DirectoryState_DELETED)); + ftl_host_change_notification_listener_ + ->OnSignalStrategyIncomingFtlMessage( + system_sender_address_, + CreateMessageWithDirectoryState( + ftl::HostStatusChangeMessage_DirectoryState_DELETED)); ASSERT_TRUE(is_handled); ftl_host_change_notification_listener_.reset(); base::RunLoop run_loop; @@ -124,10 +126,11 @@ ReceiveNonSystemNotification_Ignored) { EXPECT_CALL(mock_listener_, OnHostDeleted()).Times(0); bool is_handled = - ftl_host_change_notification_listener_->OnSignalStrategyIncomingMessage( - peer_sender_address_, - CreateMessageWithDirectoryState( - ftl::HostStatusChangeMessage_DirectoryState_DELETED)); + ftl_host_change_notification_listener_ + ->OnSignalStrategyIncomingFtlMessage( + peer_sender_address_, + CreateMessageWithDirectoryState( + ftl::HostStatusChangeMessage_DirectoryState_DELETED)); ASSERT_FALSE(is_handled); base::RunLoop run_loop; base::SequencedTaskRunner::GetCurrentDefault()->PostTask( @@ -138,9 +141,9 @@ TEST_F(FtlHostChangeNotificationListenerTest, ReceiveUnknownChromotingMessage_Ignored) { EXPECT_CALL(mock_listener_, OnHostDeleted()).Times(0); - bool is_handled = - ftl_host_change_notification_listener_->OnSignalStrategyIncomingMessage( - system_sender_address_, SignalingMessage{ftl::ChromotingMessage()}); + bool is_handled = ftl_host_change_notification_listener_ + ->OnSignalStrategyIncomingFtlMessage( + system_sender_address_, ftl::ChromotingMessage()); ASSERT_FALSE(is_handled); base::RunLoop run_loop; base::SequencedTaskRunner::GetCurrentDefault()->PostTask( @@ -152,10 +155,11 @@ ReceiveUnknownDirectoryState_Ignored) { EXPECT_CALL(mock_listener_, OnHostDeleted()).Times(0); bool is_handled = - ftl_host_change_notification_listener_->OnSignalStrategyIncomingMessage( - system_sender_address_, - CreateMessageWithDirectoryState( - ftl::HostStatusChangeMessage_DirectoryState_NOT_SET)); + ftl_host_change_notification_listener_ + ->OnSignalStrategyIncomingFtlMessage( + system_sender_address_, + CreateMessageWithDirectoryState( + ftl::HostStatusChangeMessage_DirectoryState_NOT_SET)); ASSERT_FALSE(is_handled); base::RunLoop run_loop; base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
diff --git a/remoting/host/ipc_desktop_environment.cc b/remoting/host/ipc_desktop_environment.cc index 86ac005..f9676fd8 100644 --- a/remoting/host/ipc_desktop_environment.cc +++ b/remoting/host/ipc_desktop_environment.cc
@@ -4,6 +4,7 @@ #include "remoting/host/ipc_desktop_environment.h" +#include <algorithm> #include <cstdint> #include <memory> #include <string> @@ -41,6 +42,7 @@ #include "remoting/host/mojom/remoting_host.mojom.h" #include "remoting/host/remote_open_url/url_forwarder_configurator.h" #include "remoting/protocol/mouse_cursor_monitor.h" +#include "remoting/signaling/signaling_id_util.h" #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" namespace remoting { @@ -183,19 +185,47 @@ const ScreenResolution& resolution, bool is_curtained) { DCHECK(network_task_runner_->BelongsToCurrentThread()); + DCHECK(desktop_session_proxy); - int id = next_id_++; - bool inserted = - active_connections_.insert(std::make_pair(id, desktop_session_proxy)) - .second; - CHECK(inserted); - - VLOG(1) << "Network: registered desktop environment " << id; + std::string_view client_jid = desktop_session_proxy->client_jid(); + if (client_jid.empty()) { + LOG(ERROR) << "Cannot connect terminal. Client JID is empty."; + return; + } + std::string client_email; + SplitSignalingIdResource(client_jid, &client_email, /*resource=*/nullptr); mojom::DesktopSessionOptionsPtr options = mojom::DesktopSessionOptions::New(); options->screen_resolution = resolution; options->is_curtained = is_curtained; options->required_username = required_username_; + + if (persist_desktop_sessions_) { + auto it = + std::ranges::find_if(connections_, [&client_email](const auto& pair) { + return pair.second.client_email == client_email && + // Find an unused session. + !pair.second.desktop_session_proxy; + }); + if (it != connections_.end()) { + int id = it->first; + VLOG(1) << "Network: reconnecting desktop session " << id; + it->second.desktop_session_proxy = desktop_session_proxy; + desktop_session_manager_->ReconnectDesktopSession(id, std::move(options)); + return; + } + } + + int id = next_id_++; + bool inserted = + connections_ + .insert(std::make_pair( + id, DesktopConnection{desktop_session_proxy, client_email})) + .second; + CHECK(inserted); + + VLOG(1) << "Network: registered desktop session " << id; + desktop_session_manager_->CreateDesktopSession(id, std::move(options)); } @@ -203,20 +233,21 @@ DesktopSessionProxy* desktop_session_proxy) { DCHECK(network_task_runner_->BelongsToCurrentThread()); - ActiveConnectionsList::iterator i; - for (i = active_connections_.begin(); i != active_connections_.end(); ++i) { - if (i->second == desktop_session_proxy) { - break; - } + auto it = FindConnection(desktop_session_proxy); + if (it == connections_.end()) { + return; } - if (i != active_connections_.end()) { - int id = i->first; - active_connections_.erase(i); - - VLOG(1) << "Network: unregistered desktop environment " << id; - desktop_session_manager_->CloseDesktopSession(id); + if (persist_desktop_sessions_) { + it->second.desktop_session_proxy = nullptr; + return; } + + int id = it->first; + connections_.erase(it); + + VLOG(1) << "Network: unregistered desktop session " << id; + desktop_session_manager_->CloseDesktopSession(id); } void IpcDesktopEnvironmentFactory::SetScreenResolution( @@ -224,15 +255,9 @@ const ScreenResolution& resolution) { DCHECK(network_task_runner_->BelongsToCurrentThread()); - ActiveConnectionsList::iterator i; - for (i = active_connections_.begin(); i != active_connections_.end(); ++i) { - if (i->second == desktop_session_proxy) { - break; - } - } - - if (i != active_connections_.end()) { - desktop_session_manager_->SetScreenResolution(i->first, resolution); + auto it = FindConnection(desktop_session_proxy); + if (it != connections_.end()) { + desktop_session_manager_->SetScreenResolution(it->first, resolution); } } @@ -257,7 +282,9 @@ return; } - CHECK(active_connections_.empty()) + // TODO: yuweih - see if we should just terminate sessions with a mismatched + // username. + CHECK(connections_.empty()) << "Cannot change required username when there are active connections."; required_username_ = std::string(username); @@ -277,10 +304,16 @@ return; } - auto i = active_connections_.find(terminal_id); - if (i != active_connections_.end()) { - i->second->DetachFromDesktop(); - i->second->AttachToDesktop(std::move(desktop_pipe), session_id); + auto it = connections_.find(terminal_id); + if (it != connections_.end()) { + DesktopSessionProxy* proxy = it->second.desktop_session_proxy; + if (!proxy) { + LOG(ERROR) << "DesktopSessionAgent attached when the client is not " + << "connected to the desktop session"; + return; + } + proxy->DetachFromDesktop(); + proxy->AttachToDesktop(std::move(desktop_pipe), session_id); } } @@ -293,15 +326,25 @@ return; } - auto i = active_connections_.find(terminal_id); - if (i != active_connections_.end()) { - DesktopSessionProxy* desktop_session_proxy = i->second; - active_connections_.erase(i); + auto it = connections_.find(terminal_id); + if (it != connections_.end()) { + DesktopSessionProxy* desktop_session_proxy = + it->second.desktop_session_proxy; + connections_.erase(it); - // Disconnect the client session. - desktop_session_proxy->DisconnectSession( - ErrorCode::OK, "Terminal disconnected.", FROM_HERE); + if (desktop_session_proxy) { + // Disconnect the client session. + desktop_session_proxy->DisconnectSession( + ErrorCode::OK, "Terminal disconnected.", FROM_HERE); + } } } +IpcDesktopEnvironmentFactory::ConnectionsList::iterator +IpcDesktopEnvironmentFactory::FindConnection(const DesktopSessionProxy* proxy) { + return std::ranges::find_if(connections_, [proxy](const auto& pair) { + return pair.second.desktop_session_proxy == proxy; + }); +} + } // namespace remoting
diff --git a/remoting/host/ipc_desktop_environment.h b/remoting/host/ipc_desktop_environment.h index 7a26236..36a9d21 100644 --- a/remoting/host/ipc_desktop_environment.h +++ b/remoting/host/ipc_desktop_environment.h
@@ -6,7 +6,6 @@ #define REMOTING_HOST_IPC_DESKTOP_ENVIRONMENT_H_ #include <cstdint> -#include <map> #include <memory> #include <string> @@ -26,6 +25,7 @@ #include "remoting/host/mojom/remoting_host.mojom.h" #include "remoting/protocol/desktop_capturer.h" #include "remoting/protocol/mouse_cursor_monitor.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_map.h" #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" namespace base { @@ -130,6 +130,36 @@ void OnTerminalDisconnected(int terminal_id) override; private: + friend class IpcDesktopEnvironmentTest; + + struct DesktopConnection { + // If `persist_desktop_sessions_` is true, this will be nullptr whenever + // the client has disconnected. + raw_ptr<DesktopSessionProxy> desktop_session_proxy; + + // The email address of the CRD client to ensure the correct desktop session + // is reused in case the host is configured to accept connections from + // multiple client users. + std::string client_email; + }; + + // List of DesktopEnvironment instances we've told the daemon process about. + using ConnectionsList = absl::flat_hash_map<int, DesktopConnection>; + + ConnectionsList::iterator FindConnection(const DesktopSessionProxy* proxy); + + // If `persist_desktop_sessions_` is true, instead of closing the desktop + // session when the client disconnects, the session will remain active while + // the pipe to the desktop process is disconnected. When the client with + // the same email address reconnects, the desktop session will be reused and + // the desktop process will be requested to send a new desktop pipe. + // TODO: yuweih - see if it makes sense to enable it on Windows. +#if BUILDFLAG(IS_LINUX) + bool persist_desktop_sessions_ = true; +#else + bool persist_desktop_sessions_ = false; +#endif + // Used to run the audio capturer. scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_; @@ -138,11 +168,7 @@ // Task runner used for running background I/O. scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; - - // List of DesktopEnvironment instances we've told the daemon process about. - typedef std::map<int, raw_ptr<DesktopSessionProxy, CtnExperimental>> - ActiveConnectionsList; - ActiveConnectionsList active_connections_; + ConnectionsList connections_; // Next desktop session ID. IDs are allocated sequentially starting from 0. // This gives us more than 67 years of unique IDs assuming a new ID is
diff --git a/remoting/host/ipc_desktop_environment_unittest.cc b/remoting/host/ipc_desktop_environment_unittest.cc index 39050ed9..73a6e12 100644 --- a/remoting/host/ipc_desktop_environment_unittest.cc +++ b/remoting/host/ipc_desktop_environment_unittest.cc
@@ -157,6 +157,10 @@ CreateDesktopSession, (int, mojom::DesktopSessionOptionsPtr), (override)); + MOCK_METHOD(void, + ReconnectDesktopSession, + (int, mojom::DesktopSessionOptionsPtr), + (override)); MOCK_METHOD(void, CloseDesktopSession, (int), (override)); MOCK_METHOD(void, SetScreenResolution, @@ -191,11 +195,13 @@ void CreateDesktopSession(int terminal_id, mojom::DesktopSessionOptionsPtr options); + void ReconnectDesktopSession(int terminal_id, + mojom::DesktopSessionOptionsPtr options); void CloseDesktopSession(int terminal_id); // Creates a DesktopEnvironment with a fake webrtc::DesktopCapturer, to mock // DesktopEnvironmentFactory::Create(). - void CreateDesktopEnvironment( + void OnCreateDesktopEnvironment( base::WeakPtr<ClientSessionControl>, base::WeakPtr<ClientSessionEvents>, const DesktopEnvironmentOptions&, @@ -205,6 +211,15 @@ // DesktopEnvironment::CreateInputInjector(). std::unique_ptr<InputInjector> CreateInputInjector(); + // Creates a new desktop environment and initializes related variables. Must + // be called when there are no active desktop environments. + void CreateDesktopEnvironment(); + + // Deletes the desktop environment. If persistent desktop sessions is false, + // this will also destroy the desktop process and delete the + // DesktopEnvironmentFactory. If it is true, then you will need to make sure + // the test method does both of these, otherwise the test will hang + // indefinitely. void DeleteDesktopEnvironment(); // Forwards |event| to |clipboard_stub_|. @@ -220,8 +235,6 @@ // Creates a new remote URL forwarder configurator for the desktop process. void ResetRemoteUrlForwarderConfigurator(); - void OnDisconnectCallback(); - // Invoked when ConnectDesktopChannel() is called over IPC. void ConnectDesktopChannel(mojo::ScopedMessagePipeHandle desktop_pipe); @@ -233,6 +246,15 @@ // can be bound that will quit the current run loop. void QuitSetupRunLoop(); + // Sets whether desktop sessions will be persistent across client connections. + // See comments on `IpcDesktopEnvironment::persist_desktop_sessions_`. This + // is true by default. It is only safe to call this method when there are no + // active desktop sessions. + void SetPersistentDesktopSessions(bool persistent); + + // Returns the number of active desktop sessions + size_t ActiveDesktopSessionsCount() const; + base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment::MainThreadType::UI}; @@ -343,6 +365,10 @@ .Times(AnyNumber()) .WillRepeatedly( Invoke(this, &IpcDesktopEnvironmentTest::CreateDesktopSession)); + EXPECT_CALL(mock_desktop_session_manager_, ReconnectDesktopSession(_, _)) + .Times(AnyNumber()) + .WillRepeatedly( + Invoke(this, &IpcDesktopEnvironmentTest::ReconnectDesktopSession)); EXPECT_CALL(mock_desktop_session_manager_, CloseDesktopSession(_)) .Times(AnyNumber()) .WillRepeatedly( @@ -371,30 +397,14 @@ remote.BindNewEndpointAndPassReceiver()); desktop_environment_factory_ = std::make_unique<IpcDesktopEnvironmentFactory>( task_runner_, task_runner_, io_task_runner_, std::move(remote)); - base::test::TestFuture<std::unique_ptr<DesktopEnvironment>> - desktop_environment_future; - desktop_environment_factory_->Create( - client_session_control_factory_.GetWeakPtr(), - client_session_events_factory_.GetWeakPtr(), DesktopEnvironmentOptions(), - desktop_environment_future.GetCallback()); - desktop_environment_ = desktop_environment_future.Take(); - - screen_controls_ = desktop_environment_->CreateScreenControls(); - - // Create the input injector. - input_injector_ = desktop_environment_->CreateInputInjector(); - - // Create the screen capturer. - video_capturer_ = desktop_environment_->CreateVideoCapturer(0); - - desktop_environment_->SetCapabilities(std::string()); - - url_forwarder_configurator_ = - desktop_environment_->CreateUrlForwarderConfigurator(); - ResetRemoteUrlForwarderConfigurator(); + SetPersistentDesktopSessions(false); + CreateDesktopEnvironment(); } void IpcDesktopEnvironmentTest::TearDown() { + // Tests must ensure DestroyDesktopProcess() is called and + // `desktop_environment_factory_` is destroyed. Otherwise this will hang + // indefinitely. RunMainLoopUntilDone(); } @@ -407,15 +417,24 @@ CreateDesktopProcess(); } +void IpcDesktopEnvironmentTest::ReconnectDesktopSession( + int terminal_id, + mojom::DesktopSessionOptionsPtr options) { + EXPECT_EQ(terminal_id_, terminal_id); + + desktop_process_->ReconnectNetworkChannel(); +} + void IpcDesktopEnvironmentTest::CloseDesktopSession(int terminal_id) { EXPECT_EQ(terminal_id_, terminal_id); // The IPC desktop environment is fully destroyed now. Release the remaining // task runners. desktop_environment_factory_.reset(); + DestroyDesktopProcess(); } -void IpcDesktopEnvironmentTest::CreateDesktopEnvironment( +void IpcDesktopEnvironmentTest::OnCreateDesktopEnvironment( base::WeakPtr<ClientSessionControl>, base::WeakPtr<ClientSessionEvents>, const DesktopEnvironmentOptions&, @@ -463,6 +482,30 @@ return remote_input_injector; } +void IpcDesktopEnvironmentTest::CreateDesktopEnvironment() { + base::test::TestFuture<std::unique_ptr<DesktopEnvironment>> + desktop_environment_future; + desktop_environment_factory_->Create( + client_session_control_factory_.GetWeakPtr(), + client_session_events_factory_.GetWeakPtr(), DesktopEnvironmentOptions(), + desktop_environment_future.GetCallback()); + desktop_environment_ = desktop_environment_future.Take(); + + screen_controls_ = desktop_environment_->CreateScreenControls(); + + // Create the input injector. + input_injector_ = desktop_environment_->CreateInputInjector(); + + // Create the screen capturer. + video_capturer_ = desktop_environment_->CreateVideoCapturer(0); + + desktop_environment_->SetCapabilities(std::string()); + + url_forwarder_configurator_ = + desktop_environment_->CreateUrlForwarderConfigurator(); + ResetRemoteUrlForwarderConfigurator(); +} + void IpcDesktopEnvironmentTest::DeleteDesktopEnvironment() { input_injector_.reset(); screen_controls_.reset(); @@ -502,7 +545,7 @@ EXPECT_CALL(*desktop_environment_factory, Create(_, _, _, _)) .Times(AnyNumber()) .WillRepeatedly( - Invoke(this, &IpcDesktopEnvironmentTest::CreateDesktopEnvironment)); + Invoke(this, &IpcDesktopEnvironmentTest::OnCreateDesktopEnvironment)); EXPECT_CALL(*desktop_environment_factory, SupportsAudioCapture()) .Times(AnyNumber()) .WillRepeatedly(Return(false)); @@ -529,10 +572,6 @@ .WillByDefault(base::test::RunOnceCallbackRepeatedly<0>(false)); } -void IpcDesktopEnvironmentTest::OnDisconnectCallback() { - DeleteDesktopEnvironment(); -} - void IpcDesktopEnvironmentTest::ConnectDesktopChannel( mojo::ScopedMessagePipeHandle desktop_pipe) { // Instruct DesktopSessionProxy to connect to the network-to-desktop pipe. @@ -553,10 +592,18 @@ setup_run_loop_->Quit(); } +void IpcDesktopEnvironmentTest::SetPersistentDesktopSessions(bool persistent) { + desktop_environment_factory_->persist_desktop_sessions_ = persistent; +} + +size_t IpcDesktopEnvironmentTest::ActiveDesktopSessionsCount() const { + return desktop_environment_factory_->connections_.size(); +} + // Runs until the desktop is attached and exits immediately after that. -TEST_F(IpcDesktopEnvironmentTest, Basic) { - std::unique_ptr<protocol::MockClipboardStub> clipboard_stub( - new protocol::MockClipboardStub()); +TEST_F(IpcDesktopEnvironmentTest, BasicEphemeralDesktopSessions) { + SetPersistentDesktopSessions(false); + auto clipboard_stub = std::make_unique<protocol::MockClipboardStub>(); EXPECT_CALL(*clipboard_stub, InjectClipboardEvent(_)).Times(0); // Start the input injector and screen capturer. @@ -565,10 +612,48 @@ // Run the message loop until the desktop is attached. setup_run_loop_->Run(); - // Stop the test. + // Simulate client disconnection. When session is ephemeral, deletion of the + // desktop environment will close the desktop session, which triggers + // CloseDesktopSession() and destroys `desktop_environment_factory_` and the + // desktop process. If neither of these is true, then TearDown() will hang + // indefinitely. DeleteDesktopEnvironment(); } +TEST_F(IpcDesktopEnvironmentTest, PersistentDesktopSession) { + SetPersistentDesktopSessions(true); + auto clipboard_stub = std::make_unique<protocol::MockClipboardStub>(); + EXPECT_CALL(*clipboard_stub, InjectClipboardEvent(_)).Times(0); + input_injector_->Start(std::move(clipboard_stub)); + setup_run_loop_->Run(); + + base::test::TestFuture<void> on_network_process_disconnected; + desktop_process_->SetOnNetworkProcessDisconnectedCallbackForTesting( + on_network_process_disconnected.GetCallback()); + + // Simulate client disconnection. + DeleteDesktopEnvironment(); + on_network_process_disconnected.Get(); + + // Desktop session remains active. + ASSERT_EQ(ActiveDesktopSessionsCount(), 1u); + + base::test::TestFuture<void> on_desktop_agent_created; + desktop_process_->SetOnDesktopAgentCreatedCallbackForTesting( + on_desktop_agent_created.GetCallback()); + + // Simulate client reconnection. + CreateDesktopEnvironment(); + on_desktop_agent_created.Get(); + + ASSERT_EQ(ActiveDesktopSessionsCount(), 1u); + + // Without these, TearDown() will hang indefinitely. + DeleteDesktopEnvironment(); + DestroyDesktopProcess(); + desktop_environment_factory_.reset(); +} + // Check touchEvents capability is set when the desktop environment can // inject touch events. TEST_F(IpcDesktopEnvironmentTest, TouchEventsCapabilities) {
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc index 28ed7c3..85b5a58 100644 --- a/remoting/host/it2me/it2me_host.cc +++ b/remoting/host/it2me/it2me_host.cc
@@ -217,8 +217,7 @@ reconnect_params_->support_id); SignalingAddress signaling_address(reconnect_params_->client_ftl_address); - signal_strategy_->SendMessage(signaling_address, - SignalingMessage{crd_message}); + signal_strategy_->SendFtlMessage(signaling_address, std::move(crd_message)); } void It2MeHost::Connect(
diff --git a/remoting/host/linux/desktop_session_factory_linux.cc b/remoting/host/linux/desktop_session_factory_linux.cc index f7d08b18..cc9ad0d 100644 --- a/remoting/host/linux/desktop_session_factory_linux.cc +++ b/remoting/host/linux/desktop_session_factory_linux.cc
@@ -23,6 +23,7 @@ #include "base/sequence_checker.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" +#include "mojo/public/cpp/bindings/associated_remote.h" #include "remoting/base/auto_thread_task_runner.h" #include "remoting/base/logging.h" #include "remoting/host/base/switches.h" @@ -68,6 +69,8 @@ // DesktopSession implementation. void SetScreenResolution(const ScreenResolution& resolution) override; + void ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) override; // WorkerProcessIpcDelegate implementation. void OnChannelConnected(int32_t peer_pid) override; @@ -107,6 +110,8 @@ mojo::AssociatedReceiver<mojom::DesktopSessionRequestHandler> desktop_session_request_handler_ GUARDED_BY_CONTEXT(sequence_checker_){ this}; + mojo::AssociatedRemote<mojom::DesktopProcessControl> desktop_process_control_ + GUARDED_BY_CONTEXT(sequence_checker_); base::WeakPtrFactory<DesktopSessionLinux> weak_ptr_factory_{this}; }; @@ -186,6 +191,9 @@ std::make_unique<LinuxWorkerProcessLauncherDelegate>(std::move(options), io_task_runner_), this); + desktop_process_control_.reset(); + launcher_->GetRemoteAssociatedInterface( + desktop_process_control_.BindNewEndpointAndPassReceiver()); } void DesktopSessionFactoryLinux::DesktopSessionLinux::TerminateSession() { @@ -203,6 +211,24 @@ NOTIMPLEMENTED_LOG_ONCE(); } +void DesktopSessionFactoryLinux::DesktopSessionLinux::ReconnectNetworkChannel( + const mojom::DesktopSessionOptions& options) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (options.required_username != required_username_) { + LOG(ERROR) << "Required username has changed."; + TerminateSession(); + return; + } + + if (desktop_process_control_.is_bound()) { + desktop_process_control_->ReconnectNetworkChannel(); + } + // If `desktop_process_control_` is not bound, then it means the desktop + // process isn't launched yet. It will send the desktop pipe after it is + // launched anyway so we don't need to do anything. +} + void DesktopSessionFactoryLinux::DesktopSessionLinux::OnChannelConnected( int32_t peer_pid) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -218,6 +244,9 @@ } void DesktopSessionFactoryLinux::DesktopSessionLinux::OnWorkerProcessStopped() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + desktop_process_control_.reset(); } void DesktopSessionFactoryLinux::DesktopSessionLinux::
diff --git a/remoting/host/linux/ei_sender_session.cc b/remoting/host/linux/ei_sender_session.cc index a55c4ab..5976677c 100644 --- a/remoting/host/linux/ei_sender_session.cc +++ b/remoting/host/linux/ei_sender_session.cc
@@ -107,6 +107,13 @@ constexpr int kPixelsPerTick = 120; +// Some libei APIs, such as ei_device_get_name(), return a const char* but +// do not document that the returned pointer will always be non-null. This +// helper is useful for safely logging these strings. +const char* NullToLiteral(const char* ptr) { + return ptr ? ptr : "null"; +} + // This functionality is copied from fractional_input_filter.cc to maintain // an equivalent functionality for now. // TODO(rkjnsn): Once we are sure the client calculations indeed generate a 1.0 @@ -190,7 +197,7 @@ auto [first_equal, first_greater] = absolute_pointers_.equal_range(region_id); if (first_equal == first_greater) { - LOG(ERROR) << "No absolute pointer for the requested region"; + LOG(ERROR) << "No absolute pointer for the requested region: " << region_id; return; } @@ -403,6 +410,7 @@ } void EiSenderSession::OnSeatAdded(EiSeatPtr seat) { + HOST_LOG << "EI seat added: " << NullToLiteral(ei_seat_get_name(seat.get())); if (default_seat_.get()) { HOST_LOG << "Ignoring additional seat"; return; @@ -432,44 +440,59 @@ } void EiSenderSession::OnSeatRemoved(EiSeatPtr seat) { + HOST_LOG << "EI seat removed: " + << NullToLiteral(ei_seat_get_name(seat.get())); if (seat == default_seat_) { default_seat_.reset(); - LOG(WARNING) << "EIS seat removed"; + LOG(WARNING) << "Default seat removed"; } } void EiSenderSession::OnDeviceAdded(EiDevicePtr device) { + HOST_LOG << "EI device added: " + << NullToLiteral(ei_device_get_name(device.get())); AllocDeviceState(device); // The compositor might provide a device with multiple capabilities, in which // case it will be inserted in multiple lists. if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_KEYBOARD)) { - keyboards_.push_back( - std::make_tuple(device, std::make_unique<EiKeymap>(device))); + HOST_LOG << ".. adding to keyboard devices"; + keyboards_.emplace_back(device, std::make_unique<EiKeymap>(device)); std::get<1>(keyboards_.back()) ->Load(base::BindOnce(&EiSenderSession::OnKeymapLoaded, GetWeakPtr(), device)); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER)) { + HOST_LOG << ".. adding to relative pointers"; relative_pointers_.push_back({device}); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_BUTTON)) { + HOST_LOG << ".. adding to button devices"; button_devices_.push_back({device}); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_SCROLL)) { + HOST_LOG << ".. adding to scroll devices"; scroll_devices_.push_back({device}); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER_ABSOLUTE)) { + HOST_LOG << ".. adding to absolute pointers"; AddDeviceRegions(absolute_pointers_, {device}); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_TOUCH)) { + HOST_LOG << ".. adding to touch devices"; AddDeviceRegions(touch_devices_, {device}); } } void EiSenderSession::OnDeviceRemoved(EiDevicePtr device) { + HOST_LOG << "EI device removed: " + << NullToLiteral(ei_device_get_name(device.get())); if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_KEYBOARD)) { + HOST_LOG << ".. removing from keyboard devices"; bool is_current = (!keyboards_.empty() && std::get<0>(keyboards_.back()) == device); + if (is_current) { + LOG(WARNING) << "The current keyboard device was removed."; + } std::erase_if(keyboards_, [&device](auto& item) { return std::get<0>(item) == device; }); @@ -479,10 +502,15 @@ } } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER)) { + HOST_LOG << ".. removing from relative pointers"; + if (!relative_pointers_.empty() && relative_pointers_.front() == device) { + LOG(WARNING) << "The current relative pointer was removed."; + } std::erase_if(relative_pointers_, [&device](auto& item) { return item == device; }); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_BUTTON)) { + HOST_LOG << ".. removing from button devices"; if (!button_devices_.empty() && button_devices_.front() == device) { LOG(WARNING) << "The first button device was removed. This may cause " "issues with button or scroll injection."; @@ -491,15 +519,18 @@ [&device](auto& item) { return item == device; }); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_SCROLL)) { + HOST_LOG << ".. removing from scroll devices"; std::erase_if(scroll_devices_, [&device](auto& item) { return item == device; }); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER_ABSOLUTE)) { + HOST_LOG << ".. removing from absolute pointers"; std::erase_if(absolute_pointers_, [&device](auto& item) { return item.second.second == device; }); } if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_TOUCH)) { + HOST_LOG << ".. removing from touch devices"; std::erase_if(touch_devices_, [&device](auto& item) { return item.second.second == device; }); @@ -508,10 +539,14 @@ } void EiSenderSession::OnDevicePaused(EiDevicePtr device) { + HOST_LOG << "EI device paused: " + << NullToLiteral(ei_device_get_name(device.get())); GetDeviceState(device).resumed = false; } void EiSenderSession::OnDeviceResumed(EiDevicePtr device) { + HOST_LOG << "EI device resumed: " + << NullToLiteral(ei_device_get_name(device.get())); GetDeviceState(device).resumed = true; // TODO(rkjnsn): Only call this on devices we expect to use. // TODO(rkjnsn): In the future, we'll want the host to keep the session open @@ -588,6 +623,8 @@ std::pair<EiRegionPtr, EiDevicePtr>, std::less<>>& map, EiDevicePtr device) { + HOST_LOG << "Adding regions from device: " + << NullToLiteral(ei_device_get_name(device.get())); for (size_t i = 0; ei_region* region = ei_device_get_region(device.get(), i); ++i) { const char* mapping_id = ei_region_get_mapping_id(region); @@ -595,9 +632,12 @@ // InjectAbsolutePointerMove(). std::string_view mapping_id_view = mapping_id ? mapping_id : std::string_view{}; - if (mapping_id_view.empty()) { - HOST_LOG << "Region found without mapping id"; - } + HOST_LOG << " region " << i << " '" << mapping_id_view + << "' x=" << ei_region_get_x(region) + << " y=" << ei_region_get_y(region) + << " w=" << ei_region_get_width(region) + << " h=" << ei_region_get_height(region) + << " scale=" << ei_region_get_physical_scale(region); map.emplace(std::piecewise_construct, std::tuple(mapping_id_view), std::forward_as_tuple(EiRegionPtr::Ref(region), device)); }
diff --git a/remoting/host/linux/user_systemd_env.cc b/remoting/host/linux/user_systemd_env.cc index bf3c677..6a73485 100644 --- a/remoting/host/linux/user_systemd_env.cc +++ b/remoting/host/linux/user_systemd_env.cc
@@ -18,6 +18,7 @@ #include "base/json/json_writer.h" #include "base/logging.h" #include "base/run_loop.h" +#include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_executor.h" @@ -113,13 +114,12 @@ base::EnvironmentMap env_map; for (const std::string& env_str : result.value()) { - // Can't use SplitString since some environment variables have `=`. - auto eq_pos = env_str.find('='); - if (eq_pos == std::string::npos || eq_pos == 0) { + auto split_result = base::SplitStringOnce(env_str, '='); + if (!split_result.has_value() || split_result->first.empty()) { LOG(WARNING) << "Invalid line in systemctl output: " << env_str; continue; } - env_map[env_str.substr(0, eq_pos)] = env_str.substr(eq_pos + 1); + env_map[std::string(split_result->first)] = split_result->second; } // The compositor isn't immediately ready after the session is created, so @@ -185,7 +185,7 @@ return EXIT_FAILURE; } if (setuid(user_info->uid) != 0) { - PLOG(ERROR) << "Failed to setuid" << user_info->uid; + PLOG(ERROR) << "Failed to setuid: " << user_info->uid; return EXIT_FAILURE; }
diff --git a/remoting/host/mojom/desktop_session.mojom b/remoting/host/mojom/desktop_session.mojom index a4749f0b..a7ba32b 100644 --- a/remoting/host/mojom/desktop_session.mojom +++ b/remoting/host/mojom/desktop_session.mojom
@@ -253,12 +253,21 @@ // and the receiver is bound in the high-privilege daemon process. interface DesktopSessionManager { // Creates a chromoting desktop environment for the session identified by - // |terminal_id|. This is done by injecting a desktop agent process and + // `terminal_id`. This is done by injecting a desktop agent process and // establishing IPC channels between the daemon, desktop, and network // processes. CreateDesktopSession(int32 terminal_id, DesktopSessionOptions options); - // Closes the desktop session identified by |terminal_id|. This involves + // Request the desktop process to reconnect the network channel, i.e. create + // a new DesktopSessionAgent and pass a new desktop pipe to the network + // process via the daemon process. `terminal_id` must be the terminal ID of + // a previously created desktop session that has been disconnected from the + // network process, but not closed by calling CloseDesktopSession(). The + // desktop process will look at `options` and may terminate itself if the + // session is deemed not reconnectable, e.g. `required_username` has changed. + ReconnectDesktopSession(int32 terminal_id, DesktopSessionOptions options); + + // Closes the desktop session identified by `terminal_id`. This involves // terminating the desktop agent process and removing any references to the // session being closed in the daemon and network processes. Not changes are // made at an OS level, meaning the user can reconnect to the same session @@ -266,7 +275,7 @@ CloseDesktopSession(int32 terminal_id); // Sends the updated screen resolution to the desktop agent running in the - // |terminal_id| session. + // `terminal_id` session. SetScreenResolution(int32 terminal_id, ScreenResolution screen_resolution); }; @@ -674,3 +683,12 @@ // Instructs the receiving process to crash itself. CrashProcess(string function_name, string file_name, int32 line_number); }; + +// Allows the high-privilege daemon process to control the high-privilege +// desktop process. +interface DesktopProcessControl { + // Request the desktop process to reconnect the network channel, i.e. create + // a new DesktopSessionAgent and pass a new desktop pipe to the network + // process via the daemon process. + ReconnectNetworkChannel(); +};
diff --git a/remoting/host/setup/BUILD.gn b/remoting/host/setup/BUILD.gn index 172281fa..b786d295 100644 --- a/remoting/host/setup/BUILD.gn +++ b/remoting/host/setup/BUILD.gn
@@ -142,7 +142,6 @@ "//remoting/proto/google/remoting/cloud/v1:messages", "//services/network:network_service", "//services/network/public/cpp", - "//third_party/libjingle_xmpp:rtc_xmllite", "//third_party/webrtc_overrides:webrtc_component", ]
diff --git a/remoting/protocol/DEPS b/remoting/protocol/DEPS index c6e19c2..0db2249 100644 --- a/remoting/protocol/DEPS +++ b/remoting/protocol/DEPS
@@ -8,8 +8,6 @@ "+remoting/codec", "+remoting/signaling", "+third_party/boringssl/src/include/openssl", - "+third_party/grpc", - "+third_party/libjingle_xmpp/xmpp", "+third_party/protobuf/src", "+third_party/webrtc", "+third_party/webrtc_overrides",
diff --git a/remoting/protocol/jingle_session.cc b/remoting/protocol/jingle_session.cc index 630afe5a..586baaa0 100644 --- a/remoting/protocol/jingle_session.cc +++ b/remoting/protocol/jingle_session.cc
@@ -36,7 +36,6 @@ #include "remoting/signaling/jingle_data_structures.h" #include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/session_config.h" -#include "remoting/signaling/xmpp_constants.h" #include "third_party/webrtc/api/candidate.h" namespace remoting::protocol {
diff --git a/remoting/protocol/jingle_session_manager.cc b/remoting/protocol/jingle_session_manager.cc index c79316a..2d3e357c 100644 --- a/remoting/protocol/jingle_session_manager.cc +++ b/remoting/protocol/jingle_session_manager.cc
@@ -19,7 +19,6 @@ #include "remoting/signaling/jingle_data_structures.h" #include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/signal_strategy.h" -#include "remoting/signaling/xmpp_constants.h" #include "third_party/webrtc/rtc_base/socket_address.h" namespace remoting::protocol {
diff --git a/remoting/protocol/jingle_session_unittest.cc b/remoting/protocol/jingle_session_unittest.cc index 8172e0f5..40d8e31 100644 --- a/remoting/protocol/jingle_session_unittest.cc +++ b/remoting/protocol/jingle_session_unittest.cc
@@ -39,7 +39,6 @@ #include "remoting/signaling/fake_signal_strategy.h" #include "remoting/signaling/jingle_data_structures.h" #include "remoting/signaling/jingle_message_xml_converter.h" -#include "remoting/signaling/xmpp_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/remoting/signaling/BUILD.gn b/remoting/signaling/BUILD.gn index ea761ce5..7f9e8e93 100644 --- a/remoting/signaling/BUILD.gn +++ b/remoting/signaling/BUILD.gn
@@ -42,7 +42,6 @@ "message_channel_strategy.h", "message_tracker.cc", "message_tracker.h", - "messaging_client.h", "registration_manager.h", "session_config.cc", "session_config.h",
diff --git a/remoting/signaling/DEPS b/remoting/signaling/DEPS index c3ca560..cc153e0 100644 --- a/remoting/signaling/DEPS +++ b/remoting/signaling/DEPS
@@ -1,12 +1,9 @@ include_rules = [ "+google_apis", "+net", - "+jingle", "+services/network/proxy_resolving_client_socket.h", "+services/network/proxy_resolving_client_socket_factory.h", "+services/network/public/cpp", - "+third_party/grpc", "+third_party/webrtc/rtc_base", "+third_party/libjingle_xmpp/xmllite", - "+third_party/libjingle_xmpp/xmpp", ]
diff --git a/remoting/signaling/corp_messaging_client.cc b/remoting/signaling/corp_messaging_client.cc index b442fcf1..22509fb 100644 --- a/remoting/signaling/corp_messaging_client.cc +++ b/remoting/signaling/corp_messaging_client.cc
@@ -12,6 +12,7 @@ #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/logging.h" +#include "base/notreached.h" #include "base/time/time.h" #include "base/uuid.h" #include "net/traffic_annotation/network_traffic_annotation.h" @@ -24,7 +25,9 @@ #include "remoting/base/protobuf_http_stream_request.h" #include "remoting/base/service_urls.h" #include "remoting/signaling/corp_message_channel_strategy.h" +#include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/message_channel.h" +#include "remoting/signaling/signal_strategy.h" #include "remoting/signaling/signaling_address.h" #include "services/network/public/cpp/shared_url_loader_factory.h" @@ -120,24 +123,24 @@ std::move(channel_strategy), /*signaling_tracker=*/nullptr); } +CorpMessagingClient::CorpMessagingClient() = default; + CorpMessagingClient::~CorpMessagingClient() = default; base::CallbackListSubscription CorpMessagingClient::RegisterMessageCallback( const MessageCallback& callback) { - return callback_list_.Add(callback); + return message_callback_list_.Add(callback); } void CorpMessagingClient::SendMessage( const SignalingAddress& destination_address, - SignalingMessage&& message, + internal::PeerMessageStruct&& message, DoneCallback on_done) { internal::HostSendMessageRequestStruct request; CHECK_EQ(destination_address.channel(), SignalingAddress::Channel::CORP); request.messaging_authz_token = destination_address.id(); - CHECK(std::holds_alternative<internal::PeerMessageStruct>(message)); - request.peer_message = - std::get<internal::PeerMessageStruct>(std::move(message)); + request.peer_message = std::move(message); LOG_IF(WARNING, !request.peer_message.message_id.empty()) << "Overwriting existing message_id value: " << request.peer_message.message_id; @@ -238,15 +241,13 @@ void CorpMessagingClient::OnMessageReceived( const internal::PeerMessageStruct& message) { - LOG_IF(WARNING, callback_list_.empty()) - << "No listener registered to receive signaling message."; - const auto* iq_stanza = std::get_if<internal::IqStanzaStruct>(&message.payload); - callback_list_.Notify(iq_stanza - ? SignalingAddress(iq_stanza->messaging_authz_token) - : SignalingAddress(), - message); + SignalingAddress sender_address = + iq_stanza ? SignalingAddress(iq_stanza->messaging_authz_token) + : SignalingAddress(); + + message_callback_list_.Notify(sender_address, message); } } // namespace remoting
diff --git a/remoting/signaling/corp_messaging_client.h b/remoting/signaling/corp_messaging_client.h index b5513a0..d2b84ee 100644 --- a/remoting/signaling/corp_messaging_client.h +++ b/remoting/signaling/corp_messaging_client.h
@@ -14,7 +14,6 @@ #include "base/memory/scoped_refptr.h" #include "remoting/base/internal_headers.h" #include "remoting/signaling/corp_message_channel_strategy.h" -#include "remoting/signaling/messaging_client.h" namespace google::protobuf { class MessageLite; @@ -36,10 +35,18 @@ class MessageChannel; class ProtobufHttpClient; class ScopedProtobufHttpRequest; +class SignalingAddress; // A class for sending and receiving messages via the Corp messaging API. -class CorpMessagingClient final : public MessagingClient { +class CorpMessagingClient { public: + using MessageCallback = + base::RepeatingCallback<void(const SignalingAddress& sender_address, + const internal::PeerMessageStruct& message)>; + using MessageCallbackList = + base::RepeatingCallbackList<void(const SignalingAddress&, + const internal::PeerMessageStruct&)>; + using DoneCallback = base::OnceCallback<void(const HttpStatus& status)>; using SignalingAddressChangedCallback = CorpMessageChannelStrategy::SignalingAddressChangedCallback; @@ -53,18 +60,38 @@ CorpMessagingClient(const CorpMessagingClient&) = delete; CorpMessagingClient& operator=(const CorpMessagingClient&) = delete; - ~CorpMessagingClient() override; + virtual ~CorpMessagingClient(); - // MessagingClient overrides. - base::CallbackListSubscription RegisterMessageCallback( - const MessageCallback& callback) override; - void SendMessage(const SignalingAddress& destination_address, - SignalingMessage&& message, - DoneCallback on_done) override; - void StartReceivingMessages(base::OnceClosure on_ready, - DoneCallback on_closed) override; - void StopReceivingMessages() override; - bool IsReceivingMessages() const override; + // Registers a callback which is run for each new message received. Simply + // delete the returned subscription object to unregister. The subscription + // object must be deleted before |this| is deleted. + virtual base::CallbackListSubscription RegisterMessageCallback( + const MessageCallback& callback); + + // Sends |message| to |destination_address| and then calls |on_done| with the + // result of the operation. + virtual void SendMessage(const SignalingAddress& destination_address, + internal::PeerMessageStruct&& message, + DoneCallback on_done); + + // Opens a stream to continuously receive new messages from the server and + // calls the registered MessageCallback once a new message is received. + // |on_ready| is called once the stream is successfully started. + // |on_closed| is called if the stream fails to start, in which case + // |on_ready| will not be called, or when the stream is closed or dropped, + // in which case it is called after |on_ready| is called. + virtual void StartReceivingMessages(base::OnceClosure on_ready, + DoneCallback on_closed); + + // Stops the stream for continuously receiving new messages. Note that + // |on_closed| callback will be silently dropped. + virtual void StopReceivingMessages(); + + // Returns true if the streaming channel is open. + virtual bool IsReceivingMessages() const; + + protected: + CorpMessagingClient(); private: template <typename CallbackFunctor> @@ -91,7 +118,7 @@ std::string public_key_; std::unique_ptr<ProtobufHttpClient> client_; std::unique_ptr<MessageChannel> message_channel_; - MessageCallbackList callback_list_; + MessageCallbackList message_callback_list_; }; } // namespace remoting
diff --git a/remoting/signaling/corp_messaging_client_unittest.cc b/remoting/signaling/corp_messaging_client_unittest.cc index 8ee13e1..0d0a4b2 100644 --- a/remoting/signaling/corp_messaging_client_unittest.cc +++ b/remoting/signaling/corp_messaging_client_unittest.cc
@@ -44,7 +44,7 @@ constexpr char kFakeAuthzTokenBase64[] = "ZmFrZV9hdXRoel90b2tlbg=="; constexpr char kFakePublicKey[] = "fake_public_key"; -using DoneCallback = MessagingClient::DoneCallback; +using DoneCallback = CorpMessagingClient::DoneCallback; base::OnceCallback<void(const HttpStatus&)> CheckStatusThenQuitRunLoopCallback( const base::Location& from_here, @@ -76,8 +76,7 @@ internal::PeerMessageStruct peer_message; messaging_client_.SendMessage( - SignalingAddress{kFakeAuthzTokenBase64}, - SignalingMessage(std::move(peer_message)), + SignalingAddress{kFakeAuthzTokenBase64}, std::move(peer_message), CheckStatusThenQuitRunLoopCallback( FROM_HERE, HttpStatus::Code::UNAUTHENTICATED, &run_loop)); test_responder_.AddErrorToMostRecentRequestUrl( @@ -85,12 +84,11 @@ run_loop.Run(); } -TEST_F(CorpMessagingClientTest, TestSendMessage_SendOneMessage) { +TEST_F(CorpMessagingClientTest, TestSendMessage_SendOnePeerMessage) { base::RunLoop run_loop; internal::PeerMessageStruct peer_message; messaging_client_.SendMessage( - SignalingAddress{kFakeAuthzTokenBase64}, - SignalingMessage(std::move(peer_message)), + SignalingAddress{kFakeAuthzTokenBase64}, std::move(peer_message), CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK, &run_loop)); @@ -106,6 +104,29 @@ run_loop.Run(); } +TEST_F(CorpMessagingClientTest, TestSendMessage_SendOneJingleMessage) { + base::RunLoop run_loop; + internal::PeerMessageStruct peer_message; + internal::IqStanzaStruct iq_stanza; + iq_stanza.xml = "<iq>test</iq>"; + peer_message.payload = std::move(iq_stanza); + + messaging_client_.SendMessage( + SignalingAddress{kFakeAuthzTokenBase64}, std::move(peer_message), + CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK, + &run_loop)); + + HostSendMessageRequest request; + ASSERT_TRUE(test_responder_.GetMostRecentRequestMessage(&request)); +#if BUILDFLAG(REMOTING_INTERNAL) + // Verify that the Jingle message was wrapped in a PeerMessage. + ASSERT_TRUE(request.has_peer_message()); +#endif + + test_responder_.AddResponseToMostRecentRequestUrl(HostSendMessageResponse()); + run_loop.Run(); +} + // Since the internals of the various messaging protos are not available in the // public builds, complicated tests which rely on them are not worth running // in that context.
diff --git a/remoting/signaling/corp_signal_strategy.cc b/remoting/signaling/corp_signal_strategy.cc index 401a2aed..fe00f59 100644 --- a/remoting/signaling/corp_signal_strategy.cc +++ b/remoting/signaling/corp_signal_strategy.cc
@@ -21,11 +21,8 @@ #include "remoting/base/logging.h" #include "remoting/base/rsa_key_pair.h" #include "remoting/signaling/corp_messaging_client.h" -#include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/signaling_address.h" -#include "remoting/signaling/xmpp_constants.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -36,7 +33,7 @@ const std::string& username, scoped_refptr<RsaKeyPair> key_pair); // CorpSignalStrategyTest uses a private c'tor w/ a fake messaging client. - Core(std::unique_ptr<MessagingClient> messaging_client, + Core(std::unique_ptr<CorpMessagingClient> messaging_client, const SignalingAddress& local_address); Core(const Core&) = delete; @@ -58,15 +55,13 @@ private: void OnIncomingMessage(const SignalingAddress& sender_address, - const SignalingMessage& message); + const internal::PeerMessageStruct& message); void OnChannelReady(); void OnSignalingAddressChanged(const SignalingAddress& address); void OnChannelClosed(const HttpStatus& status); void SetState(State state); - void OnStanza(const SignalingAddress& sender_address, - std::unique_ptr<jingle_xmpp::XmlElement> stanza); - std::unique_ptr<MessagingClient> messaging_client_; + std::unique_ptr<CorpMessagingClient> messaging_client_; base::ObserverList<Listener, true> listeners_; State state_ = DISCONNECTED; @@ -96,7 +91,7 @@ } CorpSignalStrategy::Core::Core( - std::unique_ptr<MessagingClient> messaging_client, + std::unique_ptr<CorpMessagingClient> messaging_client, const SignalingAddress& local_address) : messaging_client_(std::move(messaging_client)), local_address_(local_address) {} @@ -165,41 +160,6 @@ return false; } - // TODO: joedow - Use std::visit(absl::Overload(...), message->payload()). - std::unique_ptr<jingle_xmpp::XmlElement> stanza; - if (auto* jingle_message = std::get_if<JingleMessage>(&message)) { - if (destination_address.empty()) { - LOG(ERROR) << "Invalid destination address."; - return false; - } - - jingle_message->from = GetLocalAddress(); - - stanza = JingleMessageToXml(*jingle_message); - } else if (auto* jingle_reply = std::get_if<JingleMessageReply>(&message)) { - if (destination_address.empty()) { - LOG(ERROR) << "Invalid destination address."; - return false; - } - - jingle_reply->from = GetLocalAddress(); - - stanza = JingleMessageReplyToXml(*jingle_reply); - } - if (stanza) { - internal::PeerMessageStruct peer_message; - internal::IqStanzaStruct iq_stanza; - iq_stanza.xml = stanza->Str(); - peer_message.payload = std::move(iq_stanza); - - return SendMessage(destination_address, std::move(peer_message)); - } - - auto* peer_message = std::get_if<internal::PeerMessageStruct>(&message); - if (!peer_message) { - LOG(ERROR) << "Tried to send a non-corp message with CorpSignalStrategy."; - return false; - } if (messaging_authz_token_.empty()) { LOG(ERROR) << "Missing authz token."; return false; @@ -212,8 +172,23 @@ << ", message: " << status.error_message(); } }); + + internal::IqStanzaStruct iq_stanza; + if (auto* jingle_message = std::get_if<JingleMessage>(&message)) { + jingle_message->to = destination_address; + iq_stanza.xml = jingle_message->ToSerializedXml(); + } else if (auto* jingle_reply = std::get_if<JingleMessageReply>(&message)) { + jingle_reply->to = destination_address; + iq_stanza.xml = jingle_reply->ToSerializedXml(); + } else { + NOTREACHED() << "Unsupported message type."; + } + + internal::PeerMessageStruct peer_message; + peer_message.payload = std::move(iq_stanza); + messaging_client_->SendMessage(SignalingAddress(messaging_authz_token_), - std::move(message), std::move(on_done)); + std::move(peer_message), std::move(on_done)); return true; } @@ -229,19 +204,13 @@ void CorpSignalStrategy::Core::OnIncomingMessage( const SignalingAddress& sender_address, - const SignalingMessage& message) { + const internal::PeerMessageStruct& message) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); HOST_LOG << "Received incoming message from " << sender_address.id(); - const auto* peer_message = std::get_if<internal::PeerMessageStruct>(&message); - if (!peer_message) { - LOG(WARNING) << "Received message with unsupported payload type."; - return; - } - const auto* iq_stanza_struct = - std::get_if<internal::IqStanzaStruct>(&peer_message->payload); + std::get_if<internal::IqStanzaStruct>(&message.payload); if (!iq_stanza_struct) { LOG(WARNING) << "Received PeerMessageStruct with non-IqStanza payload."; return; @@ -324,7 +293,7 @@ } CorpSignalStrategy::CorpSignalStrategy( - std::unique_ptr<MessagingClient> messaging_client, + std::unique_ptr<CorpMessagingClient> messaging_client, const SignalingAddress& local_address) { core_ = std::make_unique<Core>(std::move(messaging_client), local_address); }
diff --git a/remoting/signaling/corp_signal_strategy.h b/remoting/signaling/corp_signal_strategy.h index bdf2738..58bfa1b7 100644 --- a/remoting/signaling/corp_signal_strategy.h +++ b/remoting/signaling/corp_signal_strategy.h
@@ -22,7 +22,7 @@ namespace remoting { -class MessagingClient; +class CorpMessagingClient; class RsaKeyPair; // CorpSignalStrategy implements SignalStrategy using the Corp messaging service @@ -58,7 +58,7 @@ private: // CorpSignalStrategyTest uses the private c'tor w/ a fake messaging client. friend class CorpSignalStrategyTest; - CorpSignalStrategy(std::unique_ptr<MessagingClient> messaging_client, + CorpSignalStrategy(std::unique_ptr<CorpMessagingClient> messaging_client, const SignalingAddress& local_address); class Core;
diff --git a/remoting/signaling/corp_signal_strategy_unittest.cc b/remoting/signaling/corp_signal_strategy_unittest.cc index 8f7d50e..59ee39d 100644 --- a/remoting/signaling/corp_signal_strategy_unittest.cc +++ b/remoting/signaling/corp_signal_strategy_unittest.cc
@@ -9,21 +9,16 @@ #include <vector> #include "base/functional/callback.h" -#include "base/functional/callback_helpers.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/test/task_environment.h" -#include "net/ssl/client_cert_store.h" -#include "net/ssl/ssl_cert_request_info.h" #include "remoting/base/http_status.h" +#include "remoting/base/internal_headers.h" #include "remoting/base/protobuf_http_test_responder.h" -#include "remoting/base/rsa_key_pair.h" -#include "remoting/proto/messaging_service.h" +#include "remoting/signaling/corp_messaging_client.h" #include "remoting/signaling/jingle_message_xml_converter.h" -#include "remoting/signaling/messaging_client.h" #include "remoting/signaling/signaling_address.h" #include "remoting/signaling/xmpp_constants.h" -#include "services/network/public/cpp/shared_url_loader_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" @@ -33,8 +28,6 @@ namespace { using testing::_; -using testing::ByMove; -using testing::Mock; using testing::Return; constexpr char kFakeLocalCorpId[] = "fake_local_user@domain.com"; @@ -68,35 +61,38 @@ return stanza; } -class FakeMessagingClient : public MessagingClient { +class FakeMessagingClient : public CorpMessagingClient { public: FakeMessagingClient() = default; ~FakeMessagingClient() override = default; - // MessagingClient implementation. + // CorpMessagingClient implementation. base::CallbackListSubscription RegisterMessageCallback( - const MessageCallback& callback) override { + const CorpMessagingClient::MessageCallback& callback) override { return callback_list_.Add(callback); } MOCK_METHOD(void, SendMessage, - (const SignalingAddress&, SignalingMessage&&, DoneCallback), + (const SignalingAddress&, + internal::PeerMessageStruct&&, + CorpMessagingClient::DoneCallback), (override)); MOCK_METHOD(void, StartReceivingMessages, - (base::OnceClosure on_ready, DoneCallback on_closed), + (base::OnceClosure on_ready, + CorpMessagingClient::DoneCallback on_closed), (override)); MOCK_METHOD(void, StopReceivingMessages, (), (override)); MOCK_METHOD(bool, IsReceivingMessages, (), (const, override)); void OnMessage(const SignalingAddress& sender_address, - const SignalingMessage& message) { + const internal::PeerMessageStruct& message) { callback_list_.Notify(sender_address, message); } private: - MessageCallbackList callback_list_; + CorpMessagingClient::MessageCallbackList callback_list_; }; } // namespace @@ -163,7 +159,7 @@ EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); return testing::Return(); }); @@ -193,7 +189,7 @@ EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_closed).Run( HttpStatus(HttpStatus::Code::UNAUTHENTICATED, "unauthenticated")); }); @@ -217,7 +213,7 @@ EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_closed).Run( HttpStatus(HttpStatus::Code::UNAVAILABLE, "unavailable")); }); @@ -236,7 +232,7 @@ TEST_F(CorpSignalStrategyTest, SendMessage_XmlElement_Success) { EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); return testing::Return(); }); @@ -246,10 +242,9 @@ internal::IqStanzaStruct iq_stanza_struct; iq_stanza_struct.xml = CreateXmlStanza(Direction::INCOMING, "id1")->Str(); iq_stanza_struct.messaging_authz_token = "faux_messaging_token"; - internal::PeerMessageStruct peer_message; - peer_message.payload = std::move(iq_stanza_struct); - messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), - SignalingMessage(peer_message)); + internal::PeerMessageStruct message; + message.payload = std::move(iq_stanza_struct); + messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), message); auto stanza = CreateXmlStanza(Direction::OUTGOING, signal_strategy_->GetNextId()); @@ -260,13 +255,12 @@ ASSERT_TRUE(JingleMessageFromXml(stanza.get(), &jingle_message, &error)); EXPECT_CALL(*messaging_client_, SendMessage(_, _, _)) - .WillOnce([&](const SignalingAddress& address, SignalingMessage&& message, - MessagingClient::DoneCallback on_done) { + .WillOnce([&](const SignalingAddress& address, + internal::PeerMessageStruct&& message, + CorpMessagingClient::DoneCallback on_done) { EXPECT_EQ("faux_messaging_token", address.id()); - auto* peer_message = std::get_if<internal::PeerMessageStruct>(&message); - ASSERT_TRUE(peer_message); auto* iq_stanza = - std::get_if<internal::IqStanzaStruct>(&peer_message->payload); + std::get_if<internal::IqStanzaStruct>(&message.payload); ASSERT_TRUE(iq_stanza); EXPECT_THAT(iq_stanza->xml, testing::HasSubstr("to=\"fake_remote_user@domain.com\"")); @@ -280,14 +274,14 @@ } TEST_F(CorpSignalStrategyTest, SendMessage_XmlElement_NotConnected) { + EXPECT_CALL(*messaging_client_, SendMessage(_, _, _)).Times(0); auto stanza = CreateXmlStanza(Direction::OUTGOING, signal_strategy_->GetNextId()); - JingleMessage jingle_message; std::string error; ASSERT_TRUE(JingleMessageFromXml(stanza.get(), &jingle_message, &error)); - EXPECT_FALSE(signal_strategy_->SendMessage( + ASSERT_FALSE(signal_strategy_->SendMessage( SignalingAddress(kFakeRemoteCorpId), SignalingMessage(std::move(jingle_message)))); } @@ -295,7 +289,7 @@ TEST_F(CorpSignalStrategyTest, ReceiveStanza_Success) { EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); return testing::Return(); }); @@ -303,17 +297,15 @@ auto stanza = CreateXmlStanza(Direction::INCOMING, signal_strategy_->GetNextId()); - std::string stanza_string = stanza->Str(); internal::IqStanzaStruct iq_stanza_struct; - iq_stanza_struct.xml = stanza_string; + iq_stanza_struct.xml = stanza->Str(); iq_stanza_struct.messaging_authz_token = "fake_authz_token"; - internal::PeerMessageStruct peer_message; - peer_message.payload = std::move(iq_stanza_struct); + internal::PeerMessageStruct message; + message.payload = std::move(iq_stanza_struct); - messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), - SignalingMessage(peer_message)); + messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), message); ASSERT_EQ(1u, received_messages_.size()); // The attribute order may change during XML conversion. @@ -327,7 +319,7 @@ TEST_F(CorpSignalStrategyTest, ReceiveStanza_MalformedXmpp) { EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); return testing::Return(); }); @@ -336,11 +328,10 @@ internal::IqStanzaStruct iq_stanza_struct; iq_stanza_struct.xml = "Malformed!!!"; - internal::PeerMessageStruct peer_message; - peer_message.payload = std::move(iq_stanza_struct); + internal::PeerMessageStruct message; + message.payload = std::move(iq_stanza_struct); - messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), - SignalingMessage(peer_message)); + messaging_client_->OnMessage(SignalingAddress(kFakeRemoteCorpId), message); ASSERT_EQ(0u, received_messages_.size()); } @@ -348,7 +339,7 @@ TEST_F(CorpSignalStrategyTest, LocalAddressPreservedAfterDisconnect) { EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); return testing::Return(); }); @@ -363,10 +354,10 @@ } TEST_F(CorpSignalStrategyTest, LocalAddressPreservedAfterChannelError) { - MessagingClient::DoneCallback on_closed_callback; + CorpMessagingClient::DoneCallback on_closed_callback; EXPECT_CALL(*messaging_client_, StartReceivingMessages(_, _)) .WillOnce([&](base::OnceClosure on_ready, - MessagingClient::DoneCallback on_closed) { + CorpMessagingClient::DoneCallback on_closed) { std::move(on_ready).Run(); on_closed_callback = std::move(on_closed); });
diff --git a/remoting/signaling/fake_signal_strategy.cc b/remoting/signaling/fake_signal_strategy.cc index 52c1b071..2ad7527 100644 --- a/remoting/signaling/fake_signal_strategy.cc +++ b/remoting/signaling/fake_signal_strategy.cc
@@ -15,8 +15,6 @@ #include "base/task/single_thread_task_runner.h" #include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/signaling_id_util.h" -#include "remoting/signaling/xmpp_constants.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -204,27 +202,6 @@ SignalingAddress from; SignalingAddress to; - std::optional<std::string> xml; - if (const auto* ftl = - std::get_if<ftl::ChromotingMessage>(&message_to_dispatch)) { - if (ftl->has_xmpp() && ftl->xmpp().has_stanza()) { - xml = ftl->xmpp().stanza(); - } - } else if (const auto* corp = std::get_if<internal::PeerMessageStruct>( - &message_to_dispatch)) { - if (const auto* iq = - std::get_if<internal::IqStanzaStruct>(&corp->payload)) { - xml = iq->xml; - } - } - - if (xml) { - auto parsed_message = SignalStrategy::ParseStanzaXml(*xml); - if (parsed_message) { - message_to_dispatch = std::move(*parsed_message); - } - } - if (const auto* jm = std::get_if<JingleMessage>(&message_to_dispatch)) { from = jm->from; to = jm->to;
diff --git a/remoting/signaling/ftl_messaging_client.cc b/remoting/signaling/ftl_messaging_client.cc index a820e0e2..e7fe6d4 100644 --- a/remoting/signaling/ftl_messaging_client.cc +++ b/remoting/signaling/ftl_messaging_client.cc
@@ -208,7 +208,7 @@ void FtlMessagingClient::SendMessage( const SignalingAddress& destination_address, - SignalingMessage&& message, + ftl::ChromotingMessage&& message, DoneCallback on_done) { std::string user_email; std::string registration_id; @@ -217,8 +217,6 @@ << destination_address.id(); return; } - CHECK(std::holds_alternative<ftl::ChromotingMessage>(message)); - auto chromoting_message = std::get<ftl::ChromotingMessage>(message); auto request = std::make_unique<ftl::InboxSendRequest>(); *request->mutable_header() = FtlServicesContext::CreateRequestHeader( @@ -228,7 +226,7 @@ FtlServicesContext::CreateIdFromString(user_email); std::string serialized_message; - bool succeeded = chromoting_message.SerializeToString(&serialized_message); + bool succeeded = message.SerializeToString(&serialized_message); DCHECK(succeeded); request->mutable_message()->set_message(serialized_message);
diff --git a/remoting/signaling/ftl_messaging_client.h b/remoting/signaling/ftl_messaging_client.h index 2395ab0..a310b76 100644 --- a/remoting/signaling/ftl_messaging_client.h +++ b/remoting/signaling/ftl_messaging_client.h
@@ -8,11 +8,14 @@ #include <memory> #include <string> +#include "base/callback_list.h" +#include "base/functional/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "remoting/base/http_status.h" +#include "remoting/proto/ftl/v1/chromoting_message.pb.h" #include "remoting/proto/ftl/v1/ftl_messages.pb.h" #include "remoting/signaling/message_tracker.h" -#include "remoting/signaling/messaging_client.h" namespace google { namespace protobuf { @@ -36,11 +39,21 @@ class OAuthTokenGetter; class RegistrationManager; class ScopedProtobufHttpRequest; +class SignalingAddress; class SignalingTracker; // A class for sending and receiving messages via the FTL API. -class FtlMessagingClient final : public MessagingClient { +class FtlMessagingClient { public: + using MessageCallback = + base::RepeatingCallback<void(const SignalingAddress& sender_address, + const ftl::ChromotingMessage& message)>; + using MessageCallbackList = + base::RepeatingCallbackList<void(const SignalingAddress&, + const ftl::ChromotingMessage&)>; + using DoneCallback = + base::OnceCallback<void(const remoting::HttpStatus& status)>; + // |signaling_tracker| is nullable. // Raw pointers must outlive |this|. FtlMessagingClient( @@ -52,18 +65,35 @@ FtlMessagingClient(const FtlMessagingClient&) = delete; FtlMessagingClient& operator=(const FtlMessagingClient&) = delete; - ~FtlMessagingClient() override; + virtual ~FtlMessagingClient(); - // MessagingClient implementations. - base::CallbackListSubscription RegisterMessageCallback( - const MessageCallback& callback) override; - void SendMessage(const SignalingAddress& destination_address, - SignalingMessage&& message, - DoneCallback on_done) override; - void StartReceivingMessages(base::OnceClosure on_ready, - DoneCallback on_closed) override; - void StopReceivingMessages() override; - bool IsReceivingMessages() const override; + // Registers a callback which is run for each new message received. Simply + // delete the returned subscription object to unregister. The subscription + // object must be deleted before |this| is deleted. + virtual base::CallbackListSubscription RegisterMessageCallback( + const MessageCallback& callback); + + // Sends |message| to |destination_address| and then calls |on_done| with the + // result of the operation. + virtual void SendMessage(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message, + DoneCallback on_done); + + // Opens a stream to continuously receive new messages from the server and + // calls the registered MessageCallback once a new message is received. + // |on_ready| is called once the stream is successfully started. + // |on_closed| is called if the stream fails to start, in which case + // |on_ready| will not be called, or when the stream is closed or dropped, + // in which case it is called after |on_ready| is called. + virtual void StartReceivingMessages(base::OnceClosure on_ready, + DoneCallback on_closed); + + // Stops the stream for continuously receiving new messages. Note that + // |on_closed| callback will be silently dropped. + virtual void StopReceivingMessages(); + + // Returns true if the streaming channel is open. + virtual bool IsReceivingMessages() const; private: friend class FtlMessagingClientTest; @@ -82,7 +112,7 @@ DoneCallback on_done); void OnSendMessageResponse(DoneCallback on_done, - const HttpStatus& status, + const remoting::HttpStatus& status, std::unique_ptr<ftl::InboxSendResponse> response); void BatchAckMessages(const ftl::BatchAckMessagesRequest& request, @@ -90,14 +120,14 @@ void OnBatchAckMessagesResponse( DoneCallback on_done, - const HttpStatus& status, + const remoting::HttpStatus& status, std::unique_ptr<ftl::BatchAckMessagesResponse> response); std::unique_ptr<ScopedProtobufHttpRequest> OpenReceiveMessagesStream( base::OnceClosure on_channel_ready, const base::RepeatingCallback< void(std::unique_ptr<ftl::ReceiveMessagesResponse>)>& on_incoming_msg, - base::OnceCallback<void(const HttpStatus&)> on_channel_closed); + base::OnceCallback<void(const remoting::HttpStatus&)> on_channel_closed); void RunMessageCallbacks(const ftl::InboxMessage& message);
diff --git a/remoting/signaling/ftl_messaging_client_unittest.cc b/remoting/signaling/ftl_messaging_client_unittest.cc index e590a3be..bc0466c 100644 --- a/remoting/signaling/ftl_messaging_client_unittest.cc +++ b/remoting/signaling/ftl_messaging_client_unittest.cc
@@ -110,9 +110,7 @@ }; MATCHER_P(SignalingMessageMatches, expected_stanza_text, "") { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&arg); - return ftl_message && ftl_message->xmpp().stanza() == expected_stanza_text; + return arg.xmpp().stanza() == expected_stanza_text; } } // namespace @@ -153,7 +151,7 @@ base::RunLoop run_loop; messaging_client_->SendMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeReceiverId, ""), - SignalingMessage{CreateXmppMessage(kMessage1Text)}, + CreateXmppMessage(kMessage1Text), CheckStatusThenQuitRunLoopCallback( FROM_HERE, HttpStatus::Code::UNAUTHENTICATED, &run_loop)); test_responder_.AddErrorToMostRecentRequestUrl( @@ -165,7 +163,7 @@ base::RunLoop run_loop; messaging_client_->SendMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeReceiverId, ""), - SignalingMessage{CreateXmppMessage(kMessage1Text)}, + CreateXmppMessage(kMessage1Text), CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK, &run_loop)); @@ -186,7 +184,7 @@ messaging_client_->SendMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeReceiverId, kFakeSenderRegId), - SignalingMessage{CreateXmppMessage(kMessage1Text)}, + CreateXmppMessage(kMessage1Text), CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK, &run_loop)); @@ -229,19 +227,15 @@ base::MockCallback<FtlMessagingClient::MessageCallback> mock_on_incoming_msg; EXPECT_CALL(mock_on_incoming_msg, Run(_, _)) - .WillOnce([&](const SignalingAddress&, const SignalingMessage& message) { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - ASSERT_NE(ftl_message, nullptr); - ASSERT_EQ(ftl_message->xmpp().stanza(), kMessage1Text); - }) - .WillOnce([&](const SignalingAddress&, const SignalingMessage& message) { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - ASSERT_NE(ftl_message, nullptr); - ASSERT_EQ(ftl_message->xmpp().stanza(), kMessage2Text); - run_loop.Quit(); - }); + .WillOnce( + [&](const SignalingAddress&, const ftl::ChromotingMessage& message) { + ASSERT_EQ(message.xmpp().stanza(), kMessage1Text); + }) + .WillOnce( + [&](const SignalingAddress&, const ftl::ChromotingMessage& message) { + ASSERT_EQ(message.xmpp().stanza(), kMessage2Text); + run_loop.Quit(); + }); base::CallbackListSubscription subscription = messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get()); @@ -318,18 +312,14 @@ Run(Property(&SignalingAddress::id, "fake_sender@gmail.com/chromoting_ftl_fake_sender_reg_id"), _)) - .WillOnce([&](const SignalingAddress&, const SignalingMessage& message) { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - ASSERT_NE(ftl_message, nullptr); - ASSERT_EQ(ftl_message->xmpp().stanza(), kMessage1Text); - }) - .WillOnce([&](const SignalingAddress&, const SignalingMessage& message) { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - ASSERT_NE(ftl_message, nullptr); - ASSERT_EQ(ftl_message->xmpp().stanza(), kMessage2Text); - }); + .WillOnce( + [&](const SignalingAddress&, const ftl::ChromotingMessage& message) { + ASSERT_EQ(message.xmpp().stanza(), kMessage1Text); + }) + .WillOnce( + [&](const SignalingAddress&, const ftl::ChromotingMessage& message) { + ASSERT_EQ(message.xmpp().stanza(), kMessage2Text); + }); int ack_count = 0; EXPECT_CALL(test_responder_.GetMockInterceptor(), Run(_))
diff --git a/remoting/signaling/ftl_signal_strategy.cc b/remoting/signaling/ftl_signal_strategy.cc index 9e8fe4a..d9851c5 100644 --- a/remoting/signaling/ftl_signal_strategy.cc +++ b/remoting/signaling/ftl_signal_strategy.cc
@@ -10,6 +10,7 @@ #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" +#include "base/notreached.h" #include "base/observer_list.h" #include "base/rand_util.h" #include "base/sequence_checker.h" @@ -22,11 +23,8 @@ #include "remoting/signaling/ftl_device_id_provider.h" #include "remoting/signaling/ftl_messaging_client.h" #include "remoting/signaling/ftl_registration_manager.h" -#include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/signaling_address.h" -#include "remoting/signaling/xmpp_constants.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -34,7 +32,7 @@ public: Core(std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client); + std::unique_ptr<FtlMessagingClient> messaging_client); Core(const Core&) = delete; Core& operator=(const Core&) = delete; @@ -50,8 +48,10 @@ void RemoveListener(Listener* listener); bool SendMessage(const SignalingAddress& destination_address, SignalingMessage&& message); + bool SendFtlMessage(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message); void OnMessageReceived(const SignalingAddress& sender_address, - const SignalingMessage& message); + const ftl::ChromotingMessage& message); bool IsSignInError() const; private: @@ -64,8 +64,8 @@ void OnReceiveMessagesStreamClosed(const HttpStatus& status); void SendMessageImpl(const SignalingAddress& receiver, - SignalingMessage&& message, - MessagingClient::DoneCallback callback); + ftl::ChromotingMessage&& message, + FtlMessagingClient::DoneCallback callback); void OnSendMessageResponse(const SignalingAddress& receiver, const std::string& stanza_id, const HttpStatus& status); @@ -74,13 +74,10 @@ void HandleHttpStatusError(const base::Location& location, const HttpStatus& status); - void OnStanza(const SignalingAddress& sender_address, - std::unique_ptr<jingle_xmpp::XmlElement> stanza); - std::unique_ptr<OAuthTokenGetter> oauth_token_getter_; std::unique_ptr<RegistrationManager> registration_manager_; - std::unique_ptr<MessagingClient> messaging_client_; + std::unique_ptr<FtlMessagingClient> messaging_client_; std::string user_email_; SignalingAddress local_address_; @@ -100,7 +97,7 @@ FtlSignalStrategy::Core::Core( std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client) { + std::unique_ptr<FtlMessagingClient> messaging_client) { DCHECK(oauth_token_getter); DCHECK(registration_manager); DCHECK(messaging_client); @@ -196,33 +193,37 @@ return false; } - std::unique_ptr<jingle_xmpp::XmlElement> stanza; + std::string message_id; + ftl::ChromotingMessage crd_message; if (auto* jingle_message = std::get_if<JingleMessage>(&message)) { // Synthesizing the from attribute in the message. jingle_message->from = local_address_; - stanza = JingleMessageToXml(*jingle_message); + message_id = jingle_message->message_id; + crd_message.mutable_xmpp()->set_stanza(jingle_message->ToSerializedXml()); } else if (auto* jingle_reply = std::get_if<JingleMessageReply>(&message)) { jingle_reply->from = local_address_; - stanza = JingleMessageReplyToXml(*jingle_reply); - } - if (stanza) { - std::string stanza_id = stanza->Attr(kQNameId); - - ftl::ChromotingMessage crd_message; - crd_message.mutable_xmpp()->set_stanza(stanza->Str()); - SendMessageImpl( - destination_address, SignalingMessage(std::move(crd_message)), - base::BindOnce(&Core::OnSendMessageResponse, weak_factory_.GetWeakPtr(), - destination_address, stanza_id)); - return GetState() == CONNECTED; + message_id = jingle_reply->message_id; + crd_message.mutable_xmpp()->set_stanza(jingle_reply->ToSerializedXml()); + } else { + NOTREACHED() << "Unsupported message type."; } - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - if (!ftl_message) { - LOG(ERROR) << "Tried to send a non-FTL message with FtlSignalStrategy."; + SendMessageImpl( + destination_address, std::move(crd_message), + base::BindOnce(&Core::OnSendMessageResponse, weak_factory_.GetWeakPtr(), + destination_address, message_id)); + return GetState() == CONNECTED; +} + +bool FtlSignalStrategy::Core::SendFtlMessage( + const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (GetState() != CONNECTED) { + HOST_LOG << "Dropping message because FTL is not connected."; return false; } @@ -323,57 +324,57 @@ void FtlSignalStrategy::Core::OnMessageReceived( const SignalingAddress& sender_address, - const SignalingMessage& message) { + const ftl::ChromotingMessage& message) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (sender_address.channel() != SignalingAddress::Channel::FTL) { LOG(WARNING) << "Ignoring message sent from non-FTL JID."; return; } - SignalingMessage message_to_dispatch = message; - - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - if (ftl_message && ftl_message->has_xmpp() && - ftl_message->xmpp().has_stanza()) { - auto parsed_message = - SignalStrategy::ParseStanzaXml(ftl_message->xmpp().stanza()); - if (parsed_message) { - // Validate the schema and FTL IDs. - SignalingAddress from; - SignalingAddress to; - if (const auto* jm = std::get_if<JingleMessage>(&*parsed_message)) { - from = jm->from; - to = jm->to; - } else if (const auto* jmr = - std::get_if<JingleMessageReply>(&*parsed_message)) { - from = jmr->from; - to = jmr->to; - } else { - LOG(WARNING) << "Received unexpected non-IQ packet"; - return; - } - - if (from != sender_address) { - LOG(WARNING) << "Expected sender: " << sender_address.id() - << ", but received: " << from.id(); - return; - } - if (to != local_address_) { - LOG(WARNING) << "Expected receiver: " << local_address_.id() - << ", but received: " << to.id(); - return; - } - message_to_dispatch = std::move(*parsed_message); - } else { - // Parsing failed. + for (auto& listener : listeners_) { + if (listener.OnSignalStrategyIncomingFtlMessage(sender_address, message)) { return; } } + if (!message.has_xmpp() || !message.xmpp().has_stanza()) { + return; + } + + auto parsed_message = SignalStrategy::ParseStanzaXml(message.xmpp().stanza()); + if (!parsed_message) { + return; + } + + // Validate the schema and FTL IDs. + SignalingAddress from; + SignalingAddress to; + if (const auto* jm = std::get_if<JingleMessage>(&*parsed_message)) { + from = jm->from; + to = jm->to; + } else if (const auto* jmr = + std::get_if<JingleMessageReply>(&*parsed_message)) { + from = jmr->from; + to = jmr->to; + } else { + LOG(WARNING) << "Received unexpected non-IQ packet"; + return; + } + + if (from != sender_address) { + LOG(WARNING) << "Expected sender: " << sender_address.id() + << ", but received: " << from.id(); + return; + } + if (to != local_address_) { + LOG(WARNING) << "Expected receiver: " << local_address_.id() + << ", but received: " << to.id(); + return; + } + for (auto& listener : listeners_) { if (listener.OnSignalStrategyIncomingMessage(sender_address, - message_to_dispatch)) { + *parsed_message)) { return; } } @@ -381,8 +382,8 @@ void FtlSignalStrategy::Core::SendMessageImpl( const SignalingAddress& receiver, - SignalingMessage&& message, - MessagingClient::DoneCallback callback) { + ftl::ChromotingMessage&& message, + FtlMessagingClient::DoneCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); std::string receiver_username; @@ -395,16 +396,10 @@ } std::string message_payload; - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&message); - if (ftl_message) { - if (ftl_message->has_xmpp()) { - message_payload = ftl_message->xmpp().stanza(); - } else if (ftl_message->has_echo()) { - message_payload = ftl_message->echo().message(); - } else { - message_payload = "Error displaying message due to unknown format."; - } + if (message.has_xmpp()) { + message_payload = message.xmpp().stanza(); + } else if (message.has_echo()) { + message_payload = message.echo().message(); } else { message_payload = "Error displaying message due to unknown format."; } @@ -443,14 +438,13 @@ } // Fake an error message so JingleSession will take it as PEER_IS_OFFLINE. - auto error_iq = std::make_unique<jingle_xmpp::XmlElement>(kQNameIq); - error_iq->SetAttr(kQNameType, kIqTypeError); - error_iq->SetAttr(kQNameId, stanza_id); - error_iq->SetAttr(kQNameFrom, receiver.id()); - error_iq->SetAttr(kQNameTo, local_address_.id()); + JingleMessageReply error_reply(JingleMessageReply::ErrorType::UNSPECIFIED); + error_reply.to = local_address_; + error_reply.from = receiver; + error_reply.message_id = stanza_id; ftl::ChromotingMessage crd_message; - crd_message.mutable_xmpp()->set_stanza(error_iq->Str()); + crd_message.mutable_xmpp()->set_stanza(error_reply.ToSerializedXml()); OnMessageReceived(receiver, crd_message); } @@ -498,7 +492,7 @@ FtlSignalStrategy::FtlSignalStrategy( std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client) { + std::unique_ptr<FtlMessagingClient> messaging_client) { CreateCore(std::move(oauth_token_getter), std::move(registration_manager), std::move(messaging_client)); } @@ -543,6 +537,12 @@ return core_->SendMessage(destination_address, std::move(message)); } +bool FtlSignalStrategy::SendFtlMessage( + const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message) { + return core_->SendFtlMessage(destination_address, std::move(message)); +} + std::string FtlSignalStrategy::GetNextId() { return base::NumberToString(base::RandUint64()); } @@ -554,7 +554,7 @@ void FtlSignalStrategy::CreateCore( std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client) { + std::unique_ptr<FtlMessagingClient> messaging_client) { core_ = std::make_unique<Core>(std::move(oauth_token_getter), std::move(registration_manager), std::move(messaging_client));
diff --git a/remoting/signaling/ftl_signal_strategy.h b/remoting/signaling/ftl_signal_strategy.h index 2b52b3a..a92b976 100644 --- a/remoting/signaling/ftl_signal_strategy.h +++ b/remoting/signaling/ftl_signal_strategy.h
@@ -6,6 +6,7 @@ #define REMOTING_SIGNALING_FTL_SIGNAL_STRATEGY_H_ #include <memory> +#include <string> #include "base/memory/scoped_refptr.h" #include "remoting/signaling/signal_strategy.h" @@ -17,7 +18,7 @@ namespace remoting { class FtlDeviceIdProvider; -class MessagingClient; +class FtlMessagingClient; class RegistrationManager; class SignalingTracker; class OAuthTokenGetter; @@ -55,6 +56,8 @@ void RemoveListener(Listener* listener) override; bool SendMessage(const SignalingAddress& destination_address, SignalingMessage&& message) override; + bool SendFtlMessage(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message) override; std::string GetNextId() override; bool IsSignInError() const override; @@ -63,11 +66,11 @@ FtlSignalStrategy(std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client); + std::unique_ptr<FtlMessagingClient> messaging_client); void CreateCore(std::unique_ptr<OAuthTokenGetter> oauth_token_getter, std::unique_ptr<RegistrationManager> registration_manager, - std::unique_ptr<MessagingClient> messaging_client); + std::unique_ptr<FtlMessagingClient> messaging_client); // This ensures that even if a Listener deletes the current instance during // OnSignalStrategyIncomingMessage(), we can delete |core_| asynchronously.
diff --git a/remoting/signaling/ftl_signal_strategy_unittest.cc b/remoting/signaling/ftl_signal_strategy_unittest.cc index f4e6c8c..42f48e31 100644 --- a/remoting/signaling/ftl_signal_strategy_unittest.cc +++ b/remoting/signaling/ftl_signal_strategy_unittest.cc
@@ -17,11 +17,12 @@ #include "remoting/base/mock_oauth_token_getter.h" #include "remoting/base/oauth_token_getter.h" #include "remoting/proto/ftl/v1/ftl_messages.pb.h" +#include "remoting/signaling/ftl_messaging_client.h" #include "remoting/signaling/jingle_message_xml_converter.h" -#include "remoting/signaling/messaging_client.h" #include "remoting/signaling/registration_manager.h" #include "remoting/signaling/signaling_address.h" #include "remoting/signaling/xmpp_constants.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" @@ -41,12 +42,10 @@ constexpr char kFakeCorpUsername[] = "user@corp.google.com"; MATCHER_P2(SignalingMessageMatches, to, from, "") { - const ftl::ChromotingMessage* ftl_message = - std::get_if<ftl::ChromotingMessage>(&arg); - if (!ftl_message || !ftl_message->has_xmpp()) { + if (!arg.has_xmpp()) { return false; } - std::string stanza = ftl_message->xmpp().stanza(); + std::string stanza = arg.xmpp().stanza(); // Check if it's a Jingle stanza or a plain message. if (stanza.find("to=\"") != std::string::npos) { return stanza.find("to=\"" + std::string(to) + "\"") != std::string::npos && @@ -94,8 +93,16 @@ return stanza; } -class FakeMessagingClient : public MessagingClient { +class FakeMessagingClient : public FtlMessagingClient { public: + FakeMessagingClient() + // static_cast is used to disambiguate which c'tor to call. + : FtlMessagingClient(static_cast<OAuthTokenGetter*>(nullptr), + nullptr, + static_cast<RegistrationManager*>(nullptr), + nullptr) {} + ~FakeMessagingClient() override = default; + base::CallbackListSubscription RegisterMessageCallback( const MessageCallback& callback) override { return callback_list_.Add(callback); @@ -112,17 +119,13 @@ is_receiving_messages_ = true; } - void StopReceivingMessages() override { - if (!is_receiving_messages_) { - return; - } - } + void StopReceivingMessages() override { is_receiving_messages_ = false; } bool IsReceivingMessages() const override { return is_receiving_messages_; } MOCK_METHOD(void, SendMessage, - (const SignalingAddress&, SignalingMessage&&, DoneCallback), + (const SignalingAddress&, ftl::ChromotingMessage&&, DoneCallback), (override)); void OnMessage(const ftl::Id& sender_id, @@ -419,12 +422,12 @@ SendMessage(Property(&SignalingAddress::id, kFakeRemoteFtlId), SignalingMessageMatches(kFakeRemoteFtlId, kFakeLocalFtlId), _)) - .WillOnce([&](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([&](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run(HttpStatus::OK()); }); signal_strategy_->SendMessage(SignalingAddress(kFakeRemoteFtlId), - SignalingMessage(std::move(jingle_message))); + SignalingMessage{std::move(jingle_message)}); } TEST_F(FtlSignalStrategyTest, SendMessage_XmlElement_AuthError) { @@ -444,13 +447,13 @@ EXPECT_CALL( *messaging_client_, SendMessage(Property(&SignalingAddress::id, kFakeRemoteFtlId), _, _)) - .WillOnce([](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run( HttpStatus(HttpStatus::Code::UNAUTHENTICATED, "unauthenticated")); }); signal_strategy_->SendMessage(SignalingAddress(kFakeRemoteFtlId), - SignalingMessage(std::move(jingle_message))); + SignalingMessage{std::move(jingle_message)}); ASSERT_EQ(3u, state_history_.size()); ASSERT_EQ(SignalStrategy::State::CONNECTING, state_history_[0]); @@ -478,13 +481,13 @@ EXPECT_CALL( *messaging_client_, SendMessage(Property(&SignalingAddress::id, kFakeRemoteFtlId), _, _)) - .WillOnce([&](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([&](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run( HttpStatus(HttpStatus::Code::UNAVAILABLE, "unavailable")); }); signal_strategy_->SendMessage(SignalingAddress(kFakeRemoteFtlId), - SignalingMessage(std::move(jingle_message))); + SignalingMessage{std::move(jingle_message)}); ASSERT_EQ(1u, received_messages_.size()); auto& error_message = received_messages_[0]; @@ -603,15 +606,15 @@ kFakeRemoteUsername, kFakeRemoteRegistrationId) .id()), SignalingMessageMatches(message_payload, ""), _)) - .WillOnce([](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run(HttpStatus::OK()); }); - signal_strategy_->SendMessage( + signal_strategy_->SendFtlMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeRemoteUsername, kFakeRemoteRegistrationId), - SignalingMessage{message}); + std::move(message)); } TEST_F(FtlSignalStrategyTest, SendMessage_AuthError) { @@ -628,17 +631,17 @@ kFakeRemoteUsername, kFakeRemoteRegistrationId) .id()), _, _)) - .WillOnce([](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run( HttpStatus(HttpStatus::Code::UNAUTHENTICATED, "unauthenticated")); }); ftl::ChromotingMessage message; - signal_strategy_->SendMessage( + signal_strategy_->SendFtlMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeRemoteUsername, kFakeRemoteRegistrationId), - SignalingMessage{message}); + std::move(message)); ASSERT_EQ(3u, state_history_.size()); ASSERT_EQ(SignalStrategy::State::CONNECTING, state_history_[0]); @@ -666,17 +669,17 @@ kFakeRemoteUsername, kFakeRemoteRegistrationId) .id()), _, _)) - .WillOnce([](const SignalingAddress&, SignalingMessage&&, - MessagingClient::DoneCallback on_done) { + .WillOnce([](const SignalingAddress&, ftl::ChromotingMessage&&, + FtlMessagingClient::DoneCallback on_done) { std::move(on_done).Run( HttpStatus(HttpStatus::Code::UNAVAILABLE, "unavailable")); }); ftl::ChromotingMessage message; - signal_strategy_->SendMessage( + signal_strategy_->SendFtlMessage( SignalingAddress::CreateFtlSignalingAddress(kFakeRemoteUsername, kFakeRemoteRegistrationId), - SignalingMessage{message}); + std::move(message)); ASSERT_EQ(0u, received_messages_.size()); // Remain signed-in for non-auth related error.
diff --git a/remoting/signaling/iq_sender.cc b/remoting/signaling/iq_sender.cc index 45712fb8..c9743c9 100644 --- a/remoting/signaling/iq_sender.cc +++ b/remoting/signaling/iq_sender.cc
@@ -14,11 +14,8 @@ #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" #include "remoting/signaling/jingle_data_structures.h" -#include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/signal_strategy.h" #include "remoting/signaling/signaling_id_util.h" -#include "remoting/signaling/xmpp_constants.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -63,38 +60,6 @@ return request; } -std::unique_ptr<IqRequest> IqSender::SendIq( - std::unique_ptr<jingle_xmpp::XmlElement> stanza, - ReplyCallback callback) { - std::string addressee = stanza->Attr(kQNameTo); - std::string id = stanza->Attr(kQNameId); - if (id.empty()) { - id = signal_strategy_->GetNextId(); - stanza->AddAttr(kQNameId, id); - } - - JingleMessage jingle_message; - std::string error; - if (!JingleMessageFromXml(stanza.get(), &jingle_message, &error)) { - LOG(ERROR) << "Failed to parse IQ stanza: " << error; - return nullptr; - } - - if (!signal_strategy_->SendMessage( - SignalingAddress(addressee), - SignalingMessage(std::move(jingle_message)))) { - return nullptr; - } - DCHECK(requests_.find(id) == requests_.end()); - bool callback_exists = !callback.is_null(); - auto request = - std::make_unique<IqRequest>(this, std::move(callback), addressee); - if (callback_exists) { - requests_[id] = request.get(); - } - return request; -} - void IqSender::RemoveRequest(IqRequest* request) { auto it = requests_.begin(); while (it != requests_.end()) { @@ -112,39 +77,34 @@ bool IqSender::OnSignalStrategyIncomingMessage( const SignalingAddress& sender_address, const SignalingMessage& message) { - if (const auto* jingle_reply = std::get_if<JingleMessageReply>(&message)) { - auto it = requests_.find(jingle_reply->message_id); - if (it == requests_.end()) { - return false; - } - - IqRequest* request = it->second; - - if (NormalizeSignalingId(request->addressee_) != - NormalizeSignalingId(jingle_reply->from.id())) { - LOG(ERROR) << "Received IQ response from an invalid JID. Ignoring it." - << " Message received from: " << jingle_reply->from.id() - << " Original JID: " << request->addressee_; - return false; - } - - requests_.erase(it); - request->OnResponse(*jingle_reply); - - return true; - } - - const JingleMessage* jingle_message = std::get_if<JingleMessage>(&message); - if (!jingle_message) { - return false; - } - // Currently JingleMessageFromXml only returns JingleMessage for 'set' IQs. // IQ results and errors are parsed into JingleMessageReply by the signal // strategy and handled above. If this changes in the future, we might need // to handle JingleMessage responses here. + const auto* jingle_reply = std::get_if<JingleMessageReply>(&message); + if (!jingle_reply) { + return false; + } - return false; + auto it = requests_.find(jingle_reply->message_id); + if (it == requests_.end()) { + return false; + } + + IqRequest* request = it->second; + + if (NormalizeSignalingId(request->addressee_) != + NormalizeSignalingId(jingle_reply->from.id())) { + LOG(ERROR) << "Received IQ response from an invalid JID. Ignoring it." + << " Message received from: " << jingle_reply->from.id() + << " Original JID: " << request->addressee_; + return false; + } + + requests_.erase(it); + request->OnResponse(*jingle_reply); + + return true; } IqRequest::IqRequest(IqSender* sender,
diff --git a/remoting/signaling/iq_sender.h b/remoting/signaling/iq_sender.h index 963bf21..607b89a 100644 --- a/remoting/signaling/iq_sender.h +++ b/remoting/signaling/iq_sender.h
@@ -19,10 +19,6 @@ class TimeDelta; } // namespace base -namespace jingle_xmpp { -class XmlElement; -} // namespace jingle_xmpp - namespace remoting { class IqRequest; @@ -30,11 +26,10 @@ struct JingleMessageReply; class SignalStrategy; -// IqSender handles sending iq requests and routing of responses to -// those requests. +// Handles sending IQ requests and the routing of responses to those requests. class IqSender : public SignalStrategy::Listener { public: - // Callback that is called when an Iq response is received. + // Called when an IQ response is received. using ReplyCallback = base::OnceCallback<void(IqRequest* request, const JingleMessageReply& response)>; @@ -46,10 +41,10 @@ ~IqSender() override; - // Send a Jingle IQ. Returns an IqRequest object that represents the request. - // |callback| is called when response to |stanza| is received. Destroy the - // returned IqRequest to cancel the callback. Caller must take ownership of - // the result. Result must be destroyed before sender is destroyed. + // Send a Jingle IQ. Returns an IqRequest instance which maps to the request. + // |callback| is called when a response to |message| is received. Destroying + // the IqRequest instance will cancel the callback. The IqRequest instance + // must be destroyed before the IqSender instance is destroyed. std::unique_ptr<IqRequest> SendIq(const JingleMessage& message, ReplyCallback callback); @@ -64,12 +59,6 @@ IqRequestMap; friend class IqRequest; - // Sends an IQ stanza. Returns an IqRequest object that represents the - // request. |callback| is called when response to |stanza| is received. - std::unique_ptr<IqRequest> SendIq( - std::unique_ptr<jingle_xmpp::XmlElement> stanza, - ReplyCallback callback); - // Removes |request| from the list of pending requests. Called by IqRequest. void RemoveRequest(IqRequest* request); @@ -77,7 +66,7 @@ IqRequestMap requests_; }; -// This call must only be used on the thread it was created on. +// IqRequest instances are bound to the thread they are created on. class IqRequest { public: IqRequest(IqSender* sender, @@ -89,8 +78,8 @@ ~IqRequest(); - // Sets timeout for the request. When the timeout expires the - // callback is called with the |response| set to nullptr. + // Sets the timeout for the request. When the timeout expires, |callback| is + // called with a JingleMessageReply with its text set to "timeout". void SetTimeout(base::TimeDelta timeout); private:
diff --git a/remoting/signaling/iq_sender_unittest.cc b/remoting/signaling/iq_sender_unittest.cc index 32a9cb5..8ea9c936 100644 --- a/remoting/signaling/iq_sender_unittest.cc +++ b/remoting/signaling/iq_sender_unittest.cc
@@ -14,12 +14,9 @@ #include "base/test/mock_callback.h" #include "base/test/task_environment.h" #include "remoting/signaling/jingle_data_structures.h" -#include "remoting/signaling/jingle_message_xml_converter.h" #include "remoting/signaling/mock_signal_strategy.h" -#include "remoting/signaling/xmpp_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" using ::testing::_; using ::testing::DeleteArg; @@ -29,15 +26,14 @@ using ::testing::Return; using ::testing::SaveArg; -using ::jingle_xmpp::QName; -using ::jingle_xmpp::XmlElement; - namespace remoting { namespace { const char kStanzaId[] = "123"; -const char kTo[] = "user@domain.com"; +const char kLocalUser[] = "local_user@domain.com"; +const char kRemoteUser[] = "remote_user@domain.com"; +const char kUpperCaseRemoteUser[] = "REMOTE_USER@domain.com"; MATCHER_P(ReplyEq, expected, "") { return arg.reply_type == expected.reply_type && @@ -48,7 +44,7 @@ class IqSenderTest : public testing::Test { public: - IqSenderTest() : signal_strategy_(SignalingAddress("local_jid@domain.com")) { + IqSenderTest() : signal_strategy_(SignalingAddress(kLocalUser)) { EXPECT_CALL(signal_strategy_, AddListener(NotNull())); sender_ = std::make_unique<IqSender>(&signal_strategy_); EXPECT_CALL( @@ -59,49 +55,42 @@ protected: void SendTestMessage() { JingleMessage message; - message.to = SignalingAddress(kTo); + message.to = SignalingAddress(kRemoteUser); message.sid = "test_sid"; message.message_id = kStanzaId; message.SetPayload(SessionTerminate()); - EXPECT_CALL(signal_strategy_, SendMessage(SignalingAddress(kTo), _)) + EXPECT_CALL(signal_strategy_, SendMessage(SignalingAddress(kRemoteUser), _)) .WillOnce([&](const SignalingAddress&, SignalingMessage&& message_arg) { auto* sent_jingle_message = std::get_if<JingleMessage>(&message_arg); EXPECT_TRUE(sent_jingle_message); - if (sent_jingle_message) { - std::unique_ptr<XmlElement> sent_stanza = - JingleMessageToXml(*sent_jingle_message); - std::unique_ptr<XmlElement> expected_stanza = - JingleMessageToXml(message); - EXPECT_EQ(expected_stanza->Str(), sent_stanza->Str()); + if (!sent_jingle_message) { + return false; } + + EXPECT_EQ(sent_jingle_message->to, message.to); + EXPECT_EQ(sent_jingle_message->sid, message.sid); + EXPECT_EQ(sent_jingle_message->message_id, message.message_id); + EXPECT_EQ(sent_jingle_message->action(), message.action()); + EXPECT_TRUE( + std::get_if<SessionTerminate>(&sent_jingle_message->payload())); return true; }); request_ = sender_->SendIq(message, callback_.Get()); } - bool FormatAndDeliverResponse(const std::string& from, - std::unique_ptr<XmlElement>* response_out) { - std::unique_ptr<XmlElement> response(new XmlElement(kQNameIq)); - response->AddAttr(QName(std::string(), "type"), "result"); - response->AddAttr(QName(std::string(), "id"), kStanzaId); - response->AddAttr(QName(std::string(), "from"), from); - - XmlElement* response_body = - new XmlElement(QName("test:namespace", "response-body")); - response->AddElement(response_body); - + bool DeliverResponse(const std::string& from, + JingleMessageReply* reply_out = nullptr) { JingleMessageReply reply; - bool parse_result = JingleMessageReplyFromXml(response.get(), &reply); - DCHECK(parse_result); + reply.reply_type = JingleMessageReply::REPLY_RESULT; reply.message_id = kStanzaId; reply.from = SignalingAddress(from); bool result = sender_->OnSignalStrategyIncomingMessage( SignalingAddress(from), SignalingMessage(reply)); - if (response_out) { - *response_out = std::move(response); + if (reply_out) { + *reply_out = std::move(reply); } return result; } @@ -116,11 +105,8 @@ TEST_F(IqSenderTest, SendIq) { ASSERT_NO_FATAL_FAILURE({ SendTestMessage(); }); - std::unique_ptr<XmlElement> response_xml; - EXPECT_TRUE(FormatAndDeliverResponse(kTo, &response_xml)); - JingleMessageReply expected_reply; - ASSERT_TRUE(JingleMessageReplyFromXml(response_xml.get(), &expected_reply)); + EXPECT_TRUE(DeliverResponse(kRemoteUser, &expected_reply)); EXPECT_CALL(callback_, Run(request_.get(), ReplyEq(expected_reply))); base::RunLoop().RunUntilIdle(); @@ -145,13 +131,9 @@ TEST_F(IqSenderTest, NotNormalizedJid) { ASSERT_NO_FATAL_FAILURE({ SendTestMessage(); }); - // Set upper-case from value, which is equivalent to kTo in the original - // message. - std::unique_ptr<XmlElement> response_xml; - EXPECT_TRUE(FormatAndDeliverResponse("USER@domain.com", &response_xml)); - + // Use an upper-case value to verify it is normalized. JingleMessageReply expected_reply; - ASSERT_TRUE(JingleMessageReplyFromXml(response_xml.get(), &expected_reply)); + EXPECT_TRUE(DeliverResponse(kUpperCaseRemoteUser, &expected_reply)); EXPECT_CALL(callback_, Run(request_.get(), ReplyEq(expected_reply))); base::RunLoop().RunUntilIdle(); @@ -160,7 +142,7 @@ TEST_F(IqSenderTest, InvalidFrom) { ASSERT_NO_FATAL_FAILURE({ SendTestMessage(); }); - EXPECT_FALSE(FormatAndDeliverResponse("different_user@domain.com", nullptr)); + EXPECT_FALSE(DeliverResponse("different_user@domain.com", nullptr)); EXPECT_CALL(callback_, Run(_, _)).Times(0); base::RunLoop().RunUntilIdle();
diff --git a/remoting/signaling/jingle_data_structures.cc b/remoting/signaling/jingle_data_structures.cc index f2479e2..1e63037 100644 --- a/remoting/signaling/jingle_data_structures.cc +++ b/remoting/signaling/jingle_data_structures.cc
@@ -15,7 +15,9 @@ #include "remoting/base/constants.h" #include "remoting/base/name_value_map.h" #include "remoting/signaling/content_description.h" +#include "remoting/signaling/jingle_message_xml_converter.h" #include "third_party/abseil-cpp/absl/functional/overload.h" +#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -125,6 +127,10 @@ action_ = ActionFromPayload(payload_); } +std::string JingleMessage::ToSerializedXml() { + return JingleMessageToXml(*this)->Str(); +} + JingleMessageReply::JingleMessageReply() = default; JingleMessageReply::JingleMessageReply(ErrorType error) @@ -146,6 +152,10 @@ JingleMessageReply::~JingleMessageReply() = default; +std::string JingleMessageReply::ToSerializedXml() { + return JingleMessageReplyToXml(*this)->Str(); +} + IceTransportInfo::IceTransportInfo() = default; IceTransportInfo::~IceTransportInfo() = default;
diff --git a/remoting/signaling/jingle_data_structures.h b/remoting/signaling/jingle_data_structures.h index f4cccac..d34b4aa 100644 --- a/remoting/signaling/jingle_data_structures.h +++ b/remoting/signaling/jingle_data_structures.h
@@ -340,6 +340,8 @@ void SetPayload(Payload payload); + std::string ToSerializedXml(); + // Unique identifier for the message. std::string message_id; @@ -404,6 +406,8 @@ JingleMessageReply& operator=(JingleMessageReply&&); ~JingleMessageReply(); + std::string ToSerializedXml(); + // Defines the role of this reply in the IQ request/response pattern. ReplyType reply_type = REPLY_RESULT;
diff --git a/remoting/signaling/messaging_client.h b/remoting/signaling/messaging_client.h deleted file mode 100644 index 346df74..0000000 --- a/remoting/signaling/messaging_client.h +++ /dev/null
@@ -1,64 +0,0 @@ -// Copyright 2019 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef REMOTING_SIGNALING_MESSAGING_CLIENT_H_ -#define REMOTING_SIGNALING_MESSAGING_CLIENT_H_ - -#include "base/callback_list.h" -#include "base/functional/callback_forward.h" -#include "remoting/signaling/signaling_message.h" - -namespace remoting { - -class HttpStatus; -class SignalingAddress; - -// An interface to send messages and receive messages from a messaging service. -class MessagingClient { - public: - using MessageCallback = - base::RepeatingCallback<void(const SignalingAddress& sender_address, - const SignalingMessage& message)>; - using MessageCallbackList = - base::RepeatingCallbackList<void(const SignalingAddress&, - const SignalingMessage&)>; - using DoneCallback = base::OnceCallback<void(const HttpStatus& status)>; - - virtual ~MessagingClient() = default; - - // Registers a callback which is run for each new message received. Simply - // delete the returned subscription object to unregister. The subscription - // object must be deleted before |this| is deleted. - virtual base::CallbackListSubscription RegisterMessageCallback( - const MessageCallback& callback) = 0; - - // Sends |message| to |destination_address| and then calls |on_done| with the - // result of the operation. - virtual void SendMessage(const SignalingAddress& destination_address, - SignalingMessage&& message, - DoneCallback on_done) = 0; - - // Opens a stream to continuously receive new messages from the server and - // calls the registered MessageCallback once a new message is received. - // |on_ready| is called once the stream is successfully started. - // |on_closed| is called if the stream fails to start, in which case - // |on_ready| will not be called, or when the stream is closed or dropped, - // in which case it is called after |on_ready| is called. - virtual void StartReceivingMessages(base::OnceClosure on_ready, - DoneCallback on_closed) = 0; - - // Stops the stream for continuously receiving new messages. Note that - // |on_closed| callback will be silently dropped. - virtual void StopReceivingMessages() = 0; - - // Returns true if the streaming channel is open. - virtual bool IsReceivingMessages() const = 0; - - protected: - MessagingClient() = default; -}; - -} // namespace remoting - -#endif // REMOTING_SIGNALING_MESSAGING_CLIENT_H_
diff --git a/remoting/signaling/mock_signal_strategy.h b/remoting/signaling/mock_signal_strategy.h index 4df18c6..e3637d0 100644 --- a/remoting/signaling/mock_signal_strategy.h +++ b/remoting/signaling/mock_signal_strategy.h
@@ -7,11 +7,11 @@ #include <memory> +#include "remoting/proto/ftl/v1/chromoting_message.pb.h" #include "remoting/signaling/iq_sender.h" #include "remoting/signaling/signal_strategy.h" #include "remoting/signaling/signaling_address.h" #include "testing/gmock/include/gmock/gmock.h" -#include "third_party/libjingle_xmpp/xmllite/xmlelement.h" namespace remoting { @@ -30,6 +30,9 @@ MOCK_METHOD2(SendMessage, bool(const SignalingAddress& destination_address, SignalingMessage&& message)); + MOCK_METHOD2(SendFtlMessage, + bool(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message)); const SignalingAddress& GetLocalAddress() const override;
diff --git a/remoting/signaling/signal_strategy.cc b/remoting/signaling/signal_strategy.cc index 6b5150e..47e9bb1 100644 --- a/remoting/signaling/signal_strategy.cc +++ b/remoting/signaling/signal_strategy.cc
@@ -69,6 +69,17 @@ return false; } +bool SignalStrategy::Listener::OnSignalStrategyIncomingFtlMessage( + const SignalingAddress& sender_address, + const ftl::ChromotingMessage& message) { + return false; +} + +bool SignalStrategy::SendFtlMessage(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message) { + return false; +} + bool SignalStrategy::IsSignInError() const { return false; }
diff --git a/remoting/signaling/signal_strategy.h b/remoting/signaling/signal_strategy.h index 4d2cdea..dafcedc 100644 --- a/remoting/signaling/signal_strategy.h +++ b/remoting/signaling/signal_strategy.h
@@ -13,6 +13,10 @@ namespace remoting { +namespace ftl { +class ChromotingMessage; +} // namespace ftl + class SignalingAddress; class SignalStrategy { @@ -54,6 +58,11 @@ virtual bool OnSignalStrategyIncomingMessage( const SignalingAddress& sender_address, const SignalingMessage& message); + + // Must return true if the message was handled, false otherwise. + virtual bool OnSignalStrategyIncomingFtlMessage( + const SignalingAddress& sender_address, + const ftl::ChromotingMessage& message); }; SignalStrategy() = default; @@ -94,6 +103,10 @@ virtual bool SendMessage(const SignalingAddress& destination_address, SignalingMessage&& message) = 0; + // Sends an FTL message. Returns false if the message couldn't be sent. + virtual bool SendFtlMessage(const SignalingAddress& destination_address, + ftl::ChromotingMessage&& message); + // Returns new ID that should be used for the next outgoing IQ // request. virtual std::string GetNextId() = 0;
diff --git a/remoting/signaling/signaling_id_util.cc b/remoting/signaling/signaling_id_util.cc index 7a3af6b4..a41263c 100644 --- a/remoting/signaling/signaling_id_util.cc +++ b/remoting/signaling/signaling_id_util.cc
@@ -55,7 +55,7 @@ return canonical_email; } -bool SplitSignalingIdResource(const std::string& full_id, +bool SplitSignalingIdResource(std::string_view full_id, std::string* email, std::string* resource) { size_t slash_index = full_id.find('/');
diff --git a/remoting/signaling/signaling_id_util.h b/remoting/signaling/signaling_id_util.h index c95aa7c..856ee321 100644 --- a/remoting/signaling/signaling_id_util.h +++ b/remoting/signaling/signaling_id_util.h
@@ -6,6 +6,7 @@ #define REMOTING_SIGNALING_SIGNALING_ID_UTIL_H_ #include <string> +#include <string_view> namespace remoting { @@ -24,13 +25,13 @@ std::string GetCanonicalEmail(const std::string& email); // Splits a signaling ID into a the email and a resource suffix. Either -// |full_id|, |resource|, or both may be null. If |full_id| is already an email +// |email|, |resource|, or both may be null. If |full_id| is already an email // address, |resource| is set to the empty string. Returns true if |full_id| // has a resource, false if not. // // e.g. "user@domain/resource" -> "user@domain", "resource", true // "user@domain" -> "user@domain", "", false -bool SplitSignalingIdResource(const std::string& full_id, +bool SplitSignalingIdResource(std::string_view full_id, std::string* email, std::string* resource);
diff --git a/remoting/signaling/signaling_message.h b/remoting/signaling/signaling_message.h index 9fb0a46..beef84b 100644 --- a/remoting/signaling/signaling_message.h +++ b/remoting/signaling/signaling_message.h
@@ -5,21 +5,14 @@ #ifndef REMOTING_SIGNALING_SIGNALING_MESSAGE_H_ #define REMOTING_SIGNALING_SIGNALING_MESSAGE_H_ -#include <memory> #include <variant> -#include "remoting/proto/ftl/v1/chromoting_message.pb.h" -#include "remoting/proto/messaging_service.h" #include "remoting/signaling/jingle_data_structures.h" namespace remoting { -// TODO: joedow - Move ChromotingMessage and PeerMessageStruct out of this -// type and into the signal strategies which use them. -using SignalingMessage = std::variant<ftl::ChromotingMessage, - internal::PeerMessageStruct, - JingleMessage, - JingleMessageReply>; +// TODO: joedow - Consolidate the JingleMessage types. +using SignalingMessage = std::variant<JingleMessage, JingleMessageReply>; } // namespace remoting
diff --git a/remoting/test/corp_messaging_playground.cc b/remoting/test/corp_messaging_playground.cc index 61555ca..09c752a 100644 --- a/remoting/test/corp_messaging_playground.cc +++ b/remoting/test/corp_messaging_playground.cc
@@ -165,14 +165,9 @@ void CorpMessagingPlayground::OnPeerMessageReceived( const SignalingAddress& sender_address, - const SignalingMessage& message) { - const auto* peer_message = std::get_if<internal::PeerMessageStruct>(&message); - if (!peer_message) { - LOG(WARNING) << "Received message with unsupported payload type."; - return; - } + const internal::PeerMessageStruct& message) { const auto* system_test = - std::get_if<internal::SystemTestStruct>(&peer_message->payload); + std::get_if<internal::SystemTestStruct>(&message.payload); if (!system_test) { LOG(WARNING) << "Received message with unsupported payload type."; return; @@ -208,8 +203,7 @@ peer_message.payload = std::move(response_message); client_->SendMessage( SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } else if (message.type == PingPongStruct::Type::PING) { // Send PONG. internal::PingPongStruct ping_pong; @@ -225,8 +219,7 @@ peer_message.payload = std::move(response_message); client_->SendMessage( SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } else { NOTREACHED(); } @@ -326,8 +319,7 @@ LOG(INFO) << "Sending ECDH response: " << response_json; client_->SendMessage( SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } else if (base::StartsWith( encrypted_struct.unencrypted_payload, kEcdhResponsePrefix)) { @@ -419,8 +411,7 @@ internal::PeerMessageStruct peer_message; peer_message.payload = std::move(message); client_->SendMessage(SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } return; } @@ -431,8 +422,7 @@ system_test_struct.test_message = std::move(simple_struct); peer_message.payload = std::move(system_test_struct); client_->SendMessage(SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } void CorpMessagingPlayground::StartPingPongRally() { @@ -455,8 +445,7 @@ internal::PeerMessageStruct peer_message; peer_message.payload = std::move(message); client_->SendMessage(SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } void CorpMessagingPlayground::SendLargeMessage() { @@ -481,8 +470,7 @@ system_test_struct.test_message = std::move(simple_struct); peer_message.payload = std::move(system_test_struct); client_->SendMessage(SignalingAddress(messaging_authz_token_), - SignalingMessage{std::move(peer_message)}, - base::DoNothing()); + std::move(peer_message), base::DoNothing()); } } // namespace remoting
diff --git a/remoting/test/corp_messaging_playground.h b/remoting/test/corp_messaging_playground.h index e96a12b..352eea3 100644 --- a/remoting/test/corp_messaging_playground.h +++ b/remoting/test/corp_messaging_playground.h
@@ -48,7 +48,7 @@ void OnStreamClosed(const HttpStatus& status); void OnSignalingAddressChanged(const SignalingAddress& local_address); void OnPeerMessageReceived(const SignalingAddress& sender_address, - const SignalingMessage& message); + const internal::PeerMessageStruct& message); void OnCharacterInput(char c); void SendMessage(int count = 1); void StartPingPongRally();
diff --git a/services/network/devtools_durable_msg_collector_manager.cc b/services/network/devtools_durable_msg_collector_manager.cc index 2a5674e..e943d63 100644 --- a/services/network/devtools_durable_msg_collector_manager.cc +++ b/services/network/devtools_durable_msg_collector_manager.cc
@@ -102,10 +102,10 @@ dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, base::trace_event::MemoryAllocatorDump::kUnitsBytes, total_memory_usage_); - dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, + dump->AddScalar("message_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects, total_message_count_); - dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, + dump->AddScalar("collector_count", base::trace_event::MemoryAllocatorDump::kUnitsObjects, collectors_.size()); return true;
diff --git a/services/network/devtools_durable_msg_collector_manager_unittest.cc b/services/network/devtools_durable_msg_collector_manager_unittest.cc index f25d9a1..302f5b5 100644 --- a/services/network/devtools_durable_msg_collector_manager_unittest.cc +++ b/services/network/devtools_durable_msg_collector_manager_unittest.cc
@@ -180,12 +180,11 @@ ASSERT_NE(dump, nullptr); MemoryAllocatorDump::Entry entry("size", "bytes", 5 * 1024 * 1024u); EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(entry)))); - MemoryAllocatorDump::Entry object_count_entry( - MemoryAllocatorDump::kNameObjectCount, MemoryAllocatorDump::kUnitsObjects, - 1u); - EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(object_count_entry)))); - MemoryAllocatorDump::Entry entry_count("object_count", "objects", 1u); + MemoryAllocatorDump::Entry entry_count("message_count", "objects", 1u); EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(entry_count)))); + MemoryAllocatorDump::Entry collector_count_entry("collector_count", "objects", + 1u); + EXPECT_THAT(dump->entries(), Contains(Eq(ByRef(collector_count_entry)))); collector_remote.reset(); EXPECT_TRUE(base::test::RunUntil( @@ -198,8 +197,11 @@ ASSERT_NE(dump2, nullptr); MemoryAllocatorDump::Entry entry2("size", "bytes", 0u); EXPECT_THAT(dump2->entries(), Contains(Eq(ByRef(entry2)))); - MemoryAllocatorDump::Entry entry2_count("object_count", "objects", 0u); + MemoryAllocatorDump::Entry entry2_count("message_count", "objects", 0u); EXPECT_THAT(dump2->entries(), Contains(Eq(ByRef(entry2_count)))); + MemoryAllocatorDump::Entry entry2_collector_count("collector_count", + "objects", 0u); + EXPECT_THAT(dump2->entries(), Contains(Eq(ByRef(entry2_collector_count)))); } TEST_F(DevtoolsDurableMessageCollectorManagerTest, GlobalLimit) {
diff --git a/services/network/multiple_durable_message_writer_impl_unittest.cc b/services/network/multiple_durable_message_writer_impl_unittest.cc index 636a52c..6555a6c3 100644 --- a/services/network/multiple_durable_message_writer_impl_unittest.cc +++ b/services/network/multiple_durable_message_writer_impl_unittest.cc
@@ -71,6 +71,13 @@ EXPECT_EQ(msg1->byte_size_for_testing(), bytes.size()); EXPECT_EQ(msg2->byte_size_for_testing(), bytes.size()); + + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg1))); + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg2))); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg1))) + .Times(testing::AnyNumber()); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg2))) + .Times(testing::AnyNumber()); } TEST_F(MultipleDurableMessageWriterImplTest, ForwardsMarkComplete) { @@ -89,6 +96,13 @@ EXPECT_TRUE(msg1->is_complete()); EXPECT_TRUE(msg2->is_complete()); + + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg1))); + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg2))); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg1))) + .Times(testing::AnyNumber()); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg2))) + .Times(testing::AnyNumber()); } TEST_F(MultipleDurableMessageWriterImplTest, ForwardsSetClientDecodingTypes) { @@ -108,6 +122,13 @@ EXPECT_EQ(msg1->get_client_decoding_types_for_testing(), types); EXPECT_EQ(msg2->get_client_decoding_types_for_testing(), types); + + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg1))); + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg2))); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg1))) + .Times(testing::AnyNumber()); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg2))) + .Times(testing::AnyNumber()); } TEST_F(MultipleDurableMessageWriterImplTest, HandlesDestroyedMessages) { @@ -123,6 +144,9 @@ MultipleDurableMessageWriterImpl writer(std::move(messages)); // Destroy msg1 + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg1))); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg1))) + .Times(testing::AnyNumber()); msg1.reset(); std::string data = "test data"; @@ -137,6 +161,10 @@ EXPECT_TRUE(msg2->is_complete()); EXPECT_EQ(msg2->byte_size_for_testing(), bytes.size()); + + EXPECT_CALL(accounting_delegate_, WillDestroyMessage(testing::Ref(*msg2))); + EXPECT_CALL(accounting_delegate_, WillRemoveBytes(testing::Ref(*msg2))) + .Times(testing::AnyNumber()); } } // namespace network
diff --git a/services/network/public/cpp/net_ipc_param_traits.cc b/services/network/public/cpp/net_ipc_param_traits.cc index 15dfb09..13e4d9e 100644 --- a/services/network/public/cpp/net_ipc_param_traits.cc +++ b/services/network/public/cpp/net_ipc_param_traits.cc
@@ -42,7 +42,6 @@ const param_type& p) { WriteParam(m, p.verified_cert); WriteParam(m, p.cert_status); - WriteParam(m, p.has_sha1); WriteParam(m, p.public_key_hashes); WriteParam(m, p.is_issued_by_known_root); WriteParam(m, p.ocsp_result); @@ -56,7 +55,6 @@ param_type* r) { return ReadParam(m, iter, &r->verified_cert) && ReadParam(m, iter, &r->cert_status) && - ReadParam(m, iter, &r->has_sha1) && ReadParam(m, iter, &r->public_key_hashes) && ReadParam(m, iter, &r->is_issued_by_known_root) && ReadParam(m, iter, &r->ocsp_result) && ReadParam(m, iter, &r->scts) &&
diff --git a/services/on_device_model/safety/bert_safety_model.cc b/services/on_device_model/safety/bert_safety_model.cc index 30140e626..4ef4622 100644 --- a/services/on_device_model/safety/bert_safety_model.cc +++ b/services/on_device_model/safety/bert_safety_model.cc
@@ -4,6 +4,8 @@ #include "services/on_device_model/safety/bert_safety_model.h" +#include "base/metrics/histogram_functions.h" +#include "base/timer/elapsed_timer.h" #include "components/translate/core/language_detection/language_detection_model.h" #include "services/on_device_model/public/mojom/on_device_model.mojom.h" #include "services/on_device_model/public/mojom/on_device_model_service.mojom.h" @@ -97,6 +99,7 @@ return nullptr; } + base::ElapsedTimer timer; auto status_or_result = static_cast<tflite::task::text::nlclassifier::NLClassifier*>( loaded_bert_model_.get()) @@ -104,6 +107,9 @@ if (absl::IsCancelled(status_or_result.status()) || !status_or_result.ok()) { return nullptr; } + base::UmaHistogramTimes( + "OptimizationGuide.GeneralizedSafetyChecker.ExecutionTime", + timer.Elapsed()); auto safety_info = mojom::SafetyInfo::New(); safety_info->class_scores.reserve(status_or_result->size());
diff --git a/services/webnn/coreml/context_impl_coreml.h b/services/webnn/coreml/context_impl_coreml.h index 4324fe9..91e0735 100644 --- a/services/webnn/coreml/context_impl_coreml.h +++ b/services/webnn/coreml/context_impl_coreml.h
@@ -58,7 +58,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) override; base::expected<scoped_refptr<WebNNTensorImpl>, mojom::ErrorPtr>
diff --git a/services/webnn/coreml/context_impl_coreml.mm b/services/webnn/coreml/context_impl_coreml.mm index 0913ae9f..8f09f7d 100644 --- a/services/webnn/coreml/context_impl_coreml.mm +++ b/services/webnn/coreml/context_impl_coreml.mm
@@ -78,7 +78,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) { GraphImplCoreml::CreateAndBuild( std::move(receiver), this, std::move(graph_info),
diff --git a/services/webnn/coreml/graph_impl_coreml.h b/services/webnn/coreml/graph_impl_coreml.h index 629efed2..16614161 100644 --- a/services/webnn/coreml/graph_impl_coreml.h +++ b/services/webnn/coreml/graph_impl_coreml.h
@@ -49,7 +49,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, mojom::CreateContextOptionsPtr context_options, ContextProperties context_properties, WebNNContextImpl::CreateGraphImplCallback callback);
diff --git a/services/webnn/coreml/graph_impl_coreml.mm b/services/webnn/coreml/graph_impl_coreml.mm index 5852846..7a3a9d3 100644 --- a/services/webnn/coreml/graph_impl_coreml.mm +++ b/services/webnn/coreml/graph_impl_coreml.mm
@@ -320,7 +320,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, mojom::CreateContextOptionsPtr context_options, ContextProperties context_properties, WebNNContextImpl::CreateGraphImplCallback callback) {
diff --git a/services/webnn/dml/context_impl_dml.cc b/services/webnn/dml/context_impl_dml.cc index d5706e9..05870f3 100644 --- a/services/webnn/dml/context_impl_dml.cc +++ b/services/webnn/dml/context_impl_dml.cc
@@ -619,7 +619,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, WebNNContextImpl::CreateGraphImplCallback callback) { GraphImplDml::CreateAndBuild( std::move(receiver), adapter_, weak_factory_.GetWeakPtr(),
diff --git a/services/webnn/dml/context_impl_dml.h b/services/webnn/dml/context_impl_dml.h index 7fb305c..b11111f 100644 --- a/services/webnn/dml/context_impl_dml.h +++ b/services/webnn/dml/context_impl_dml.h
@@ -76,7 +76,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) override; base::expected<scoped_refptr<WebNNTensorImpl>, mojom::ErrorPtr>
diff --git a/services/webnn/dml/graph_impl_dml.cc b/services/webnn/dml/graph_impl_dml.cc index 8347dd8a..ac5ffe5 100644 --- a/services/webnn/dml/graph_impl_dml.cc +++ b/services/webnn/dml/graph_impl_dml.cc
@@ -6033,7 +6033,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, base::expected<ComPtr<IDMLCompiledOperator>, HRESULT> compilation_result) { TRACE_EVENT0("gpu", "dml::GraphImplDml::OnCompilationComplete"); @@ -6182,7 +6183,7 @@ // and not during execution. for (auto& [constant_id, constant_tensor] : constant_tensor_operands) { TensorImplDml* constant_tensor_impl = - static_cast<TensorImplDml*>(constant_tensor); + static_cast<TensorImplDml*>(constant_tensor.get()); // Get the graph input index with the constant id. const auto graph_input_index_iterator = constant_id_to_input_index_map.find(constant_id); @@ -6395,7 +6396,8 @@ mojom::GraphInfoPtr& graph_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>>& constant_operands, - const base::flat_map<OperandId, WebNNTensorImpl*>& constant_tensor_operands, + const base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>>& + constant_tensor_operands, GraphBuilderDml& graph_builder, absl::flat_hash_map<OperandId, uint32_t>& constant_id_to_input_index_map, GraphBufferBindingInfo& graph_buffer_binding_info) { @@ -6869,7 +6871,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, WebNNContextImpl::CreateGraphImplCallback callback, const bool disable_dml_meta_commands_for_gpu) { TRACE_EVENT0("gpu", "dml::GraphImplDml::CreateAndBuild");
diff --git a/services/webnn/dml/graph_impl_dml.h b/services/webnn/dml/graph_impl_dml.h index fabc2d9..5b76ecea 100644 --- a/services/webnn/dml/graph_impl_dml.h +++ b/services/webnn/dml/graph_impl_dml.h
@@ -79,7 +79,7 @@ mojom::GraphInfoPtr& graph_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>>& constant_operands, - const base::flat_map<OperandId, WebNNTensorImpl*>& + const base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>>& constant_tensor_operands, GraphBuilderDml& graph_builder, absl::flat_hash_map<OperandId, uint32_t>& constant_id_to_input_index_map, @@ -99,7 +99,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, WebNNContextImpl::CreateGraphImplCallback callback, bool disable_dml_meta_commands_for_gpu); @@ -181,7 +182,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, base::expected<Microsoft::WRL::ComPtr<IDMLCompiledOperator>, HRESULT> compilation_result);
diff --git a/services/webnn/ort/context_impl_ort.cc b/services/webnn/ort/context_impl_ort.cc index 7d780ef..536880e 100644 --- a/services/webnn/ort/context_impl_ort.cc +++ b/services/webnn/ort/context_impl_ort.cc
@@ -410,7 +410,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) { GraphImplOrt::CreateAndBuild( std::move(receiver), std::move(graph_info),
diff --git a/services/webnn/ort/context_impl_ort.h b/services/webnn/ort/context_impl_ort.h index 7fd6af6..5c965f6 100644 --- a/services/webnn/ort/context_impl_ort.h +++ b/services/webnn/ort/context_impl_ort.h
@@ -89,7 +89,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) override; base::expected<scoped_refptr<WebNNTensorImpl>, mojom::ErrorPtr>
diff --git a/services/webnn/ort/graph_impl_ort.cc b/services/webnn/ort/graph_impl_ort.cc index 1df2a40..cec6450 100644 --- a/services/webnn/ort/graph_impl_ort.cc +++ b/services/webnn/ort/graph_impl_ort.cc
@@ -136,7 +136,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplOrt* context, WebNNContextImpl::CreateGraphImplCallback callback) { ScopedTrace scoped_trace("GraphImplOrt::CreateAndBuild");
diff --git a/services/webnn/ort/graph_impl_ort.h b/services/webnn/ort/graph_impl_ort.h index 9456ad6..b8b662b8 100644 --- a/services/webnn/ort/graph_impl_ort.h +++ b/services/webnn/ort/graph_impl_ort.h
@@ -41,7 +41,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplOrt* context, WebNNContextImpl::CreateGraphImplCallback callback);
diff --git a/services/webnn/public/webnn_features.gni b/services/webnn/public/webnn_features.gni index c13fcd89..55befec 100644 --- a/services/webnn/public/webnn_features.gni +++ b/services/webnn/public/webnn_features.gni
@@ -7,6 +7,7 @@ declare_args() { # This flag enables the graph dump feature for WebNN. - webnn_enable_graph_dump = - (is_win || is_linux || is_mac) && !is_official_build && dcheck_always_on + # TODO(crbug.com/485677947): Re-enable this flag for debug builds once this + # bug is fixed. + webnn_enable_graph_dump = false }
diff --git a/services/webnn/tflite/context_impl_litert.cc b/services/webnn/tflite/context_impl_litert.cc index aced65c4..ff8cbe1 100644 --- a/services/webnn/tflite/context_impl_litert.cc +++ b/services/webnn/tflite/context_impl_litert.cc
@@ -83,7 +83,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) { if (is_incognito_) { // In incognito mode, weights are stored in the Flatbuffer model file @@ -110,7 +111,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback, base::File weights_file) { if (!weights_file.IsValid()) {
diff --git a/services/webnn/tflite/context_impl_litert.h b/services/webnn/tflite/context_impl_litert.h index 59f9632..1bac289 100644 --- a/services/webnn/tflite/context_impl_litert.h +++ b/services/webnn/tflite/context_impl_litert.h
@@ -65,7 +65,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) override; void DidCreateWeightsFile( @@ -74,7 +75,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback, base::File weights_file);
diff --git a/services/webnn/tflite/context_impl_tflite.cc b/services/webnn/tflite/context_impl_tflite.cc index 99899a7..74a70a9 100644 --- a/services/webnn/tflite/context_impl_tflite.cc +++ b/services/webnn/tflite/context_impl_tflite.cc
@@ -83,7 +83,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) { if (is_incognito_) { // In incognito mode, weights are stored in the Flatbuffer model file @@ -108,7 +109,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback, base::File weights_file) { if (!weights_file.IsValid()) {
diff --git a/services/webnn/tflite/context_impl_tflite.h b/services/webnn/tflite/context_impl_tflite.h index d7b5acfa..78ef63a 100644 --- a/services/webnn/tflite/context_impl_tflite.h +++ b/services/webnn/tflite/context_impl_tflite.h
@@ -66,7 +66,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) override; void DidCreateWeightsFile( @@ -75,7 +76,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback, base::File weights_file);
diff --git a/services/webnn/tflite/graph_impl_litert.cc b/services/webnn/tflite/graph_impl_litert.cc index 84fe65f2..b6c4ea6 100644 --- a/services/webnn/tflite/graph_impl_litert.cc +++ b/services/webnn/tflite/graph_impl_litert.cc
@@ -394,7 +394,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplLiteRt* context, base::File weights_file, WebNNContextImpl::CreateGraphImplCallback callback) {
diff --git a/services/webnn/tflite/graph_impl_litert.h b/services/webnn/tflite/graph_impl_litert.h index a6ac1e6..4e93464 100644 --- a/services/webnn/tflite/graph_impl_litert.h +++ b/services/webnn/tflite/graph_impl_litert.h
@@ -42,7 +42,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplLiteRt* context, base::File weights_file, WebNNContextImpl::CreateGraphImplCallback callback);
diff --git a/services/webnn/tflite/graph_impl_tflite.cc b/services/webnn/tflite/graph_impl_tflite.cc index f6652449..a5f894ed 100644 --- a/services/webnn/tflite/graph_impl_tflite.cc +++ b/services/webnn/tflite/graph_impl_tflite.cc
@@ -467,7 +467,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplTflite* context, base::File weights_file, WebNNContextImpl::CreateGraphImplCallback callback) {
diff --git a/services/webnn/tflite/graph_impl_tflite.h b/services/webnn/tflite/graph_impl_tflite.h index 068566f..6156d6d 100644 --- a/services/webnn/tflite/graph_impl_tflite.h +++ b/services/webnn/tflite/graph_impl_tflite.h
@@ -42,7 +42,8 @@ ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, ContextImplTflite* context, base::File weights_file, WebNNContextImpl::CreateGraphImplCallback callback);
diff --git a/services/webnn/webnn_context_impl.h b/services/webnn/webnn_context_impl.h index a7be16e..b4242a2 100644 --- a/services/webnn/webnn_context_impl.h +++ b/services/webnn/webnn_context_impl.h
@@ -135,7 +135,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphImplCallback callback) = 0; // Pass ownership of a newly-created `graph_impl` to this context.
diff --git a/services/webnn/webnn_graph_builder_impl.cc b/services/webnn/webnn_graph_builder_impl.cc index dc275e1..2e3d595c 100644 --- a/services/webnn/webnn_graph_builder_impl.cc +++ b/services/webnn/webnn_graph_builder_impl.cc
@@ -2878,7 +2878,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands) + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands) : compute_resource_info(std::move(compute_resource_info)), constant_operands(std::move(constant_operands)), constant_tensor_operands(std::move(constant_tensor_operands)) {} @@ -2993,7 +2994,8 @@ void WebNNGraphBuilderImpl::DidTransposePendingPermutations( mojom::GraphInfoPtr graph_info, WebNNGraphImpl::ComputeResourceInfo compute_resource_info, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphCallback callback, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>>&& constant_operands) { @@ -3077,7 +3079,8 @@ std::vector<std::pair<OperandId, std::unique_ptr<WebNNConstantOperand>>> graph_constants; graph_constants.reserve(graph_info.constant_operand_ids_to_handles.size()); - std::vector<std::pair<OperandId, WebNNTensorImpl*>> graph_constant_tensors; + std::vector<std::pair<OperandId, scoped_refptr<WebNNTensorImpl>>> + graph_constant_tensors; graph_constant_tensors.reserve( graph_info.id_to_constant_tensor_operand_map.size()); @@ -3160,7 +3163,7 @@ return std::nullopt; } - graph_constant_tensors.emplace_back(operand_id, tensor_impl.get()); + graph_constant_tensors.emplace_back(operand_id, tensor_impl); processed_operands.insert(operand_id); break; }
diff --git a/services/webnn/webnn_graph_builder_impl.h b/services/webnn/webnn_graph_builder_impl.h index 1ee34a80..8443134 100644 --- a/services/webnn/webnn_graph_builder_impl.h +++ b/services/webnn/webnn_graph_builder_impl.h
@@ -69,7 +69,8 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> constant_operands, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands); + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands); ~ValidateGraphSuccessResult(); ValidateGraphSuccessResult(const ValidateGraphSuccessResult&) = delete; @@ -89,7 +90,8 @@ // Constant tensors associated with this graph, which will be used during // graph construction. - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands; + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands; }; // Transfer ownership of this builder's resources to a returned @@ -107,7 +109,8 @@ void DidTransposePendingPermutations( mojom::GraphInfoPtr graph_info, WebNNGraphImpl::ComputeResourceInfo compute_resource_info, - base::flat_map<OperandId, WebNNTensorImpl*> constant_tensor_operands, + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> + constant_tensor_operands, CreateGraphCallback callback, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>>&& constant_operands);
diff --git a/services/webnn/webnn_graph_builder_impl_unittest.cc b/services/webnn/webnn_graph_builder_impl_unittest.cc index 5d58549..b542d4f6 100644 --- a/services/webnn/webnn_graph_builder_impl_unittest.cc +++ b/services/webnn/webnn_graph_builder_impl_unittest.cc
@@ -113,7 +113,7 @@ WebNNGraphImpl::ComputeResourceInfo compute_resource_info, base::flat_map<OperandId, std::unique_ptr<WebNNConstantOperand>> /*constant_operands*/, - base::flat_map<OperandId, WebNNTensorImpl*> + base::flat_map<OperandId, scoped_refptr<WebNNTensorImpl>> /*constant_tensor_operands*/, CreateGraphImplCallback callback) override { // Asynchronously resolve `callback` so there's an opportunity for
diff --git a/services/webnn/webnn_graph_impl_unittest.cc b/services/webnn/webnn_graph_impl_unittest.cc index d2e87160..b8554fa8 100644 --- a/services/webnn/webnn_graph_impl_unittest.cc +++ b/services/webnn/webnn_graph_impl_unittest.cc
@@ -148,7 +148,9 @@ base::flat_map< OperandId, std::unique_ptr<WebNNConstantOperand>> /*constant_operands*/, - base::flat_map<OperandId, WebNNTensorImpl*> /*constant_tensor_operands*/, + base::flat_map< + OperandId, + scoped_refptr<WebNNTensorImpl>> /*constant_tensor_operands*/, CreateGraphImplCallback callback) override { FakeWebNNGraphImpl::CreateAndBuild( std::move(receiver), AsWeakPtr(), *graph_info,
diff --git a/testing/perf/cbb_ref_info/edge/dev/windows.json b/testing/perf/cbb_ref_info/edge/dev/windows.json index 0af0ab2..ab00fa7 100644 --- a/testing/perf/cbb_ref_info/edge/dev/windows.json +++ b/testing/perf/cbb_ref_info/edge/dev/windows.json
@@ -2,5 +2,5 @@ "browser": "edge", "channel": "dev", "platform": "windows", - "version": "147.0.3878.0" + "version": "147.0.3890.0" } \ No newline at end of file
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index a593803..54991ab 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -1159,6 +1159,21 @@ ] } ], + "AndroidSetupList": [ + { + "platforms": [ + "android" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "AndroidSetupList" + ] + } + ] + } + ], "AndroidSmsOtpFilling": [ { "platforms": [
diff --git a/third_party/androidx/BUILD.gn b/third_party/androidx/BUILD.gn index 131996d..dd18223 100644 --- a/third_party/androidx/BUILD.gn +++ b/third_party/androidx/BUILD.gn
@@ -3397,6 +3397,7 @@ ":androidx_compose_runtime_runtime_saveable_java", ":androidx_compose_ui_ui_java", ":androidx_lifecycle_lifecycle_common_java", + ":androidx_lifecycle_lifecycle_runtime_compose_java", ":androidx_lifecycle_lifecycle_viewmodel_java", ":androidx_lifecycle_lifecycle_viewmodel_savedstate_java", "//third_party/android_deps:org_jetbrains_kotlinx_kotlinx_serialization_core_java", @@ -3439,6 +3440,7 @@ ] deps = [ ":androidx_annotation_annotation_java", + ":androidx_collection_collection_java", ":androidx_core_core_ktx_java", ":androidx_lifecycle_lifecycle_common_java", ":androidx_lifecycle_lifecycle_livedata_core_java",
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle index 68f70fb..bc4c27d 100644 --- a/third_party/androidx/build.gradle +++ b/third_party/androidx/build.gradle
@@ -338,7 +338,7 @@ google() maven { // This URL is generated by the fetch_all_androidx.py script. - url 'https://androidx.dev/snapshots/builds/14974200/artifacts/repository' + url 'https://androidx.dev/snapshots/builds/14976394/artifacts/repository' } mavenCentral() }
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium index 715f468..16382e4 100644 --- a/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium +++ b/third_party/androidx/committed/libs/androidx_activity_activity/README.chromium
@@ -1,6 +1,6 @@ Name: Activity Short Name: activity -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/activity/activity/1.13.0-SNAPSHOT/activity-1.13.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/activity/activity/1.13.0-SNAPSHOT/activity-1.13.0-20260304.212319-1.aar Version: 1.13.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium index 3a7dd07f..96bcb0b 100644 --- a/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium +++ b/third_party/androidx/committed/libs/androidx_activity_activity_compose/README.chromium
@@ -1,6 +1,6 @@ Name: Activity Compose Short Name: activity-compose -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/activity/activity-compose/1.13.0-SNAPSHOT/activity-compose-1.13.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/activity/activity-compose/1.13.0-SNAPSHOT/activity-compose-1.13.0-20260304.212319-1.aar Version: 1.13.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium index 7a9b7d1..8b56139c 100644 --- a/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_activity_activity_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Activity Kotlin Extensions Short Name: activity-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/activity/activity-ktx/1.13.0-SNAPSHOT/activity-ktx-1.13.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/activity/activity-ktx/1.13.0-SNAPSHOT/activity-ktx-1.13.0-20260304.212319-1.aar Version: 1.13.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium b/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium index 840e221..c90b961f 100644 --- a/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium +++ b/third_party/androidx/committed/libs/androidx_annotation_annotation_experimental/README.chromium
@@ -1,6 +1,6 @@ Name: Experimental annotation Short Name: annotation-experimental -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/annotation/annotation-experimental/1.6.0-SNAPSHOT/annotation-experimental-1.6.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/annotation/annotation-experimental/1.6.0-SNAPSHOT/annotation-experimental-1.6.0-20260304.212319-1.aar Version: 1.6.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium index 2ff07919..dc17009e 100644 --- a/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium +++ b/third_party/androidx/committed/libs/androidx_annotation_annotation_jvm/README.chromium
@@ -1,6 +1,6 @@ Name: Annotation Short Name: annotation-jvm -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/annotation/annotation-jvm/1.10.0-SNAPSHOT/annotation-jvm-1.10.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/annotation/annotation-jvm/1.10.0-SNAPSHOT/annotation-jvm-1.10.0-20260304.212319-1.jar Version: 1.10.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium b/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium index c03acf7..573386d 100644 --- a/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium +++ b/third_party/androidx/committed/libs/androidx_appcompat_appcompat/README.chromium
@@ -1,6 +1,6 @@ Name: AppCompat Short Name: appcompat -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/appcompat/appcompat/1.8.0-SNAPSHOT/appcompat-1.8.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/appcompat/appcompat/1.8.0-SNAPSHOT/appcompat-1.8.0-20260304.212319-1.aar Version: 1.8.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium b/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium index f29a870..2164440b 100644 --- a/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium +++ b/third_party/androidx/committed/libs/androidx_appcompat_appcompat_resources/README.chromium
@@ -1,6 +1,6 @@ Name: AppCompat Resources Short Name: appcompat-resources -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/appcompat/appcompat-resources/1.8.0-SNAPSHOT/appcompat-resources-1.8.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/appcompat/appcompat-resources/1.8.0-SNAPSHOT/appcompat-resources-1.8.0-20260304.212319-1.aar Version: 1.8.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium index 0959347..7f72923 100644 --- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium +++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch/README.chromium
@@ -1,6 +1,6 @@ Name: AppSearch Short Name: appsearch -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/appsearch/appsearch/1.2.0-SNAPSHOT/appsearch-1.2.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/appsearch/appsearch/1.2.0-SNAPSHOT/appsearch-1.2.0-20260304.212319-1.aar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium index 988a0d3..7bfab01 100644 --- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium +++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_builtin_types/README.chromium
@@ -1,6 +1,6 @@ Name: AppSearch Builtin Types Short Name: appsearch-builtin-types -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/appsearch/appsearch-builtin-types/1.2.0-SNAPSHOT/appsearch-builtin-types-1.2.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/appsearch/appsearch-builtin-types/1.2.0-SNAPSHOT/appsearch-builtin-types-1.2.0-20260304.212319-1.aar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium index 01d39cf..808c2d1 100644 --- a/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium +++ b/third_party/androidx/committed/libs/androidx_appsearch_appsearch_platform_storage/README.chromium
@@ -1,6 +1,6 @@ Name: AppSearch Platform Storage Short Name: appsearch-platform-storage -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/appsearch/appsearch-platform-storage/1.2.0-SNAPSHOT/appsearch-platform-storage-1.2.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/appsearch/appsearch-platform-storage/1.2.0-SNAPSHOT/appsearch-platform-storage-1.2.0-20260304.212319-1.aar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium b/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium index 1f29825..0c6c54b 100644 --- a/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium +++ b/third_party/androidx/committed/libs/androidx_arch_core_core_common/README.chromium
@@ -1,6 +1,6 @@ Name: Arch-Common Short Name: core-common -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/arch/core/core-common/2.3.0-SNAPSHOT/core-common-2.3.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/arch/core/core-common/2.3.0-SNAPSHOT/core-common-2.3.0-20260304.212319-1.jar Version: 2.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium index 8ac0946..685b784 100644 --- a/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium +++ b/third_party/androidx/committed/libs/androidx_arch_core_core_runtime/README.chromium
@@ -1,6 +1,6 @@ Name: Arch-Runtime Short Name: core-runtime -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/arch/core/core-runtime/2.3.0-SNAPSHOT/core-runtime-2.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/arch/core/core-runtime/2.3.0-SNAPSHOT/core-runtime-2.3.0-20260304.212319-1.aar Version: 2.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium b/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium index 6734802..70fc6b1 100644 --- a/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium +++ b/third_party/androidx/committed/libs/androidx_autofill_autofill/README.chromium
@@ -1,6 +1,6 @@ Name: Autofill Short Name: autofill -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/autofill/autofill/1.4.0-SNAPSHOT/autofill-1.4.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/autofill/autofill/1.4.0-SNAPSHOT/autofill-1.4.0-20260304.212319-1.aar Version: 1.4.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium index d01a3e23..413ff51 100644 --- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium +++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_common/README.chromium
@@ -1,6 +1,6 @@ Name: Benchmark - Common Short Name: benchmark-common -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/benchmark/benchmark-common/1.5.0-SNAPSHOT/benchmark-common-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/benchmark/benchmark-common/1.5.0-SNAPSHOT/benchmark-common-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium index faa02cf..a8785a88 100644 --- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium +++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_junit4/README.chromium
@@ -1,6 +1,6 @@ Name: Benchmark - JUnit4 Short Name: benchmark-junit4 -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/benchmark/benchmark-junit4/1.5.0-SNAPSHOT/benchmark-junit4-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/benchmark/benchmark-junit4/1.5.0-SNAPSHOT/benchmark-junit4-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium index 06e93e3..d08faec 100644 --- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium +++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro/README.chromium
@@ -1,6 +1,6 @@ Name: Benchmark - Macrobenchmark Short Name: benchmark-macro -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/benchmark/benchmark-macro/1.5.0-SNAPSHOT/benchmark-macro-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/benchmark/benchmark-macro/1.5.0-SNAPSHOT/benchmark-macro-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium index 18ae1983..03f1797d 100644 --- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium +++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_macro_junit4/README.chromium
@@ -1,6 +1,6 @@ Name: Benchmark - Macrobenchmark JUnit4 Short Name: benchmark-macro-junit4 -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/benchmark/benchmark-macro-junit4/1.5.0-SNAPSHOT/benchmark-macro-junit4-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/benchmark/benchmark-macro-junit4/1.5.0-SNAPSHOT/benchmark-macro-junit4-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium index cf9ba2f7..113dc40 100644 --- a/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_benchmark_benchmark_traceprocessor_android/README.chromium
@@ -1,6 +1,6 @@ Name: Benchmark TraceProcessor Short Name: benchmark-traceprocessor-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/benchmark/benchmark-traceprocessor-android/1.5.0-SNAPSHOT/benchmark-traceprocessor-android-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/benchmark/benchmark-traceprocessor-android/1.5.0-SNAPSHOT/benchmark-traceprocessor-android-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium b/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium index 5e91d31d..ae58495 100644 --- a/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium +++ b/third_party/androidx/committed/libs/androidx_biometric_biometric/README.chromium
@@ -1,6 +1,6 @@ Name: Biometric Short Name: biometric -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/biometric/biometric/1.4.0-SNAPSHOT/biometric-1.4.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/biometric/biometric/1.4.0-SNAPSHOT/biometric-1.4.0-20260304.212319-1.aar Version: 1.4.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium b/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium index 56da756..60a46931 100644 --- a/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium +++ b/third_party/androidx/committed/libs/androidx_browser_browser/README.chromium
@@ -1,6 +1,6 @@ Name: Browser Short Name: browser -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/browser/browser/1.10.0-SNAPSHOT/browser-1.10.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/browser/browser/1.10.0-SNAPSHOT/browser-1.10.0-20260304.212319-1.aar Version: 1.10.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium b/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium index 82f3b485..63f370d 100644 --- a/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium +++ b/third_party/androidx/committed/libs/androidx_cardview_cardview/README.chromium
@@ -1,6 +1,6 @@ Name: CardView Short Name: cardview -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/cardview/cardview/1.1.0-SNAPSHOT/cardview-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/cardview/cardview/1.1.0-SNAPSHOT/cardview-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium index 1510371e..94062591 100644 --- a/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium +++ b/third_party/androidx/committed/libs/androidx_collection_collection_jvm/README.chromium
@@ -1,6 +1,6 @@ Name: collections Short Name: collection-jvm -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/collection/collection-jvm/1.7.0-SNAPSHOT/collection-jvm-1.7.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/collection/collection-jvm/1.7.0-SNAPSHOT/collection-jvm-1.7.0-20260304.212319-1.jar Version: 1.7.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium index b1de6a56..c8569a2 100644 --- a/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_collection_collection_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Collections Kotlin Extensions Short Name: collection-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/collection/collection-ktx/1.7.0-SNAPSHOT/collection-ktx-1.7.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/collection/collection-ktx/1.7.0-SNAPSHOT/collection-ktx-1.7.0-20260304.212319-1.jar Version: 1.7.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium index 1b769a4..16e478a 100644 --- a/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_animation_animation_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Animation Short Name: animation-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/animation/animation-android/1.11.0-SNAPSHOT/animation-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/animation/animation-android/1.11.0-SNAPSHOT/animation-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium index c5b81987..9bf5450 100644 --- a/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_animation_animation_core_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Animation Core Short Name: animation-core-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/animation/animation-core-android/1.11.0-SNAPSHOT/animation-core-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/animation/animation-core-android/1.11.0-SNAPSHOT/animation-core-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium index 486f968..dee8663 100644 --- a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Foundation Short Name: foundation-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/foundation/foundation-android/1.11.0-SNAPSHOT/foundation-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/foundation/foundation-android/1.11.0-SNAPSHOT/foundation-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium index 1316f17..9ea87f1c 100644 --- a/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_foundation_foundation_layout_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Layouts Short Name: foundation-layout-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/foundation/foundation-layout-android/1.11.0-SNAPSHOT/foundation-layout-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/foundation/foundation-layout-android/1.11.0-SNAPSHOT/foundation-layout-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium index d9fe0c1..3225aa8b 100644 --- a/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_material3_material3_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Material3 Components Short Name: material3-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/material3/material3-android/1.5.0-SNAPSHOT/material3-android-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/material3/material3-android/1.5.0-SNAPSHOT/material3-android-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium index 84549a54..4481d8e 100644 --- a/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_material_material_ripple_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Material Ripple Short Name: material-ripple-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/material/material-ripple-android/1.11.0-SNAPSHOT/material-ripple-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/material/material-ripple-android/1.11.0-SNAPSHOT/material-ripple-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium index 658e21b..90193f9 100644 --- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Runtime Short Name: runtime-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/runtime/runtime-android/1.11.0-SNAPSHOT/runtime-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/runtime/runtime-android/1.11.0-SNAPSHOT/runtime-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium index 001460e..a0af3f78 100644 --- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_annotation_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Runtime Annotation Short Name: runtime-annotation-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/runtime/runtime-annotation-android/1.11.0-SNAPSHOT/runtime-annotation-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/runtime/runtime-annotation-android/1.11.0-SNAPSHOT/runtime-annotation-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium index 15c5c5c..b43a43b4 100644 --- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_retain_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Runtime Retain Short Name: runtime-retain-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/runtime/runtime-retain-android/1.11.0-SNAPSHOT/runtime-retain-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/runtime/runtime-retain-android/1.11.0-SNAPSHOT/runtime-retain-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium index d9736f78..c680546 100644 --- a/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_runtime_runtime_saveable_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Saveable Short Name: runtime-saveable-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/runtime/runtime-saveable-android/1.11.0-SNAPSHOT/runtime-saveable-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/runtime/runtime-saveable-android/1.11.0-SNAPSHOT/runtime-saveable-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium index 1873521..236a7b99 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose UI Short Name: ui-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-android/1.11.0-SNAPSHOT/ui-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-android/1.11.0-SNAPSHOT/ui-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium index 094bdaf..2e8bf128 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_geometry_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Geometry Short Name: ui-geometry-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-geometry-android/1.11.0-SNAPSHOT/ui-geometry-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-geometry-android/1.11.0-SNAPSHOT/ui-geometry-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium index 814e460..78fedf5 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_graphics_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Graphics Short Name: ui-graphics-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-graphics-android/1.11.0-SNAPSHOT/ui-graphics-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-graphics-android/1.11.0-SNAPSHOT/ui-graphics-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium index 1ac7e4a3..11d73fa 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Testing Short Name: ui-test-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-test-android/1.11.0-SNAPSHOT/ui-test-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-test-android/1.11.0-SNAPSHOT/ui-test-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium index decbf02..6a37630 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_junit4_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Testing for JUnit4 Short Name: ui-test-junit4-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-test-junit4-android/1.11.0-SNAPSHOT/ui-test-junit4-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-test-junit4-android/1.11.0-SNAPSHOT/ui-test-junit4-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium index 3358b805..b1db3a9 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_test_manifest/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Testing manifest dependency Short Name: ui-test-manifest -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-test-manifest/1.11.0-SNAPSHOT/ui-test-manifest-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-test-manifest/1.11.0-SNAPSHOT/ui-test-manifest-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium index 523afee..66705d4 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose UI Text Short Name: ui-text-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-text-android/1.11.0-SNAPSHOT/ui-text-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-text-android/1.11.0-SNAPSHOT/ui-text-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium index 35f6262b..f7da2b4 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_text_google_fonts/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Google Fonts integration Short Name: ui-text-google-fonts -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-text-google-fonts/1.11.0-SNAPSHOT/ui-text-google-fonts-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-text-google-fonts/1.11.0-SNAPSHOT/ui-text-google-fonts-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium index b0b3a5f..3ddc40b 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_unit_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Unit Short Name: ui-unit-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-unit-android/1.11.0-SNAPSHOT/ui-unit-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-unit-android/1.11.0-SNAPSHOT/ui-unit-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium b/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium index 428f5a1f..4abfdb97 100644 --- a/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_compose_ui_ui_util_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Util Short Name: ui-util-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/compose/ui/ui-util-android/1.11.0-SNAPSHOT/ui-util-android-1.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/compose/ui/ui-util-android/1.11.0-SNAPSHOT/ui-util-android-1.11.0-20260304.212319-1.aar Version: 1.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium index 1254c1d..66f739c 100644 --- a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium +++ b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout/README.chromium
@@ -1,6 +1,6 @@ Name: ConstraintLayout Short Name: constraintlayout -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/constraintlayout/constraintlayout/2.3.0-SNAPSHOT/constraintlayout-2.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/constraintlayout/constraintlayout/2.3.0-SNAPSHOT/constraintlayout-2.3.0-20260304.212319-1.aar Version: 2.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium index d42afb2..6d94732 100644 --- a/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium +++ b/third_party/androidx/committed/libs/androidx_constraintlayout_constraintlayout_core/README.chromium
@@ -1,6 +1,6 @@ Name: ConstraintLayout Core Short Name: constraintlayout-core -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/constraintlayout/constraintlayout-core/1.2.0-SNAPSHOT/constraintlayout-core-1.2.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/constraintlayout/constraintlayout-core/1.2.0-SNAPSHOT/constraintlayout-core-1.2.0-20260304.212319-1.jar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core/README.chromium b/third_party/androidx/committed/libs/androidx_core_core/README.chromium index 2f1bd2b..c0f0f60 100644 --- a/third_party/androidx/committed/libs/androidx_core_core/README.chromium +++ b/third_party/androidx/committed/libs/androidx_core_core/README.chromium
@@ -1,6 +1,6 @@ Name: Core Short Name: core -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/core/core/1.18.0-SNAPSHOT/core-1.18.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/core/core/1.18.0-SNAPSHOT/core-1.18.0-20260304.212319-1.aar Version: 1.18.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium index 8621c047..46fe942 100644 --- a/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_core_core_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Core Kotlin Extensions Short Name: core-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/core/core-ktx/1.18.0-SNAPSHOT/core-ktx-1.18.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/core/core-ktx/1.18.0-SNAPSHOT/core-ktx-1.18.0-20260304.212319-1.aar Version: 1.18.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium index e138a1a..94cecff4 100644 --- a/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium +++ b/third_party/androidx/committed/libs/androidx_core_core_pip/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.core:core-pip Short Name: core-pip -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/core/core-pip/1.0.0-SNAPSHOT/core-pip-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/core/core-pip/1.0.0-SNAPSHOT/core-pip-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium b/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium index da2abf4a..7312072 100644 --- a/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium +++ b/third_party/androidx/committed/libs/androidx_core_core_viewtree/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.core:core-viewtree Short Name: core-viewtree -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/core/core-viewtree/1.1.0-SNAPSHOT/core-viewtree-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/core/core-viewtree/1.1.0-SNAPSHOT/core-viewtree-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium index 9ec9cb5..9b7248c7a 100644 --- a/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium +++ b/third_party/androidx/committed/libs/androidx_credentials_credentials/README.chromium
@@ -1,6 +1,6 @@ Name: Credentials Short Name: credentials -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/credentials/credentials/1.6.0-SNAPSHOT/credentials-1.6.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/credentials/credentials/1.6.0-SNAPSHOT/credentials-1.6.0-20260304.212319-1.aar Version: 1.6.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium index 993e0bf7..37a2b67 100644 --- a/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium +++ b/third_party/androidx/committed/libs/androidx_credentials_credentials_play_services_auth/README.chromium
@@ -1,6 +1,6 @@ Name: Credentials Play Services Auth Short Name: credentials-play-services-auth -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/credentials/credentials-play-services-auth/1.6.0-SNAPSHOT/credentials-play-services-auth-1.6.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/credentials/credentials-play-services-auth/1.6.0-SNAPSHOT/credentials-play-services-auth-1.6.0-20260304.212319-1.aar Version: 1.6.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium index 5061879..6472481 100644 --- a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium +++ b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.credentials.registry:registry-provider Short Name: registry-provider -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/credentials/registry/registry-provider/1.0.0-SNAPSHOT/registry-provider-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/credentials/registry/registry-provider/1.0.0-SNAPSHOT/registry-provider-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium index f1ec8033..25d3b800 100644 --- a/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium +++ b/third_party/androidx/committed/libs/androidx_credentials_registry_registry_provider_play_services/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.credentials.registry:registry-provider-play-services Short Name: registry-provider-play-services -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/credentials/registry/registry-provider-play-services/1.0.0-SNAPSHOT/registry-provider-play-services-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/credentials/registry/registry-provider-play-services/1.0.0-SNAPSHOT/registry-provider-play-services-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium b/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium index 6d953a1..815e4e4f 100644 --- a/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium +++ b/third_party/androidx/committed/libs/androidx_cursoradapter_cursoradapter/README.chromium
@@ -1,6 +1,6 @@ Name: Cursor Adapter Short Name: cursoradapter -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/cursoradapter/cursoradapter/1.1.0-SNAPSHOT/cursoradapter-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/cursoradapter/cursoradapter/1.1.0-SNAPSHOT/cursoradapter-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium index f9c8de4..df374f01 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_android/README.chromium
@@ -1,6 +1,6 @@ Name: DataStore Short Name: datastore-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-android/1.3.0-SNAPSHOT/datastore-android-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-android/1.3.0-SNAPSHOT/datastore-android-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium index a7ea292..3c71bdc6 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_android/README.chromium
@@ -1,6 +1,6 @@ Name: DataStore Core Short Name: datastore-core-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-core-android/1.3.0-SNAPSHOT/datastore-core-android-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-core-android/1.3.0-SNAPSHOT/datastore-core-android-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium index f274791..d182eaf 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_core_okio_jvm/README.chromium
@@ -1,6 +1,6 @@ Name: DataStore Core Okio Short Name: datastore-core-okio-jvm -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-core-okio-jvm/1.3.0-SNAPSHOT/datastore-core-okio-jvm-1.3.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-core-okio-jvm/1.3.0-SNAPSHOT/datastore-core-okio-jvm-1.3.0-20260304.212319-1.jar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium index 00d8912b..257ea34 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_android/README.chromium
@@ -1,6 +1,6 @@ Name: Preferences DataStore Short Name: datastore-preferences-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-preferences-android/1.3.0-SNAPSHOT/datastore-preferences-android-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-preferences-android/1.3.0-SNAPSHOT/datastore-preferences-android-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium index 7d5bf8f..2250de41 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_core_android/README.chromium
@@ -1,6 +1,6 @@ Name: Preferences DataStore Core Short Name: datastore-preferences-core-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-preferences-core-android/1.3.0-SNAPSHOT/datastore-preferences-core-android-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-preferences-core-android/1.3.0-SNAPSHOT/datastore-preferences-core-android-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium index 604dce7..f348514 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_external_protobuf/README.chromium
@@ -1,6 +1,6 @@ Name: Preferences External Protobuf Short Name: datastore-preferences-external-protobuf -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-preferences-external-protobuf/1.3.0-SNAPSHOT/datastore-preferences-external-protobuf-1.3.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-preferences-external-protobuf/1.3.0-SNAPSHOT/datastore-preferences-external-protobuf-1.3.0-20260304.212319-1.jar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: BSD-3-Clause
diff --git a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium index d9067fd..342ffcc 100644 --- a/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium +++ b/third_party/androidx/committed/libs/androidx_datastore_datastore_preferences_proto/README.chromium
@@ -1,6 +1,6 @@ Name: Preferences DataStore Proto Short Name: datastore-preferences-proto -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/datastore/datastore-preferences-proto/1.3.0-SNAPSHOT/datastore-preferences-proto-1.3.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/datastore/datastore-preferences-proto/1.3.0-SNAPSHOT/datastore-preferences-proto-1.3.0-20260304.212319-1.jar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium b/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium index 8ddd9ba5..255d41a4 100644 --- a/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium +++ b/third_party/androidx/committed/libs/androidx_drawerlayout_drawerlayout/README.chromium
@@ -1,6 +1,6 @@ Name: Drawer Layout Short Name: drawerlayout -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/drawerlayout/drawerlayout/1.3.0-SNAPSHOT/drawerlayout-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/drawerlayout/drawerlayout/1.3.0-SNAPSHOT/drawerlayout-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium index c781617..9d6b41f 100644 --- a/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium +++ b/third_party/androidx/committed/libs/androidx_fragment_fragment/README.chromium
@@ -1,6 +1,6 @@ Name: fragment Short Name: fragment -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/fragment/fragment/1.9.0-SNAPSHOT/fragment-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/fragment/fragment/1.9.0-SNAPSHOT/fragment-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium index 22dfe61..6de6591 100644 --- a/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium +++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_compose/README.chromium
@@ -1,6 +1,6 @@ Name: Fragment Compose Short Name: fragment-compose -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/fragment/fragment-compose/1.9.0-SNAPSHOT/fragment-compose-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/fragment/fragment-compose/1.9.0-SNAPSHOT/fragment-compose-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium index 2a07e76..561488fe 100644 --- a/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Fragment Kotlin Extensions Short Name: fragment-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/fragment/fragment-ktx/1.9.0-SNAPSHOT/fragment-ktx-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/fragment/fragment-ktx/1.9.0-SNAPSHOT/fragment-ktx-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium index 06b2760..6ccac87 100644 --- a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium +++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing/README.chromium
@@ -1,6 +1,6 @@ Name: Fragment Testing Extensions Short Name: fragment-testing -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/fragment/fragment-testing/1.9.0-SNAPSHOT/fragment-testing-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/fragment/fragment-testing/1.9.0-SNAPSHOT/fragment-testing-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium index 47369f75..0ee1002 100644 --- a/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium +++ b/third_party/androidx/committed/libs/androidx_fragment_fragment_testing_manifest/README.chromium
@@ -1,6 +1,6 @@ Name: Fragment Testing Manifest dependency Short Name: fragment-testing-manifest -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/fragment/fragment-testing-manifest/1.9.0-SNAPSHOT/fragment-testing-manifest-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/fragment/fragment-testing-manifest/1.9.0-SNAPSHOT/fragment-testing-manifest-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium b/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium index fe2e151..6aae2988 100644 --- a/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium +++ b/third_party/androidx/committed/libs/androidx_graphics_graphics_path/README.chromium
@@ -1,6 +1,6 @@ Name: Android Graphics Path Short Name: graphics-path -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/graphics/graphics-path/1.1.0-SNAPSHOT/graphics-path-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/graphics/graphics-path/1.1.0-SNAPSHOT/graphics-path-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_authoring_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_authoring_android/README.chromium index 484ad39..7067194 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_authoring_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_authoring_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Authoring Short Name: ink-authoring-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-authoring-android/1.1.0-SNAPSHOT/ink-authoring-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-authoring-android/1.1.0-SNAPSHOT/ink-authoring-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_brush_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_brush_android/README.chromium index 72746f2..c5a9807 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_brush_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_brush_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Brush Short Name: ink-brush-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-brush-android/1.1.0-SNAPSHOT/ink-brush-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-brush-android/1.1.0-SNAPSHOT/ink-brush-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_geometry_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_geometry_android/README.chromium index 6e688ba..c79a420 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_geometry_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_geometry_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Geometry Short Name: ink-geometry-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-geometry-android/1.1.0-SNAPSHOT/ink-geometry-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-geometry-android/1.1.0-SNAPSHOT/ink-geometry-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_nativeloader_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_nativeloader_android/README.chromium index e3ec5a6..c255368 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_nativeloader_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_nativeloader_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Native Loader Short Name: ink-nativeloader-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-nativeloader-android/1.1.0-SNAPSHOT/ink-nativeloader-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-nativeloader-android/1.1.0-SNAPSHOT/ink-nativeloader-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_rendering_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_rendering_android/README.chromium index 5b22760..f5b9c7b8 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_rendering_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_rendering_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Rendering Short Name: ink-rendering-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-rendering-android/1.1.0-SNAPSHOT/ink-rendering-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-rendering-android/1.1.0-SNAPSHOT/ink-rendering-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_ink_ink_strokes_android/README.chromium b/third_party/androidx/committed/libs/androidx_ink_ink_strokes_android/README.chromium index 3cf25e2..023a4dbd 100644 --- a/third_party/androidx/committed/libs/androidx_ink_ink_strokes_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_ink_ink_strokes_android/README.chromium
@@ -1,6 +1,6 @@ Name: Ink Strokes Short Name: ink-strokes-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/ink/ink-strokes-android/1.1.0-SNAPSHOT/ink-strokes-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/ink/ink-strokes-android/1.1.0-SNAPSHOT/ink-strokes-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium b/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium index 6d8652d..baba3ce 100644 --- a/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium +++ b/third_party/androidx/committed/libs/androidx_interpolator_interpolator/README.chromium
@@ -1,6 +1,6 @@ Name: Interpolators Short Name: interpolator -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/interpolator/interpolator/1.1.0-SNAPSHOT/interpolator-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/interpolator/interpolator/1.1.0-SNAPSHOT/interpolator-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium b/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium index 32f903c15..fb1a8da1 100644 --- a/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium +++ b/third_party/androidx/committed/libs/androidx_leanback_leanback/README.chromium
@@ -1,6 +1,6 @@ Name: Leanback Short Name: leanback -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/leanback/leanback/1.3.0-SNAPSHOT/leanback-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/leanback/leanback/1.3.0-SNAPSHOT/leanback-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium b/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium index 160632f5..45ef7d6 100644 --- a/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium +++ b/third_party/androidx/committed/libs/androidx_leanback_leanback_grid/README.chromium
@@ -1,6 +1,6 @@ Name: Leanback Grid Short Name: leanback-grid -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/leanback/leanback-grid/1.1.0-SNAPSHOT/leanback-grid-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/leanback/leanback-grid/1.1.0-SNAPSHOT/leanback-grid-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium index a1d8a014..ade22108 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_java8/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle-Common for Java 8 Short Name: lifecycle-common-java8 -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-common-java8/2.11.0-SNAPSHOT/lifecycle-common-java8-2.11.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-common-java8/2.11.0-SNAPSHOT/lifecycle-common-java8-2.11.0-20260304.212319-1.jar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium index d4d1b154..ef69a76 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_common_jvm/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle-Common Short Name: lifecycle-common-jvm -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-common-jvm/2.11.0-SNAPSHOT/lifecycle-common-jvm-2.11.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-common-jvm/2.11.0-SNAPSHOT/lifecycle-common-jvm-2.11.0-20260304.212319-1.jar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium index 181e99e..1e6c1af 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle LiveData Short Name: lifecycle-livedata -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-livedata/2.11.0-SNAPSHOT/lifecycle-livedata-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-livedata/2.11.0-SNAPSHOT/lifecycle-livedata-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium index 0ef9dde..71e5028 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle LiveData Core Short Name: lifecycle-livedata-core -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core/2.11.0-SNAPSHOT/lifecycle-livedata-core-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core/2.11.0-SNAPSHOT/lifecycle-livedata-core-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium index 866ddf4..0bc2389 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_core_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: LiveData Core Kotlin Extensions Short Name: lifecycle-livedata-core-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-core-ktx-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-livedata-core-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-core-ktx-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium index ae5ecdb..db567c3 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_livedata_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: LiveData Kotlin Extensions Short Name: lifecycle-livedata-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-livedata-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-ktx-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-livedata-ktx/2.11.0-SNAPSHOT/lifecycle-livedata-ktx-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium index b1af3db..98bbb0d 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_process/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle Process Short Name: lifecycle-process -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-process/2.11.0-SNAPSHOT/lifecycle-process-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-process/2.11.0-SNAPSHOT/lifecycle-process-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium index d6a5c2e..cd69d6f 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle Runtime Short Name: lifecycle-runtime-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-runtime-android/2.11.0-SNAPSHOT/lifecycle-runtime-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-runtime-android/2.11.0-SNAPSHOT/lifecycle-runtime-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium index cd6afd31..4718214 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle Runtime Compose Short Name: lifecycle-runtime-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-runtime-compose-android/2.11.0-SNAPSHOT/lifecycle-runtime-compose-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-runtime-compose-android/2.11.0-SNAPSHOT/lifecycle-runtime-compose-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium index 8be4546a..e607afc 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_runtime_ktx_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle Kotlin Extensions Short Name: lifecycle-runtime-ktx-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-runtime-ktx-android/2.11.0-SNAPSHOT/lifecycle-runtime-ktx-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-runtime-ktx-android/2.11.0-SNAPSHOT/lifecycle-runtime-ktx-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium index 200f770..310ce308 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_service/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle Service Short Name: lifecycle-service -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-service/2.11.0-SNAPSHOT/lifecycle-service-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-service/2.11.0-SNAPSHOT/lifecycle-service-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium index d7cee22..6031a63 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle ViewModel Short Name: lifecycle-viewmodel-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium index e35b963c..589d4fc 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle ViewModel Compose Short Name: lifecycle-viewmodel-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-compose-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-compose-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-compose-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-compose-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium index 3587194c..0dd4ac7 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle ViewModel Kotlin Extensions Short Name: lifecycle-viewmodel-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-ktx/2.11.0-SNAPSHOT/lifecycle-viewmodel-ktx-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-ktx/2.11.0-SNAPSHOT/lifecycle-viewmodel-ktx-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium index 22901eb..35c46cce 100644 --- a/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_lifecycle_lifecycle_viewmodel_savedstate_android/README.chromium
@@ -1,6 +1,6 @@ Name: Lifecycle ViewModel with SavedState Short Name: lifecycle-viewmodel-savedstate-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-savedstate-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-savedstate-android-2.11.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/lifecycle/lifecycle-viewmodel-savedstate-android/2.11.0-SNAPSHOT/lifecycle-viewmodel-savedstate-android-2.11.0-20260304.212319-1.aar Version: 2.11.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium b/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium index f8c6c15..10bcd9c 100644 --- a/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium +++ b/third_party/androidx/committed/libs/androidx_loader_loader/README.chromium
@@ -1,6 +1,6 @@ Name: loader Short Name: loader -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/loader/loader/1.2.0-SNAPSHOT/loader-1.2.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/loader/loader/1.2.0-SNAPSHOT/loader-1.2.0-20260304.212319-1.aar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_media_media/README.chromium b/third_party/androidx/committed/libs/androidx_media_media/README.chromium index f7bfdcd..46a574d 100644 --- a/third_party/androidx/committed/libs/androidx_media_media/README.chromium +++ b/third_party/androidx/committed/libs/androidx_media_media/README.chromium
@@ -1,6 +1,6 @@ Name: Media Short Name: media -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/media/media/1.8.0-SNAPSHOT/media-1.8.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/media/media/1.8.0-SNAPSHOT/media-1.8.0-20260304.212319-1.aar Version: 1.8.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium b/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium index 8950ccdb..503505c 100644 --- a/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium +++ b/third_party/androidx/committed/libs/androidx_mediarouter_mediarouter/README.chromium
@@ -1,6 +1,6 @@ Name: MediaRouter Short Name: mediarouter -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/mediarouter/mediarouter/1.9.0-SNAPSHOT/mediarouter-1.9.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/mediarouter/mediarouter/1.9.0-SNAPSHOT/mediarouter-1.9.0-20260304.212319-1.aar Version: 1.9.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium index b4e00db..82b639a1 100644 --- a/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_common_android/README.chromium
@@ -1,6 +1,6 @@ Name: Navigation Common Short Name: navigation-common-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/navigation/navigation-common-android/2.10.0-SNAPSHOT/navigation-common-android-2.10.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/navigation/navigation-common-android/2.10.0-SNAPSHOT/navigation-common-android-2.10.0-20260304.212319-1.aar Version: 2.10.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium index d2bfddc..fa57a9c 100644 --- a/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: Compose Navigation Short Name: navigation-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/navigation/navigation-compose-android/2.10.0-SNAPSHOT/navigation-compose-android-2.10.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/navigation/navigation-compose-android/2.10.0-SNAPSHOT/navigation-compose-android-2.10.0-20260304.212319-1.aar Version: 2.10.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium index c79b3de..cbab3279 100644 --- a/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_navigation_navigation_runtime_android/README.chromium
@@ -1,6 +1,6 @@ Name: Navigation Runtime Short Name: navigation-runtime-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/navigation/navigation-runtime-android/2.10.0-SNAPSHOT/navigation-runtime-android-2.10.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/navigation/navigation-runtime-android/2.10.0-SNAPSHOT/navigation-runtime-android-2.10.0-20260304.212319-1.aar Version: 2.10.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium index 59d0ac43..db6a991 100644 --- a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_android/README.chromium
@@ -1,6 +1,6 @@ Name: Navigation Event Short Name: navigationevent-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/navigationevent/navigationevent-android/1.1.0-SNAPSHOT/navigationevent-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/navigationevent/navigationevent-android/1.1.0-SNAPSHOT/navigationevent-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium index d709075..f7dd40f 100644 --- a/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_navigationevent_navigationevent_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: NavigationEvent Compose Short Name: navigationevent-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/navigationevent/navigationevent-compose-android/1.1.0-SNAPSHOT/navigationevent-compose-android-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/navigationevent/navigationevent-compose-android/1.1.0-SNAPSHOT/navigationevent-compose-android-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium index cd7a5a93..1537c65 100644 --- a/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_paging_paging_common_android/README.chromium
@@ -1,6 +1,6 @@ Name: Paging-Common Short Name: paging-common-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/paging/paging-common-android/3.5.0-SNAPSHOT/paging-common-android-3.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/paging/paging-common-android/3.5.0-SNAPSHOT/paging-common-android-3.5.0-20260304.212319-1.aar Version: 3.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium index e17659b..61cd12fa 100644 --- a/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_paging_paging_common_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Paging-Common Kotlin Extensions Short Name: paging-common-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/paging/paging-common-ktx/3.5.0-SNAPSHOT/paging-common-ktx-3.5.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/paging/paging-common-ktx/3.5.0-SNAPSHOT/paging-common-ktx-3.5.0-20260304.212319-1.jar Version: 3.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium index 61afede2..bcf0f4d4 100644 --- a/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_paging_paging_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: Paging-Compose Short Name: paging-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/paging/paging-compose-android/3.5.0-SNAPSHOT/paging-compose-android-3.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/paging/paging-compose-android/3.5.0-SNAPSHOT/paging-compose-android-3.5.0-20260304.212319-1.aar Version: 3.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium index 90315a4..7f0f6a2 100644 --- a/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium +++ b/third_party/androidx/committed/libs/androidx_paging_paging_runtime/README.chromium
@@ -1,6 +1,6 @@ Name: Paging-Runtime Short Name: paging-runtime -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/paging/paging-runtime/3.5.0-SNAPSHOT/paging-runtime-3.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/paging/paging-runtime/3.5.0-SNAPSHOT/paging-runtime-3.5.0-20260304.212319-1.aar Version: 3.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium b/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium index c7a55e34..58ebb36 100644 --- a/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium +++ b/third_party/androidx/committed/libs/androidx_palette_palette/README.chromium
@@ -1,6 +1,6 @@ Name: Palette Short Name: palette -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/palette/palette/1.1.0-SNAPSHOT/palette-1.1.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/palette/palette/1.1.0-SNAPSHOT/palette-1.1.0-20260304.212319-1.aar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium index 7917715a..a810152 100644 --- a/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium +++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_document_service/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.pdf:pdf-document-service Short Name: pdf-document-service -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/pdf/pdf-document-service/1.0.0-SNAPSHOT/pdf-document-service-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/pdf/pdf-document-service/1.0.0-SNAPSHOT/pdf-document-service-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_ink/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_ink/README.chromium index c0d7bfb4..ed2a309 100644 --- a/third_party/androidx/committed/libs/androidx_pdf_pdf_ink/README.chromium +++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_ink/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.pdf:pdf-ink Short Name: pdf-ink -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/pdf/pdf-ink/1.0.0-SNAPSHOT/pdf-ink-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/pdf/pdf-ink/1.0.0-SNAPSHOT/pdf-ink-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium index c5227297..4c49b6bb 100644 --- a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium +++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.pdf:pdf-viewer Short Name: pdf-viewer -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/pdf/pdf-viewer/1.0.0-SNAPSHOT/pdf-viewer-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/pdf/pdf-viewer/1.0.0-SNAPSHOT/pdf-viewer-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium index d1656ef..ea83978 100644 --- a/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium +++ b/third_party/androidx/committed/libs/androidx_pdf_pdf_viewer_fragment/README.chromium
@@ -1,6 +1,6 @@ Name: androidx.pdf:pdf-viewer-fragment Short Name: pdf-viewer-fragment -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/pdf/pdf-viewer-fragment/1.0.0-SNAPSHOT/pdf-viewer-fragment-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/pdf/pdf-viewer-fragment/1.0.0-SNAPSHOT/pdf-viewer-fragment-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium b/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium index 60ec67cf..b259dd14 100644 --- a/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium +++ b/third_party/androidx/committed/libs/androidx_preference_preference/README.chromium
@@ -1,6 +1,6 @@ Name: Preference Short Name: preference -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/preference/preference/1.3.0-SNAPSHOT/preference-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/preference/preference/1.3.0-SNAPSHOT/preference-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium b/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium index 0b3fd16..ee3350a 100644 --- a/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium +++ b/third_party/androidx/committed/libs/androidx_profileinstaller_profileinstaller/README.chromium
@@ -1,6 +1,6 @@ Name: Profile Installer Short Name: profileinstaller -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/profileinstaller/profileinstaller/1.5.0-SNAPSHOT/profileinstaller-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/profileinstaller/profileinstaller/1.5.0-SNAPSHOT/profileinstaller-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium b/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium index 93209303..d22c160 100644 --- a/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium +++ b/third_party/androidx/committed/libs/androidx_recyclerview_recyclerview/README.chromium
@@ -1,6 +1,6 @@ Name: RecyclerView Short Name: recyclerview -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/recyclerview/recyclerview/1.5.0-SNAPSHOT/recyclerview-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/recyclerview/recyclerview/1.5.0-SNAPSHOT/recyclerview-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium b/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium index d7b9a941..8a39eb2 100644 --- a/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium +++ b/third_party/androidx/committed/libs/androidx_resourceinspection_resourceinspection_annotation/README.chromium
@@ -1,6 +1,6 @@ Name: Resource Inspection - Annotations Short Name: resourceinspection-annotation -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/resourceinspection/resourceinspection-annotation/1.1.0-SNAPSHOT/resourceinspection-annotation-1.1.0-20260304.131506-1.jar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/resourceinspection/resourceinspection-annotation/1.1.0-SNAPSHOT/resourceinspection-annotation-1.1.0-20260304.212319-1.jar Version: 1.1.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium index 5f8c5e5a..acc671f0 100644 --- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_android/README.chromium
@@ -1,6 +1,6 @@ Name: Saved State Short Name: savedstate-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/savedstate/savedstate-android/1.5.0-SNAPSHOT/savedstate-android-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/savedstate/savedstate-android/1.5.0-SNAPSHOT/savedstate-android-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium index 7bde8c5..7fa30473 100644 --- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_compose_android/README.chromium
@@ -1,6 +1,6 @@ Name: Saved State Compose Short Name: savedstate-compose-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/savedstate/savedstate-compose-android/1.5.0-SNAPSHOT/savedstate-compose-android-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/savedstate/savedstate-compose-android/1.5.0-SNAPSHOT/savedstate-compose-android-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium index 32637a46..448fec6 100644 --- a/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_savedstate_savedstate_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: SavedState Kotlin Extensions Short Name: savedstate-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/savedstate/savedstate-ktx/1.5.0-SNAPSHOT/savedstate-ktx-1.5.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/savedstate/savedstate-ktx/1.5.0-SNAPSHOT/savedstate-ktx-1.5.0-20260304.212319-1.aar Version: 1.5.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium b/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium index 85cc2b2..4317fca 100644 --- a/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium +++ b/third_party/androidx/committed/libs/androidx_slidingpanelayout_slidingpanelayout/README.chromium
@@ -1,6 +1,6 @@ Name: Sliding Pane Layout Short Name: slidingpanelayout -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/slidingpanelayout/slidingpanelayout/1.3.0-SNAPSHOT/slidingpanelayout-1.3.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/slidingpanelayout/slidingpanelayout/1.3.0-SNAPSHOT/slidingpanelayout-1.3.0-20260304.212319-1.aar Version: 1.3.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium index 0585411c..aae86e5 100644 --- a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_android/README.chromium
@@ -1,6 +1,6 @@ Name: SQLite Short Name: sqlite-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/sqlite/sqlite-android/2.7.0-SNAPSHOT/sqlite-android-2.7.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/sqlite/sqlite-android/2.7.0-SNAPSHOT/sqlite-android-2.7.0-20260304.212319-1.aar Version: 2.7.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium index bb8c611c..de4b1085 100644 --- a/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_sqlite_sqlite_framework_android/README.chromium
@@ -1,6 +1,6 @@ Name: SQLite Framework Integration Short Name: sqlite-framework-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/sqlite/sqlite-framework-android/2.7.0-SNAPSHOT/sqlite-framework-android-2.7.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/sqlite/sqlite-framework-android/2.7.0-SNAPSHOT/sqlite-framework-android-2.7.0-20260304.212319-1.aar Version: 2.7.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium index 02920e6..2659f24 100644 --- a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium +++ b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator/README.chromium
@@ -1,6 +1,6 @@ Name: UIAutomator Short Name: uiautomator -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/test/uiautomator/uiautomator/2.4.0-SNAPSHOT/uiautomator-2.4.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/test/uiautomator/uiautomator/2.4.0-SNAPSHOT/uiautomator-2.4.0-20260304.212319-1.aar Version: 2.4.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium index 8a50184..d7da747 100644 --- a/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_test_uiautomator_uiautomator_shell_android/README.chromium
@@ -1,6 +1,6 @@ Name: Shell Short Name: uiautomator-shell-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/test/uiautomator/uiautomator-shell-android/2.4.0-SNAPSHOT/uiautomator-shell-android-2.4.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/test/uiautomator/uiautomator-shell-android/2.4.0-SNAPSHOT/uiautomator-shell-android-2.4.0-20260304.212319-1.aar Version: 2.4.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium b/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium index 5d7960f5..714d1b71 100644 --- a/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_tracing_tracing_android/README.chromium
@@ -1,6 +1,6 @@ Name: Tracing Short Name: tracing-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/tracing/tracing-android/2.0.0-SNAPSHOT/tracing-android-2.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/tracing/tracing-android/2.0.0-SNAPSHOT/tracing-android-2.0.0-20260304.212319-1.aar Version: 2.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium b/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium index 60dcade..a35f57f 100644 --- a/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium +++ b/third_party/androidx/committed/libs/androidx_tracing_tracing_ktx/README.chromium
@@ -1,6 +1,6 @@ Name: Tracing Kotlin Extensions Short Name: tracing-ktx -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/tracing/tracing-ktx/2.0.0-SNAPSHOT/tracing-ktx-2.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/tracing/tracing-ktx/2.0.0-SNAPSHOT/tracing-ktx-2.0.0-20260304.212319-1.aar Version: 2.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium b/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium index d23798e..52ba1fd3 100644 --- a/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium +++ b/third_party/androidx/committed/libs/androidx_viewpager2_viewpager2/README.chromium
@@ -1,6 +1,6 @@ Name: ViewPager2 Short Name: viewpager2 -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/viewpager2/viewpager2/1.2.0-SNAPSHOT/viewpager2-1.2.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/viewpager2/viewpager2/1.2.0-SNAPSHOT/viewpager2-1.2.0-20260304.212319-1.aar Version: 1.2.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium b/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium index 2980db7..7e0b9642 100644 --- a/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium +++ b/third_party/androidx/committed/libs/androidx_webkit_webkit/README.chromium
@@ -1,6 +1,6 @@ Name: Webkit Short Name: webkit -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/webkit/webkit/1.16.0-SNAPSHOT/webkit-1.16.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/webkit/webkit/1.16.0-SNAPSHOT/webkit-1.16.0-20260304.212319-1.aar Version: 1.16.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium b/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium index b7b8331..fb54e50 100644 --- a/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium +++ b/third_party/androidx/committed/libs/androidx_window_sidecar_sidecar/README.chromium
@@ -1,6 +1,6 @@ Name: WindowManager Sidecar Short Name: sidecar -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/window/sidecar/sidecar/1.0.0-SNAPSHOT/sidecar-1.0.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/window/sidecar/sidecar/1.0.0-SNAPSHOT/sidecar-1.0.0-20260304.212319-1.aar Version: 1.0.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_window/README.chromium b/third_party/androidx/committed/libs/androidx_window_window/README.chromium index f6ca272..17f462ea 100644 --- a/third_party/androidx/committed/libs/androidx_window_window/README.chromium +++ b/third_party/androidx/committed/libs/androidx_window_window/README.chromium
@@ -1,6 +1,6 @@ Name: WindowManager Short Name: window -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/window/window/1.6.0-SNAPSHOT/window-1.6.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/window/window/1.6.0-SNAPSHOT/window-1.6.0-20260304.212319-1.aar Version: 1.6.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium b/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium index fabcd7f6..2a02bd2 100644 --- a/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium +++ b/third_party/androidx/committed/libs/androidx_window_window_core_android/README.chromium
@@ -1,6 +1,6 @@ Name: WindowManager Core Short Name: window-core-android -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/window/window-core-android/1.6.0-SNAPSHOT/window-core-android-1.6.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/window/window-core-android/1.6.0-SNAPSHOT/window-core-android-1.6.0-20260304.212319-1.aar Version: 1.6.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium b/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium index f5d7b5ae..b8aed14 100644 --- a/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium +++ b/third_party/androidx/committed/libs/androidx_work_work_multiprocess/README.chromium
@@ -1,6 +1,6 @@ Name: WorkManager Multiprocess Short Name: work-multiprocess -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/work/work-multiprocess/2.12.0-SNAPSHOT/work-multiprocess-2.12.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/work/work-multiprocess/2.12.0-SNAPSHOT/work-multiprocess-2.12.0-20260304.212319-1.aar Version: 2.12.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium b/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium index dab3d62..7e6a92b 100644 --- a/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium +++ b/third_party/androidx/committed/libs/androidx_work_work_runtime/README.chromium
@@ -1,6 +1,6 @@ Name: WorkManager Runtime Short Name: work-runtime -URL: https://androidx.dev/snapshots/builds/14974200/artifacts/repository/androidx/work/work-runtime/2.12.0-SNAPSHOT/work-runtime-2.12.0-20260304.131506-1.aar +URL: https://androidx.dev/snapshots/builds/14976394/artifacts/repository/androidx/work/work-runtime/2.12.0-SNAPSHOT/work-runtime-2.12.0-20260304.212319-1.aar Version: 2.12.0-SNAPSHOT Update Mechanism: Autoroll License: Apache-2.0
diff --git a/third_party/blink/public/web/web_document_loader.h b/third_party/blink/public/web/web_document_loader.h index 33e2368..b6da61a 100644 --- a/third_party/blink/public/web/web_document_loader.h +++ b/third_party/blink/public/web/web_document_loader.h
@@ -38,7 +38,6 @@ #include "third_party/blink/public/platform/cross_variant_mojo_util.h" #include "third_party/blink/public/platform/web_archive_info.h" #include "third_party/blink/public/platform/web_common.h" -#include "third_party/blink/public/platform/web_source_location.h" #include "third_party/blink/public/web/web_navigation_type.h" namespace blink {
diff --git a/third_party/blink/renderer/bindings/core/v8/is_return_type_compatible.h b/third_party/blink/renderer/bindings/core/v8/is_return_type_compatible.h index eaae629..d6672697 100644 --- a/third_party/blink/renderer/bindings/core/v8/is_return_type_compatible.h +++ b/third_party/blink/renderer/bindings/core/v8/is_return_type_compatible.h
@@ -253,6 +253,16 @@ std::is_convertible<NativeIntType, IDLIntType>::value || std::is_enum<NativeIntType>::value; +// Accept return of a specific union member type instead of the union. This is +// mostly relevant for properties that accept unions for setters by allowing +// to return a more specific type for a getter and allows to minimize heap +// allocations for short-lived objects. +template <typename IDLType, typename ReturnType> + requires(std::derived_from<IDLType, UnionBase> && + !std::is_same_v<IDLType, std::remove_pointer_t<ReturnType>> && + std::is_constructible_v<IDLType, ReturnType>) +inline constexpr bool IsReturnTypeCompatible<IDLType, ReturnType> = true; + // TODO(caseq): should we restrict KURLs to strings of particular type? Forbid // implicit conversion altogether? template <typename IDLStringType>
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h index e51a369..258154a 100644 --- a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h +++ b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
@@ -32,6 +32,29 @@ class EnumerationBase; class UnionBase; +namespace internal { + +template <typename T> +struct NullTraits {}; + +template <typename T> + requires std::is_pointer_v<T> +struct NullTraits<T> { + static bool IsNull(T t) { return t == nullptr; } +}; + +template <> +struct NullTraits<nullptr_t> { + static constexpr bool IsNull(nullptr_t) { return true; } +}; + +template <> +struct NullTraits<String> { + static bool IsNull(const String& str) { return str.IsNull(); } +}; + +} // namespace internal + } // namespace bindings // ToV8Traits provides C++ -> V8 conversion. @@ -792,6 +815,13 @@ DCHECK(value); return value->ToV8(script_state); } + template <typename ArgType> + requires(!std::is_same_v<T*, std::remove_cvref_t<ArgType>> && + std::is_constructible_v<T, ArgType>) + [[nodiscard]] static v8::Local<v8::Value> ToV8(ScriptState* script_state, + ArgType&& value) { + return T::DirectToV8(script_state, std::forward<ArgType>(value)); + } }; template <typename T> @@ -803,6 +833,17 @@ return v8::Null(script_state->GetIsolate()); return ToV8Traits<T>::ToV8(script_state, value); } + template <typename ArgType> + requires(!std::is_same_v<T*, std::remove_cvref_t<ArgType>> && + std::is_constructible_v<T, ArgType>) + [[nodiscard]] static v8::Local<v8::Value> ToV8(ScriptState* script_state, + ArgType&& value) { + if (bindings::internal::NullTraits<std::remove_cvref_t<ArgType>>::IsNull( + value)) { + return v8::Null(script_state->GetIsolate()); + } + return T::DirectToV8(script_state, std::forward<ArgType>(value)); + } }; // Optional
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/union.py b/third_party/blink/renderer/bindings/scripts/bind_gen/union.py index 5372906..f9b3ab2f 100644 --- a/third_party/blink/renderer/bindings/scripts/bind_gen/union.py +++ b/third_party/blink/renderer/bindings/scripts/bind_gen/union.py
@@ -860,6 +860,35 @@ return func_decl, func_def +def make_direct_tov8_functions(cg_context): + assert isinstance(cg_context, CodeGenContext) + + F = FormatNode + + decls = [] + defs = [] + for member in cg_context.union_members: + if member.is_null: + continue + traits_type = native_value_tag(member.idl_type) + arg_type = member.type_info.member_ref_t + func_def = CxxFuncDefNode("DirectToV8", + arg_decls=[ + "ScriptState* script_state", + "{} value".format( + member.type_info.member_ref_t) + ], + class_name="${class_name}", + return_type="v8::Local<v8::Value>") + func_def.set_base_template_vars(cg_context.template_bindings()) + func_def.body.append( + F("return ToV8Traits<{}>::ToV8(script_state, value);".format( + traits_type))) + defs.append(func_def) + func_decl = func_def.make_decl(static=True) + decls.append(func_decl) + return decls, defs + def make_trace_function(cg_context): assert isinstance(cg_context, CodeGenContext) @@ -1008,6 +1037,8 @@ ctor_decls, ctor_defs = make_constructors(cg_context) accessor_decls, accessor_defs = make_accessor_functions(cg_context) tov8_func_decls, tov8_func_defs = make_tov8_function(cg_context) + direct_tov8_func_decls, direct_tov8_func_defs = make_direct_tov8_functions( + cg_context) trace_func_decls, trace_func_defs = make_trace_function(cg_context) clear_func_decls, clear_func_defs = make_clear_function(cg_context) name_func_decls, name_func_defs = make_name_function(cg_context) @@ -1101,6 +1132,10 @@ class_def.public_section.append(EmptyNode()) source_blink_ns.body.append(tov8_func_defs) source_blink_ns.body.append(EmptyNode()) + class_def.public_section.extend(direct_tov8_func_decls) + source_blink_ns.body.append(EmptyNode()) + source_blink_ns.body.extend(direct_tov8_func_defs) + source_blink_ns.body.append(EmptyNode()) class_def.public_section.append(trace_func_decls) class_def.public_section.append(EmptyNode())
diff --git a/third_party/blink/renderer/core/animation/element_animations.cc b/third_party/blink/renderer/core/animation/element_animations.cc index 9bfdc0a..eb60534 100644 --- a/third_party/blink/renderer/core/animation/element_animations.cc +++ b/third_party/blink/renderer/core/animation/element_animations.cc
@@ -212,6 +212,14 @@ CompositedPaintStatus status) { if (status == ElementAnimations::CompositedPaintStatus::kNotComposited || status == ElementAnimations::CompositedPaintStatus::kNoAnimation) { + if (clip_path_paint_worklet_candidate_ && + clip_path_paint_worklet_candidate_->HasActiveAnimationsOnCompositor()) { + // This can some times be called during pre-paint, we need to ensure the + // animation is kept in sync! + clip_path_paint_worklet_candidate_->SetCompositorPending( + Animation::CompositorPendingReason::kPendingDowngrade); + } + clip_path_paint_worklet_candidate_ = nullptr; }
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc index 891e030..2f8929b 100644 --- a/third_party/blink/renderer/core/dom/element.cc +++ b/third_party/blink/renderer/core/dom/element.cc
@@ -5884,6 +5884,7 @@ child_attacher = &whitespace_attacher; } RebuildTransitionLayoutTree(*child_attacher); + RebuildOverscrollAreaLayoutTree(*child_attacher); RebuildPseudoElementLayoutTree(kPseudoIdAfter, *child_attacher); RebuildPseudoElementLayoutTree(kPseudoIdPickerIcon, *child_attacher); RebuildPseudoElementLayoutTree(kPseudoIdInterestHint, *child_attacher); @@ -5980,6 +5981,22 @@ *this, rebuild_pseudo_tree, ViewTransitionUtils::Filter::kDirectChildren); } +void Element::RebuildOverscrollAreaLayoutTree( + WhitespaceAttacher& whitespace_attacher) { + OverscrollAreaTracker* overscroll_area_tracker = GetOverscrollAreaTracker(); + if (!overscroll_area_tracker) { + return; + } + + for (Element* overscroll_area : + overscroll_area_tracker->DOMSortedElements()) { + PseudoElement* pseudo_element = + overscroll_area->GetPseudoElement(kPseudoIdOverscrollAreaParent); + pseudo_element->RebuildLayoutTree(whitespace_attacher); + CHECK(pseudo_element->GetLayoutObject()); + } +} + void Element::AttachOverscrollPseudoElements(AttachContext& context) { OverscrollAreaTracker* overscroll_area_tracker = GetOverscrollAreaTracker(); if (!overscroll_area_tracker) {
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h index 6a98c71..26bc78e 100644 --- a/third_party/blink/renderer/core/dom/element.h +++ b/third_party/blink/renderer/core/dom/element.h
@@ -2254,6 +2254,7 @@ void RebuildColumnLayoutTrees(WhitespaceAttacher&); void RebuildFirstLetterLayoutTree(); void RebuildTransitionLayoutTree(WhitespaceAttacher&); + void RebuildOverscrollAreaLayoutTree(WhitespaceAttacher&); void RebuildShadowRootLayoutTree(WhitespaceAttacher&); inline void CheckForEmptyStyleChange(const Node* node_before_change, const Node* node_after_change);
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc index 8c9e9e0..edb5d93 100644 --- a/third_party/blink/renderer/core/dom/node.cc +++ b/third_party/blink/renderer/core/dom/node.cc
@@ -2458,11 +2458,8 @@ return content.ReleaseString(); } -V8UnionStringOrTrustedScript* Node::textContentForBinding() const { - const String& value = textContent(); - if (value.IsNull()) - return nullptr; - return MakeGarbageCollected<V8UnionStringOrTrustedScript>(value); +String Node::textContentForBinding() const { + return textContent(); } void Node::setTextContentForBinding(const V8UnionStringOrTrustedScript* value,
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h index a3a28db9f..e5b5b83 100644 --- a/third_party/blink/renderer/core/dom/node.h +++ b/third_party/blink/renderer/core/dom/node.h
@@ -344,7 +344,7 @@ TextVisitor* visitor = nullptr, unsigned int max_length = UINT_MAX) const; virtual void setTextContent(const String&); - V8UnionStringOrTrustedScript* textContentForBinding() const; + String textContentForBinding() const; virtual void setTextContentForBinding( const V8UnionStringOrTrustedScript* value, ExceptionState& exception_state);
diff --git a/third_party/blink/renderer/core/dom/opaque_range_test.cc b/third_party/blink/renderer/core/dom/opaque_range_test.cc index d4c0135f..e765de2e 100644 --- a/third_party/blink/renderer/core/dom/opaque_range_test.cc +++ b/third_party/blink/renderer/core/dom/opaque_range_test.cc
@@ -107,4 +107,16 @@ EXPECT_FALSE(range->collapsed()); } +TEST_F(OpaqueRangeTest, RemovalClearsOpaqueRanges) { + ScopedOpaqueRangeForTest scoped_feature(true); + SetBodyContent("<textarea>Hello</textarea>"); + auto* textarea = + To<HTMLTextAreaElement>(GetDocument().body()->firstElementChild()); + OpaqueRange::Create(GetDocument(), textarea, 0, 3); + OpaqueRange::Create(GetDocument(), textarea, 2, 5); + EXPECT_EQ(textarea->opaque_ranges_.size(), 2u); + textarea->remove(); + EXPECT_TRUE(textarea->opaque_ranges_.empty()); +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/fileapi/blob.cc b/third_party/blink/renderer/core/fileapi/blob.cc index 76875296..c652c04 100644 --- a/third_party/blink/renderer/core/fileapi/blob.cc +++ b/third_party/blink/renderer/core/fileapi/blob.cc
@@ -335,7 +335,7 @@ if (!IsValidBlobType(type)) { return g_empty_string; } - return type.DeprecatedLower(); + return type.LowerASCII(); } } // namespace blink
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.cc b/third_party/blink/renderer/core/frame/frame_serializer.cc index 50b9df1..eb4c42b0 100644 --- a/third_party/blink/renderer/core/frame/frame_serializer.cc +++ b/third_party/blink/renderer/core/frame/frame_serializer.cc
@@ -1143,7 +1143,7 @@ // that case. css_text.Append("@charset \""); css_text.Append(charset.IsValid() - ? charset.GetName().GetString().DeprecatedLower() + ? charset.GetName().GetString().LowerASCII() : "utf-8"); css_text.Append("\";\n\n");
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc index 1975b9b..af98d18 100644 --- a/third_party/blink/renderer/core/html/forms/html_input_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -619,6 +619,11 @@ if (!previously_selectable && now_selectable) SetSelectionRange(0, 0, kSelectionHasNoDirection); + // Disconnect all OpaqueRanges on any type change. + if (previously_selectable && RuntimeEnabledFeatures::OpaqueRangeEnabled()) { + DisconnectAllOpaqueRanges(); + } + UpdateHasBeenPasswordField(new_type_name); SetNeedsValidityCheck();
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc index 5b8841f..58ca7a9 100644 --- a/third_party/blink/renderer/core/html/forms/text_control_element.cc +++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -1320,6 +1320,20 @@ return ComputedStyleRef().TextOverflow(); } +void TextControlElement::DisconnectAllOpaqueRanges() { + while (!opaque_ranges_.empty()) { + opaque_ranges_.back()->disconnect(); + } +} + +void TextControlElement::RemovedFrom(ContainerNode& insertion_point) { + if (insertion_point.isConnected() && + RuntimeEnabledFeatures::OpaqueRangeEnabled()) { + DisconnectAllOpaqueRanges(); + } + HTMLFormControlElementWithState::RemovedFrom(insertion_point); +} + void TextControlElement::RegisterOpaqueRange(OpaqueRange* range) { opaque_ranges_.push_back(range); }
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.h b/third_party/blink/renderer/core/html/forms/text_control_element.h index dfc665d7..eafd12b 100644 --- a/third_party/blink/renderer/core/html/forms/text_control_element.h +++ b/third_party/blink/renderer/core/html/forms/text_control_element.h
@@ -234,6 +234,8 @@ protected: TextControlElement(const QualifiedName&, Document&); + void RemovedFrom(ContainerNode&) override; + void DisconnectAllOpaqueRanges(); virtual HTMLElement* UpdatePlaceholderText() = 0; // Creates the editor if necessary. Implementations that support an editor @@ -391,6 +393,7 @@ FRIEND_TEST_ALL_PREFIXES(TextControlElementTest, IndexForPosition); FRIEND_TEST_ALL_PREFIXES(HTMLTextAreaElementTest, ValueWithHardLineBreaks); FRIEND_TEST_ALL_PREFIXES(HTMLTextAreaElementTest, ValueWithHardLineBreaksRtl); + FRIEND_TEST_ALL_PREFIXES(OpaqueRangeTest, RemovalClearsOpaqueRanges); }; inline bool IsTextControl(const Node& node) {
diff --git a/third_party/blink/renderer/core/html/html_script_element.cc b/third_party/blink/renderer/core/html/html_script_element.cc index f8f82e19..4860d58 100644 --- a/third_party/blink/renderer/core/html/html_script_element.cc +++ b/third_party/blink/renderer/core/html/html_script_element.cc
@@ -244,7 +244,7 @@ Node::setTextContent(string); } -V8UnionStringOrTrustedScript* HTMLScriptElement::scriptTextContentForBinding() { +String HTMLScriptElement::scriptTextContentForBinding() { return textContentForBinding(); }
diff --git a/third_party/blink/renderer/core/html/html_script_element.h b/third_party/blink/renderer/core/html/html_script_element.h index efe4543..57d54e9 100644 --- a/third_party/blink/renderer/core/html/html_script_element.h +++ b/third_party/blink/renderer/core/html/html_script_element.h
@@ -69,7 +69,7 @@ void setScriptTextContentForBinding(const V8UnionStringOrTrustedScript*, ExceptionState&); - V8UnionStringOrTrustedScript* scriptTextContentForBinding(); + String scriptTextContentForBinding(); void setScriptInnerTextForBinding( const V8UnionStringLegacyNullToEmptyStringOrTrustedScript* string_or_trusted_script,
diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc index 09b08c1..403f0e2 100644 --- a/third_party/blink/renderer/core/input/event_handler_test.cc +++ b/third_party/blink/renderer/core/input/event_handler_test.cc
@@ -52,6 +52,7 @@ #include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h" #include "third_party/blink/renderer/platform/keyboard_codes.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "ui/base/cursor/cursor.h" @@ -3669,8 +3670,13 @@ TEST_F(EventHandlerSimTest, GestureTapHoverState) { ResizeView(gfx::Size(800, 600)); - // RecomputeMouseHoverState() bails early if we are not focused. - GetPage().SetFocused(true); + // With this feature enabled, RecomputeMouseHoverState() fires synthetic + // mouse events for inactive pages. If the feature is disabled, we need to + // focus the page to avoid the early exit in RecomputeMouseHoverState(). + // See crbug.com/385474535 for more details. + if (!RuntimeEnabledFeatures::SyntheticMouseHoverOverInactivePageEnabled()) { + GetPage().SetFocused(true); + } SimRequest request("https://example.com/test.html", "text/html"); LoadURL("https://example.com/test.html");
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc index 996f3ee..4ccb6621 100644 --- a/third_party/blink/renderer/core/input/mouse_event_manager.cc +++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -385,8 +385,13 @@ if (!view) return; - if (!frame_->GetPage() || !frame_->GetPage()->GetFocusController().IsActive()) + const bool should_page_receive_mouse_hover = + frame_->GetPage() && + (RuntimeEnabledFeatures::SyntheticMouseHoverOverInactivePageEnabled() || + frame_->GetPage()->GetFocusController().IsActive()); + if (!should_page_receive_mouse_hover) { return; + } // Don't dispatch a synthetic mouse move event if the mouse cursor is not // visible to the user.
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager_test.cc b/third_party/blink/renderer/core/input/mouse_event_manager_test.cc index 33e9919..cb69add 100644 --- a/third_party/blink/renderer/core/input/mouse_event_manager_test.cc +++ b/third_party/blink/renderer/core/input/mouse_event_manager_test.cc
@@ -15,6 +15,8 @@ #include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h" #include "third_party/blink/renderer/platform/keyboard_codes.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" namespace blink { @@ -49,8 +51,13 @@ TEST_F(MouseEventManagerTest, HoverEffectAfterNav) { LocalFrame* frame = MainFrame().GetFrame(); - // RecomputeMouseHoverState() bails early if we are not focused. - GetPage().SetFocused(true); + // With this feature enabled, RecomputeMouseHoverState() fires synthetic + // mouse events for inactive pages. The SetFocused call remains as a fallback + // in case the feature needs to be disabled. + // See crbug.com/385474535 for more details. + if (!RuntimeEnabledFeatures::SyntheticMouseHoverOverInactivePageEnabled()) { + GetPage().SetFocused(true); + } // This mousemove sets last_known_mouse_position_ before we navigate. GetEventHandler().HandleMouseMoveEvent( @@ -99,4 +106,62 @@ EXPECT_EQ("rgb(255, 0, 0)", color.SerializeAsCSSColor()); } +TEST_F(MouseEventManagerTest, + RecomputeMouseHoverStateForActiveAndInactivePage) { + SimRequest request("https://example.com/test.html", "text/html"); + LoadURL("https://example.com/test.html"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { margin: 10px; } + #target { width: 20px; height: 20px; background: gray; } + #target:hover { background: red; } </style> + <div id=target></div> + )HTML"); + Compositor().BeginFrame(); + + // Test hover over target element on active page + GetPage().SetActive(true); + + // Set the mouse position over the target element + GetEventHandler().HandleMouseMoveEvent( + CreateTestMouseEvent(WebInputEvent::Type::kMouseMove, + gfx::PointF(20, 20)), + Vector<WebMouseEvent>(), Vector<WebMouseEvent>()); + GetEventHandler().MarkHoverStateDirty(); + GetEventHandler().RecomputeMouseHoverStateIfNeeded(); + + LayoutObject* target = + GetDocument().getElementById(AtomicString("target"))->GetLayoutObject(); + Color hover_color = + target->Style()->VisitedDependentColor(GetCSSPropertyBackgroundColor()); + // :hover pseudo-class should match when the pointer is over the element. + EXPECT_EQ("rgb(255, 0, 0)", hover_color.SerializeAsCSSColor()); + + // Move mouse position away from the target element to reset target background + // color to gray. + GetEventHandler().HandleMouseMoveEvent( + CreateTestMouseEvent(WebInputEvent::Type::kMouseMove, + gfx::PointF(200, 200)), + Vector<WebMouseEvent>(), Vector<WebMouseEvent>()); + GetEventHandler().MarkHoverStateDirty(); + GetEventHandler().RecomputeMouseHoverStateIfNeeded(); + hover_color = + target->Style()->VisitedDependentColor(GetCSSPropertyBackgroundColor()); + EXPECT_EQ("rgb(128, 128, 128)", hover_color.SerializeAsCSSColor()); + + // Move mouse back over element but with page inactive. + GetPage().SetActive(false); + GetEventHandler().HandleMouseMoveEvent( + CreateTestMouseEvent(WebInputEvent::Type::kMouseMove, + gfx::PointF(20, 20)), + Vector<WebMouseEvent>(), Vector<WebMouseEvent>()); + GetEventHandler().MarkHoverStateDirty(); + GetEventHandler().RecomputeMouseHoverStateIfNeeded(); + hover_color = + target->Style()->VisitedDependentColor(GetCSSPropertyBackgroundColor()); + // Same behavior is expected regardless of Page Active state. Per + // W3C Pointer Events, §â€¯4.4.6 “mouseenter” + EXPECT_EQ("rgb(255, 0, 0)", hover_color.SerializeAsCSSColor()); +} } // namespace blink
diff --git a/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.cc b/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.cc index adaf6c5c..183bf8b 100644 --- a/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.cc +++ b/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.cc
@@ -163,7 +163,7 @@ } if (flex_line_index > 0) { - CHECK_LE(flex_line_index - 1, main_gaps_.size()); + CHECK_LT(flex_line_index - 1, main_gaps_.size()); // We increment the `RangeOfCrossGapsAfter` for the previous line, since // the CrossGaps that start at this line fall "after" the previous line. main_gaps_[flex_line_index - 1].IncrementRangeOfCrossGapsAfter(
diff --git a/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.h b/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.h index d499b32..bbaee4d3 100644 --- a/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.h +++ b/third_party/blink/renderer/core/layout/flex/flex_gap_accumulator.h
@@ -225,6 +225,10 @@ content_main_end_ = content_main_end; } + void SetFirstFlexLineProcessedIndex(wtf_size_t index) { + first_flex_line_processed_index_ = index; + } + const Vector<MainGap>& MainGaps() const { return main_gaps_; } // In the flex algorithm, there are some cases where we need to suppress a row
diff --git a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc index 905252b8..ba130b2 100644 --- a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc +++ b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
@@ -2064,6 +2064,18 @@ is_column_ ? container_builder_.BorderScrollbarPadding().block_end : container_builder_.BorderScrollbarPadding().inline_end; + // The gap accumulator expects flex lines in ascending order, which isn't + // guaranteed during column flex fragmentation. Pre-set the minimum flex line + // index for this fragment. + if (gap_accumulator && is_column_) { + for (wtf_size_t i = 0; i < flex_lines->size(); i++) { + if (!(*flex_lines)[i].has_seen_all_children) { + gap_accumulator->SetFirstFlexLineProcessedIndex(i); + break; + } + } + } + for (auto entry = item_iterator.NextItem(broke_before_row); FlexItemData* flex_item = entry.flex_item; entry = item_iterator.NextItem(broke_before_row)) {
diff --git a/third_party/blink/renderer/core/layout/gap/gap_geometry.cc b/third_party/blink/renderer/core/layout/gap/gap_geometry.cc index ffce4508e..46ccf08 100644 --- a/third_party/blink/renderer/core/layout/gap/gap_geometry.cc +++ b/third_party/blink/renderer/core/layout/gap/gap_geometry.cc
@@ -243,19 +243,23 @@ bool is_above_main_gap) { CHECK(!intersections.empty()); - // The cross gap size depends on which side of the main gap the - // intersection comes from. "Above" corresponds to the flex line at - // `gap_index`, while "below" corresponds to the next line. - const LayoutUnit cross_gap_size = is_above_main_gap - ? cross_gap_size_above.value() - : cross_gap_size_below.value(); - - // Two consecutive intersections produce overlapping decorations when their - // distance is less than `cross_gap_size`. - const bool overlaps_with_intersection = - intersections.size() > 1 && - (intersection_offset - intersections.back().GetOffset() < - cross_gap_size); + // Two consecutive intersections form overlap windows when their cross gaps + // overlap. Because intersections are placed in the middle of a cross gap, + // we'll have to go to the end edge of the previous intersection and the + // start edge of the current intersection to accurately determine their + // overlap status. + bool overlaps_with_intersection = false; + if (intersections.size() > 1) { + const LayoutUnit current_cross_gap_size = + is_above_main_gap ? cross_gap_size_above.value() + : cross_gap_size_below.value(); + const LayoutUnit prev_cross_gap_size = + intersections.back().IsAboveMainGap() ? cross_gap_size_above.value() + : cross_gap_size_below.value(); + overlaps_with_intersection = + (intersection_offset - intersections.back().GetOffset() < + (prev_cross_gap_size + current_cross_gap_size) / 2); + } if (overlaps_with_intersection) { if (intersections.back().IsOverlapWindowOpen()) {
diff --git a/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc index 5edcf1c..e4d450e3 100644 --- a/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc +++ b/third_party/blink/renderer/core/loader/anchor_element_interaction_test.cc
@@ -33,6 +33,7 @@ #include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h" #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" #include "ui/gfx/geometry/point_f.h" @@ -881,6 +882,12 @@ }; TEST_F(AnchorElementInteractionViewportHeuristicsTest, BasicTest) { + // When this is enabled, host receives an additional PointerOver call that it + // does not expect. This test should account for mouse hover over both active + // and inactive pages. https://issues.chromium.org/issues/488090081 + ScopedSyntheticMouseHoverOverInactivePageForTest + disable_synthetic_mouse_hover_over_inactive_page(false); + String body = R"HTML( <body style="margin: 0px"> <div style="height: 200px"></div> @@ -1033,6 +1040,12 @@ TEST_F(AnchorElementInteractionViewportHeuristicsTest, PointerDownImmediatelyAfterScroll) { + // When this is enabled, host receives an additional PointerOver call that it + // does not expect. This test should account for mouse hover over both active + // and inactive pages. https://issues.chromium.org/issues/488090081 + ScopedSyntheticMouseHoverOverInactivePageForTest + disable_synthetic_mouse_hover_over_inactive_page(false); + String source(KURL("https://example.com")); SimRequest main_resource(source, "text/html"); LoadURL(source); @@ -1074,6 +1087,12 @@ TEST_F(AnchorElementInteractionViewportHeuristicsTest, EagerHeuristicsTriggerForAnchorsInViewport) { + // When this is enabled, host receives an additional PointerOver call that it + // does not expect. This test should account for mouse hover over both active + // and inactive pages. https://issues.chromium.org/issues/488090081 + ScopedSyntheticMouseHoverOverInactivePageForTest + disable_synthetic_mouse_hover_over_inactive_page(false); + String body = R"HTML( <body style="margin: 0px"> <div style="height: 50px"></div> @@ -1107,6 +1126,12 @@ TEST_F(AnchorElementInteractionViewportHeuristicsTest, PredictorDisabledIfAllAnchorsNotSampledIn) { + // When this is enabled, host receives an additional PointerOver call that it + // does not expect. This test should account for mouse hover over both active + // and inactive pages. https://issues.chromium.org/issues/488090081 + ScopedSyntheticMouseHoverOverInactivePageForTest + disable_synthetic_mouse_hover_over_inactive_page(false); + std::map<std::string, std::string> params = GetParamsForNavigationPredictor(); params["random_anchor_sampling_period"] = "2"; base::test::ScopedFeatureList feature_list;
diff --git a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.cc b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.cc index 4cea085..351a1ef 100644 --- a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.cc +++ b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.cc
@@ -18,8 +18,8 @@ DCHECK(element->isConnected()); element->SetOverscrollContainer(container_); overscroll_members_.push_back(element); + container_->SetNeedsReattachLayoutTree(); needs_dom_sort_ = overscroll_members_.size() > 1; - needs_layout_tree_rebuild_ = true; } const VectorOf<Element>& OverscrollAreaTracker::DOMSortedElements() { @@ -40,7 +40,6 @@ } overscroll_members_.clear(); needs_dom_sort_ = false; - needs_layout_tree_rebuild_ = true; } void OverscrollAreaTracker::RemoveOverscroll(Element* element) { @@ -48,7 +47,6 @@ element->ClearOverscrollContainer(); Erase(overscroll_members_, element); needs_dom_sort_ = needs_dom_sort_ && overscroll_members_.size() > 1; - needs_layout_tree_rebuild_ = true; } void OverscrollAreaTracker::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.h b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.h index b611e0f..027f4fac 100644 --- a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.h +++ b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker.h
@@ -29,9 +29,6 @@ const VectorOf<Element>& DOMSortedElements(); - bool NeedsLayoutTreeRebuild() const { return needs_layout_tree_rebuild_; } - void ClearNeedsLayoutTreeRebuild() { needs_layout_tree_rebuild_ = false; } - void Trace(Visitor*) const override; private: @@ -41,7 +38,6 @@ VectorOf<Element> overscroll_members_; bool needs_dom_sort_ = false; - bool needs_layout_tree_rebuild_ = false; }; } // namespace blink
diff --git a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker_test.cc b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker_test.cc index 0de15259..d7dde73 100644 --- a/third_party/blink/renderer/core/overscroll/overscroll_area_tracker_test.cc +++ b/third_party/blink/renderer/core/overscroll/overscroll_area_tracker_test.cc
@@ -226,6 +226,14 @@ EXPECT_EQ(c1tracker.DOMSortedElements()[0], menu0); EXPECT_EQ(c1tracker.DOMSortedElements()[1], menu1); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container1->GetLayoutObject()); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container1->GetLayoutObject()); mark_container(container2); // container1 and container2 are containers. @@ -235,6 +243,14 @@ EXPECT_EQ(c1tracker.DOMSortedElements()[0], menu1); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 1); EXPECT_EQ(c2tracker.DOMSortedElements()[0], menu0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container2->GetLayoutObject()); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container1->GetLayoutObject()); clear_container(container1); mark_container(container0); @@ -245,6 +261,14 @@ EXPECT_EQ(c1tracker.DOMSortedElements().size(), 0); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 1); EXPECT_EQ(c2tracker.DOMSortedElements()[0], menu0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container2->GetLayoutObject()); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container0->GetLayoutObject()); clear_container(container2); // container0 is a container. @@ -254,6 +278,14 @@ EXPECT_EQ(c0tracker.DOMSortedElements()[1], menu1); EXPECT_EQ(c1tracker.DOMSortedElements().size(), 0); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container0->GetLayoutObject()); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container0->GetLayoutObject()); clear_container(container0); // There are no containers. @@ -261,6 +293,8 @@ EXPECT_EQ(c0tracker.DOMSortedElements().size(), 0); EXPECT_EQ(c1tracker.DOMSortedElements().size(), 0); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent), nullptr); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent), nullptr); mark_container(container0); mark_container(container1); @@ -272,6 +306,14 @@ EXPECT_EQ(c1tracker.DOMSortedElements()[0], menu1); EXPECT_EQ(c2tracker.DOMSortedElements().size(), 1); EXPECT_EQ(c2tracker.DOMSortedElements()[0], menu0); + EXPECT_EQ(menu0->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container2->GetLayoutObject()); + EXPECT_EQ(menu1->GetPseudoElement(kPseudoIdOverscrollAreaParent) + ->GetLayoutObject() + ->Parent(), + container1->GetLayoutObject()); } TEST_F(OverscrollAreaTrackerTest, OverscrollElementsAreDOMSorted) { @@ -364,6 +406,34 @@ EXPECT_EQ(tracker.DOMSortedElements()[0], menu1); } +TEST_F(OverscrollAreaTrackerTest, OverscrollAreaRebuildLayoutTree) { + SetInnerHTML(R"HTML( + <div id="container" overscrollcontainer> + <div><div id="menu"></div></div> + </div> + <button id="button" command="toggle-overscroll" commandfor="menu"></button> + )HTML"); + + UpdateAllLifecyclePhasesForTest(); + + auto* container = GetDocument().getElementById(AtomicString("container")); + auto* button = GetDocument().getElementById(AtomicString("button")); + auto* menu = GetDocument().getElementById(AtomicString("menu")); + + button->SetAttributeWithoutValidation(html_names::kCommandAttr, "--foo"); + UpdateAllLifecyclePhasesForTest(); + ASSERT_FALSE(menu->GetPseudoElement(kPseudoIdOverscrollAreaParent)); + button->SetAttributeWithoutValidation(html_names::kCommandAttr, + "toggle-overscroll"); + UpdateAllLifecyclePhasesForTest(); + + PseudoElement* overscroll_area_parent = + menu->GetPseudoElement(kPseudoIdOverscrollAreaParent); + ASSERT_TRUE(overscroll_area_parent->GetLayoutObject()); + ASSERT_EQ(overscroll_area_parent->GetLayoutObject()->Parent(), + container->GetLayoutObject()); +} + TEST_F(OverscrollAreaTrackerTest, MultipleIdsReferToFirstElement) { SetInnerHTML(R"HTML( <div id="container" overscrollcontainer>
diff --git a/third_party/blink/renderer/core/paint/clip_path_clipper.cc b/third_party/blink/renderer/core/paint/clip_path_clipper.cc index 802d76a..6093b1a 100644 --- a/third_party/blink/renderer/core/paint/clip_path_clipper.cc +++ b/third_party/blink/renderer/core/paint/clip_path_clipper.cc
@@ -135,7 +135,21 @@ ElementAnimations* element_animations = element->GetElementAnimations(); DCHECK(element_animations || status == CompositedPaintStatus::kNotComposited); if (element_animations) { - element_animations->SetCompositedClipPathStatus(status); + CompositedPaintStatus prev_status = + element_animations->CompositedClipPathStatus(); + + if (element_animations->SetCompositedClipPathStatus(status)) { + // In very rare cases, this is not done, leaving a stale paint worklet. + // This can happen if a descendant composited transform animation + // invalidates this animation, but its first keyframe happens to not + // actually mutate the transform yet. Most of the time this has no effect. + if (prev_status == CompositedPaintStatus::kComposited && + status == CompositedPaintStatus::kNotComposited) { + element->GetLayoutObject() + ->SetShouldDoFullPaintInvalidationWithoutLayoutChange( + PaintInvalidationReason::kStyle); + } + } } } @@ -193,16 +207,7 @@ // TODO(crbug.com/454365238): Fallback point for cc clip-path animations, should // be annotated with a histogram. -bool ClipPathAnimationShouldFallback(const LayoutObject& layout_object, - bool is_in_block_fragmentation) { - // If not all the fragments of this layout object have been populated yet, it - // will be impossible to tell if a composited clip path animation is possible - // or not based only on the layout object. Exclude the possibility if we're - // fragmented. - if (is_in_block_fragmentation) { - return true; - } - +bool ClipPathAnimationShouldFallback(const LayoutObject& layout_object) { // We also shouldn't composite in the case of will-change: contents. if (layout_object.StyleRef().SubtreeWillChangeContents()) { return true; @@ -347,7 +352,7 @@ } void ClipPathClipper::FallbackClipPathAnimationIfNecessary( const LayoutObject& layout_object, - bool is_in_block_fragmentation) { + bool should_force_fallback) { if (!RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled()) { return; } @@ -359,8 +364,7 @@ base::debug::DumpWithoutCrashing(); } - if (ClipPathAnimationShouldFallback(layout_object, - is_in_block_fragmentation)) { + if (should_force_fallback || ClipPathAnimationShouldFallback(layout_object)) { SetCompositeClipPathStatus(layout_object.GetNode(), CompositedPaintStatus::kNotComposited); }
diff --git a/third_party/blink/renderer/core/paint/clip_path_clipper.h b/third_party/blink/renderer/core/paint/clip_path_clipper.h index fbd3d6b..bb467d43 100644 --- a/third_party/blink/renderer/core/paint/clip_path_clipper.h +++ b/third_party/blink/renderer/core/paint/clip_path_clipper.h
@@ -64,13 +64,25 @@ CompositedStateResolutionType state); // Sets a potential composited clip path animation to be not composited. - // Called during pre-paint, currently in the case of fragmented layouts. + // Called early during pre-paint, before the paint properties have finished + // populating. At this point, some state that would normally be a fallback + // reason in CheckCanStartAnimationOnCompositor is not available. + // Additionally, there are some reasons where we need to force a fallback + // immediately rather than waiting to be set compositor pending again. This + // method covers those cases. + // TODO(crbug.com/488268869): The handling of fragmentation here shouldn't be + // necessary, layout has already produced the required information. + // TODO(crbug.com/489619758): Most of these reasons should be moved to be + // handled elsewhere. This method has been the source of bugs, see + // crbug.com/488090095 static void FallbackClipPathAnimationIfNecessary( const LayoutObject& layout_object, - bool is_in_block_fragmentation); + bool should_force_fallback = false); // Called by the paint property tree builder if a maximum clip area can't be // sufficiently determined. + // TODO(crbug.com/489619758): This should be merged with + // FallbackClipPathAnimationIfNecessary or removed. static void FallbackClipPathAnimationDueToAbsentBounds( const LayoutObject& layout_object);
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc index 265c843..d51510ab 100644 --- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc +++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -2457,8 +2457,7 @@ } void FragmentPaintPropertyTreeBuilder::UpdateClipPathClip() { - if (NeedsPaintPropertyUpdate() || - !ClipPathClipper::ClipPathStatusResolved(object_)) { + if (NeedsPaintPropertyUpdate()) { DCHECK(!precise_clip_path_rect_.has_value()); if (NeedsClipPathClipOrMask(object_, /*fully_resolve_composited_state=*/true)) { @@ -4139,7 +4138,7 @@ IsA<LayoutBox>(object_) && (To<LayoutBox>(object_).PhysicalFragmentCount() > 1); ClipPathClipper::FallbackClipPathAnimationIfNecessary( - object_, is_in_fragment_container); + object_, /* should_force_fallback = */ is_in_fragment_container); UpdatePaintingLayer(); UpdateFragmentData();
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc index 07204b2..be346a0 100644 --- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc +++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -623,6 +623,13 @@ if (!pre_paint_info->fragment_data) return; } else if (object.IsFragmentLessBox()) { + // CC Clip-path animations expect paint property updates to go through to + // update the composited paint status. However, because this box doesn't + // paint, we can safely mark the animation as non-composited. This is done + // for correctness and should have no material impact, at least until off- + // -screen / non-visible animations are handled more appropriately. + ClipPathClipper::FallbackClipPathAnimationIfNecessary( + object, /* should_force_fallback = */ true); return; }
diff --git a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc index 89315d97..48b79be 100644 --- a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc +++ b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc
@@ -35,21 +35,46 @@ namespace blink { +// Enum for various updates that can occur during a document lifecycle run. Each +// bit represents an independent bit of state that could change relevant to a +// running clip-path animation/transition. enum UpdatesNeededForNextFrame { kNoMainFrameUpdates = 0, + + // Means that status before pre-paint will be kNeedsRepaint. At the moment, + // this does not necessarily verify that RecalcCompositedStatus was actually + // called, only that the state is marked for repainting. kPaintStatusReset = 1 << 0, kNeedsPaintPropertyUpdate = 1 << 2, + + // Checks whether ChromeClient::ScheduleAnimation is called. This is used to + // determined whether an animation is causing new main frames or not. kScheduledAnimationUpdate = 1 << 3, + // When true, checks for !DisplayItemClient::IsValid(). When that check is + // true, the animation's owning PaintLayer will also be marked for repaint + // (not explicitly checked), and so will the cached paint result of the + // ClipPathMask. This should always be good enough to ensure a new + // PaintWorkletDeferredImage is actually painted, though this is not + // explicitly checked. kPaintInvalidated = 1 << 4, + // Used in the case where an animation is running on main thread but does not + // result in style mutation (e.g., because the two nearest keyfranes are + // equal, the animation is discrete, or because the animation is using a step + // timing function) kMainThreadAnimationFrameNoInvalidation = kScheduledAnimationUpdate, + // The above, but the animation *did* mutate style. kMainThreadAnimationFrame = kMainThreadAnimationFrameNoInvalidation | kNeedsPaintPropertyUpdate | kPaintInvalidated, + // When a clip path animation is set pending, we expect a 'full-fat' update + // where everything is dirtied. kAllUpdates = kMainThreadAnimationFrame | kPaintStatusReset, + // Used in the case where there's a paint property change on a running cc + // animation. kMainThreadPropertyInvalidation = kNeedsPaintPropertyUpdate | kPaintInvalidated }; @@ -83,6 +108,12 @@ raw_ptr<cc::AnimationHost> animation_host_; }; +// TODO(clchambers): This should probably be subclassed at some point from +// ObjectInvalidatorTest, since it has most of the machinery we use. Either the +// animation-specific code can be added to RenderingTestChromeClient, or instead +// we can compromise and just call PendingAnimations more directly since it +// should be the same thing. Be sure to cleanup the friend class decl in +// DisplayItemClient when this is done. class ClipPathPaintDefinitionTest : public PageTestBase { public: ClipPathPaintDefinitionTest() = default; @@ -110,6 +141,14 @@ return nullptr; } + // The next 4 methods are easy short-cuts for checking various cc clip path + // invariants are held throughout the lifecycle, to make this file a bit more + // readable at the cost of problem location in the test being 2-3 frames down + // from the top of a stack trace. Right now, we check for animation updates, + // composited paint status updates (naive checking for kNeedsRepaint), paint + // property updates, and paint updates. View the code of these methods for a + // description of what those invariants are and why they are enforced. + void EnsureCCClipPathInvariantsHoldStyleAndLayout( CompositedPaintStatus status, Element* element, @@ -119,10 +158,26 @@ LayoutObject* lo = element->GetLayoutObject(); - // Changes to a compositable animation should set NeedsPaintPropertyUpdate. + // Changes to a compositable clip-path animation should set + // NeedsPaintPropertyUpdate. This is because we force a switch from + // ClipPathClip (ordinary clipping) to ClipPathMask with its associated + // ClipPathMaskEffect (SVG clipping). If this is not done, bad things + // happen. EXPECT_EQ(lo->NeedsPaintPropertyUpdate(), !!(updates & kNeedsPaintPropertyUpdate)); - // Changes to a compositable animation should set kNeedsRepaint. + + // Changes to a compositable animation should set kNeedsRepaint. This is + // because for clip-path animations, the status value is cached to avoid + // repeatedly calling the heavy check CheckCanStartAnimationOnCompositor in + // the pre-paint tree walk, which can be very frequent and will occur during + // hit tests. Note that this behavior, like the above, is not universal for + // NPW animations. background-color will recompute its status at paint time + // and this is not necessarily an issue because full paint invalidation on + // an animated element should be fairly infrequent. Note that the composited + // paint status can even be checked during the pre-paint tree walk even if + // NeedsPaintPropertyUpdate is value, because InitPaintProperties checks the + // status to ensure paint properties are populated for cc clip paths even + // when they wouldn't ordinarily be needed. EXPECT_EQ(element->GetElementAnimations()->CompositedClipPathStatus(), (!(updates & kPaintStatusReset) || status == CompositedPaintStatus::kNoAnimation) @@ -142,7 +197,17 @@ GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( DocumentUpdateReason::kTest); - // Composited paint status should be resolved by this point. + // Check the element's DisplayItemClient for invalidation - if we're + // expecting an repaint, then IsValid() will be false. For a composited clip + // path animation, this means that a new PaintWorkletDeferredImage is + // created (or removed, for a fallback), which is necessary to ensure that + // the latest keyframes are on the compositor and that we don't get stale + // paint. + EXPECT_EQ(static_cast<DisplayItemClient*>(lo)->IsValid(), + !(updates & kPaintInvalidated)); + + // Composited paint status should be resolved by this point. If it hasn't + // been, that means paint properties haven't been updated. EXPECT_EQ(element->GetElementAnimations()->CompositedClipPathStatus(), status); @@ -156,34 +221,56 @@ switch (status) { case CompositedPaintStatus::kNoAnimation: case CompositedPaintStatus::kNotComposited: - // GetAnimationIfCompositable should return nothing in this - // circumstance. + // For a fallback, the cached clip path animation candidate should be + // cleared so we don't hold on to stale references. This also means that + // the presence of a valid aniamtion in paint is a guarantee that we're + // in the composited path. EXPECT_EQ(ClipPathClipper::GetClipPathAnimation(*lo), nullptr); // If a clip path is non-composited or non-existent, then the clip path - // mask should not be set. If it is, it can cause a crash. + // mask should not be set. If it is, it can cause a crash. Note that + // this is not necessarily true, SVG clips can set this without a cc + // clip path animation, which is not tested here. EXPECT_TRUE(!lo->FirstFragment().PaintProperties() || !lo->FirstFragment().PaintProperties()->ClipPathMask()); // Non-composited animations SHOULD still be causing animation updates. // Additionally, style/layout code seems to trigger animation update for - // the first frame after an animation cancel. + // the first frame after an animation cancel. Too few updates means a + // fallback may not have been done properly (ie, paint is stuck.) EXPECT_EQ(!!(updates & kScheduledAnimationUpdate), Client()->HasScheduledAnimation()); + // If the animation is still running on cc, it means that something went + // wrong with a fallback. kNotComposited should always be coincident + // with a pending cancel. + EXPECT_FALSE(animation->HasActiveAnimationsOnCompositor()); break; case CompositedPaintStatus::kComposited: - // GetAnimationIfCompositable should return the given animation, if it - // is compositable. + // A compositable animation should always be cached in + // ElementAnimations. We do this primarily because finding it every time + // is an unnecessary expense. It requires walking through all keyframe + // effects associated with an element until an animation that mutates + // clip-paths is found. EXPECT_EQ(ClipPathClipper::GetClipPathAnimation(*lo), animation); // Composited clip-path animations depend on ClipPathMask() being set. + // If this is not true, the animation will have no output (no + // PaintWorkletDeferredImage for CC to update). EXPECT_TRUE(lo->FirstFragment().PaintProperties()->ClipPathMask()); // Composited clip-path animations shouldn't cause further animation - // updates after the first paint. + // updates after the first paint. Too many animation updates + // mean that we're causing too many unnecessary main frames, undermining + // perf. EXPECT_EQ(!!(updates & kScheduledAnimationUpdate), Client()->HasScheduledAnimation()); + // The animation should be have been set up for compositing during + // PreCommit. + EXPECT_TRUE(animation->HasActiveAnimationsOnCompositor()); break; case CompositedPaintStatus::kNeedsRepaint: // kNeedsRepaint is only valid before pre-paint has been run. NOTREACHED(); } + + // Clear paint invalidation reasons + static_cast<DisplayItemClient*>(lo)->Validate(); } // Given an element with a *CSS* defined compositable clip-path animation with @@ -192,21 +279,24 @@ // further testing. Animation* StartAndVerifyEligibleClipPathAnimation( Element* element, - int time_to_first_style_change_ms, - int init_time_ms = 0) { + int time_to_first_style_change_ms) { + // Set up timing + PAC so that animation servicing works as expected. InitPaintArtifactCompositor(); - UpdateAndAdvanceTimeTo(init_time_ms); + UpdateAndAdvanceTimeTo(0); + // A CSS animation object won't be created until the lifecycle runs for the + // first time. Because we need the animation object to verify invariants, we + // run only style and layout first. EnsureCCClipPathInvariantsHoldStyleAndLayout( CompositedPaintStatus::kComposited, element, UpdatesNeededForNextFrame::kAllUpdates); + // With the animation object created, we can then run paint and verify + // animation-specific invariants, such as HasActiveAnimationsOnCompositor. Animation* animation = GetFirstAnimation(element); - EnsureCCClipPathInvariantsHoldThroughoutPainting( CompositedPaintStatus::kComposited, element, animation, UpdatesNeededForNextFrame::kAllUpdates); - StartAllWaitingAnimationsOnCompositor(element, 0); // Tick the animation before the first style change first. In cases of @@ -216,13 +306,13 @@ // to check both - many things other than animations can cause lifecycle // updates (e.g. hit tests, javascript events, etc). Even if we're running // lifecycle anyway, we don't want to be causing unnecessary painting. - UpdateAndAdvanceTimeTo(init_time_ms + time_to_first_style_change_ms / 2); + UpdateAndAdvanceTimeTo(time_to_first_style_change_ms / 2); EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus::kComposited, element, animation, UpdatesNeededForNextFrame::kNoMainFrameUpdates); // Style change, but no updates. - UpdateAndAdvanceTimeTo(init_time_ms + time_to_first_style_change_ms + 1); + UpdateAndAdvanceTimeTo(time_to_first_style_change_ms + 1); EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus::kComposited, element, animation, UpdatesNeededForNextFrame::kNoMainFrameUpdates); @@ -230,6 +320,58 @@ return animation; } + // Given an element with a *CSS* defined *non*-compositable clip-path + // animation (due to one or more disqualifiers), start the animation and + // advance to the first style change to ensure all invariants hold (ie, that + // the behavior should be the same as main thread except for the extra work to + // verify status from kNoAnimation -> kNeedsRepaint - > kNotComposited). The + // animation pointer is returned for further testing. + Animation* StartAndVerifyNonEligibleClipPathAnimation( + Element* element, + int time_to_first_style_change_ms) { + // Set up timing + PAC so that animation servicing works as expected. + InitPaintArtifactCompositor(); + UpdateAndAdvanceTimeTo(0); + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kNotComposited, element, + UpdatesNeededForNextFrame::kAllUpdates); + + // Animation is not composited. + Animation* animation = GetFirstAnimation(element); + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + // For the case of scheduled animation update, it may seem weird that we + // schedule an update even when there is no keyframe change, but, if you + // were to check the update time (we do not currently do this), for a + // main-thread animation the update would be scheduled for the time to the + // next timing change. The scheduled animation update here is, conceptually, + // the same as the last one, just advanced by time_to_first_style_change_ms + // / 2. + UpdateAndAdvanceTimeTo(time_to_first_style_change_ms / 2); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrameNoInvalidation); + + // The style change here SHOULD invalidate both paint properties and paint. + // Clip paths (on main thread) are accumulated as PaintOps when painting the + // element's associated PaintLayer, and so a change to the clip means + // re-painting and re-rasterizing the entire layer. If this does not happen, + // it probably means something in the non-invalidation logic for clip paths + // is too aggressive. See AdjustForCompositableAnimationPaint (note for more + // complex scenarios, this can also happen with the clip-path paint + // hierarchy has become inconsistent, see crbug.com/480422022. However, this + // method is only called by during the start of tests, so if an invariant + // breaks here, it is almost certainly in the aforementioned logic). + UpdateAndAdvanceTimeTo(time_to_first_style_change_ms + 1); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); + + return animation; + } + void EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus status, Element* element, @@ -310,6 +452,11 @@ std::unique_ptr<LayerTreeHostEmbedder> layer_tree_; }; +/* -------------------------------------------- */ +/* 1. ELIGIBLE COMPOSITABLE ANIMATION TESTS */ +/* Regression tests for known-good cases */ +/* -------------------------------------------- */ + // Test the case where there is a clip-path animation with two simple // keyframes that will not fall back to main. TEST_F(ClipPathPaintDefinitionTest, SimpleClipPathAnimationNotFallback) { @@ -464,75 +611,17 @@ StartAndVerifyEligibleClipPathAnimation(element, 1000); } -// Test the case where there is a clip-path animation with two simple -// keyframes that will not fall back to main. -TEST_F(ClipPathPaintDefinitionTest, ReverseClipPathAnimationNoUpdates) { - SetBodyInnerHTML(R"HTML( - <style> - @keyframes clippath { - 0% { - clip-path: circle(50% at 50% 50%); - } - 100% { - clip-path: circle(30% at 30% 30%); - } - } - .animation { - animation: clippath 4s steps(4, jump-end); - } - </style> - <div id ="target" style="width: 100px; height: 100px"> - </div> - )HTML"); +/* ----------------------------------------- */ +/* ANIMATION FALLBACK TESTS */ +/* For anims that fall back from the value */ +/* filter or fail standard compositability */ +/* checks in CheckCanStart*. */ +/* ----------------------------------------- */ - Element* element = GetElementById("target"); - element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - - // Init clock. - UpdateAndAdvanceTimeTo(0); - - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); - - Animation* animation = GetFirstAnimation(element); - - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - StartAllWaitingAnimationsOnCompositor(element, 0); - - // Advance animation to the 3rd frame. - UpdateAndAdvanceTimeTo(2000 + 1); - - // As usual, one expects no updates from composited animations. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kComposited, element, animation, - UpdatesNeededForNextFrame::kNoMainFrameUpdates); - - // Reverse the animation. - animation->updatePlaybackRate(-1); - - // Run lifecycle once more: animation should still be composited. Because it's - // the same animation, it shouldn't schedule an animation update. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kComposited, element, animation, - static_cast<UpdatesNeededForNextFrame>( - UpdatesNeededForNextFrame::kPaintStatusReset | - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate)); - - // Advance animation back (since it is reversed) to the 2nd frame. - UpdateAndAdvanceTimeTo(2000 + 1 + 1000 + 2); - - // Run lifecycle once more: repaints should be avoided even with negative - // playback rate. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kComposited, element, animation, - UpdatesNeededForNextFrame::kNoMainFrameUpdates); -} - -// Clip-path: initial is not composited and must fall back to main thread. +// Clip-path: initial is not composited and must fall back to main thread. This +// is done because initial results in a special keyframe value that the +// ClipPathPaintDefinition doesn't know what to do with. In future, we may +// interpret this as the same as clip-path: none, which is allowed. TEST_F(ClipPathPaintDefinitionTest, FallbackForClipPathInital) { SetBodyInnerHTML(R"HTML( <style> @@ -545,7 +634,7 @@ } } .animation { - animation: clippath 4s steps(4, jump-end); + animation: clippath 4s; } </style> <div id ="target" style="width: 100px; height: 100px"> @@ -555,157 +644,7 @@ Element* element = GetElementById("target"); element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - // Init clock. - UpdateAndAdvanceTimeTo(0); - - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kNotComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); - - Animation* animation = GetFirstAnimation(element); - - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - // Advance the animation time. - UpdateAndAdvanceTimeTo(500); - - // Animation should not be updating the composited paint status, but we do - // expect scheduled animation updates since the main thread is responsible for - // the animation. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); - - // Advance the animation time to the next meaningful frame. - UpdateAndAdvanceTimeTo(2000 + 1); - - // Main frame should still be producing frames. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - static_cast<UpdatesNeededForNextFrame>( - UpdatesNeededForNextFrame::kScheduledAnimationUpdate | - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate)); -} - -// TODO(crbug.com/449152897): Backdrop-filter and clip path paint worklet -// images are not rasterized correctly. -TEST_F(ClipPathPaintDefinitionTest, FallbackForCoincidentBackdropFilter) { - SetBodyInnerHTML(R"HTML( - <style> - @keyframes clippath { - 0% { - clip-path: circle(30% at 20% 20%); - } - 100% { - clip-path: circle(30% at 30% 30%); - } - } - .animation { - backdrop-filter: invert(1); - animation: clippath 4s steps(4, jump-end); - } - </style> - <div id ="target" style="width: 100px; height: 100px"> - </div> - )HTML"); - - Element* element = GetElementById("target"); - element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - - // Init clock. - UpdateAndAdvanceTimeTo(0); - - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kNotComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); - - Animation* animation = GetFirstAnimation(element); - - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - // Advance the animation time. - UpdateAndAdvanceTimeTo(500); - - // Animation should not be updating the composited paint status, but we do - // expect scheduled animation updates since the main thread is responsible for - // the animation. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); - - // Advance the animation time to the next meaningful frame. - UpdateAndAdvanceTimeTo(2000 + 1); - - // Main thread should still be producing frames. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - static_cast<UpdatesNeededForNextFrame>( - UpdatesNeededForNextFrame::kScheduledAnimationUpdate | - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate)); -} - -// Clip-path: none requires the cull rect, but perspective makes the cull rect -// infinite, as a result, we must fall back in this case. -TEST_F(ClipPathPaintDefinitionTest, FallbackForClipPathNoneWithPerspective) { - SetBodyInnerHTML(R"HTML( - <style> - @keyframes clippath { - 0% { - clip-path: circle(10% at 30% 30%); - } - 100% { - clip-path: none; - } - } - .animation { - animation: clippath 4s infinite; - } - </style> - <div style="transform: perspective(200px);"> - <div id ="target" style="width: 100px; height: 100px;"> - </div> - </div> - )HTML"); - - Element* element = GetElementById("target"); - element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - - // Init clock. - UpdateAndAdvanceTimeTo(0); - - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kNotComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); - - Animation* animation = GetFirstAnimation(element); - - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - // Advance the animation time. - UpdateAndAdvanceTimeTo(500); - - // Animation should not be updating the composited paint status, but we do - // expect scheduled animation updates since the main thread is responsible for - // the animation. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); - - // Advance the animation time to the next meaningful frame. - UpdateAndAdvanceTimeTo(2000 + 1); - - // Main frame should still be producing frames. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - static_cast<UpdatesNeededForNextFrame>( - UpdatesNeededForNextFrame::kScheduledAnimationUpdate | - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate)); + StartAndVerifyNonEligibleClipPathAnimation(element, 2000); } // <br> cannot be composited due to it not supporting paint properties, so we @@ -734,26 +673,520 @@ Element* element = GetElementById("target"); container->setAttribute(html_names::kClassAttr, AtomicString("animation")); - // Init clock. - UpdateAndAdvanceTimeTo(0); + StartAndVerifyNonEligibleClipPathAnimation(element, 1000); +} +/* ----------------------------------------- */ +/* SPECIAL ANIMATION FALLBACK TESTS */ +/* For anims that fall back during pre-paint */ +/* ----------------------------------------- */ + +// These cases primarily are here to prevent broken painting or stuck +// animations. Most fallback cases are either expected to set the animation +// pending (in which case, the ordinary handling is sufficient), or are not +// actually a reason an animation can't continue to run on the compositor. +// Clip-path animations have special handling to ensure cases that absolutely +// need to fall back are handled with care. + +// These tests exist as pairs, one for the "normal" fallback case, and one for +// the case where the disqualifying factor is added after the animation has +// already started. This is important to test to ensure that animations that +// start composited but then have a disqualifying factor added later properly +// fall back, rather than getting stuck in a broken state. ie, that the paint +// status and the animation composinting state are updated atomically. + +// TODO(clchambers): I hope one day most of these tests won't exist. +// will-change: contents doesn't even apply to NPW-based clip-path animations, +// since we don't need render surfaces. The only case we really care about is +// when a clip-path animation is shared with a transform animation (which may +// need a surface). Backdrop-filter should just be fixed, since masks already +// work with backdrop filter, this behavior was just never moved to svg clips. +// In the long term, fragmentation for clip-paths should simply be properly +// defined so we don't need to fall back (see crbug.com/40241353), but in the +// short term, it needs to be handled better than having a code block in the +// middle of the pre-paint tree walk. See crbug.com/488268869. Perspective +// transforms / child transform anims with clip-path: none will always be an +// issue until/unless this feature is completely rewritten to clip differently +// however, for various perf reasons. (ie, we don't want to allocate unbounded +// mask tiles on cc). Though I envision a better way to handle that case. + +// TODO(crbug.com/449152897): Backdrop-filter and clip path paint worklet +// images are not rasterized correctly. We fall back in this case to prevent +// broken painting. +TEST_F(ClipPathPaintDefinitionTest, FallbackForCoincidentBackdropFilter) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(30% at 20% 20%); + } + 100% { + clip-path: circle(30% at 30% 30%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .bdfilter { + backdrop-filter: invert(1); + } + </style> + <div id ="target" style="width: 100px; height: 100px"> + </div> + )HTML"); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, + AtomicString("animation bdfilter")); + + StartAndVerifyNonEligibleClipPathAnimation(element, 1000); +} + +// This is a variation of the above test. The backdrop-filter is added later, +// rather than immediately. This ensures the animation is still functioning +// properly in this case. +TEST_F(ClipPathPaintDefinitionTest, FallbackForLateBackdropFilter) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(30% at 20% 20%); + } + 100% { + clip-path: circle(30% at 30% 30%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .bdfilter { + backdrop-filter: invert(1); + } + </style> + <div id ="target" style="width: 100px; height: 100px"> + </div> + )HTML"); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); + + // Advance the animation time and add a backdrop-filter. + UpdateAndAdvanceTimeTo(1250); + element->setAttribute(html_names::kClassAttr, + AtomicString("animation bdfilter")); + + // Next main frame should proceed ordinarily EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kNotComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); + CompositedPaintStatus::kComposited, element, + UpdatesNeededForNextFrame::kMainThreadPropertyInvalidation); - Animation* animation = GetFirstAnimation(element); - + // However, the animation will fall back during pre-paint. EnsureCCClipPathInvariantsHoldThroughoutPainting( CompositedPaintStatus::kNotComposited, element, animation, UpdatesNeededForNextFrame::kAllUpdates); - UpdateAndAdvanceTimeTo(500); + // Animation should still producve frames on main as normal + UpdateAndAdvanceTimeTo(2001); - // Animation should still run, but the composited paint status should not - // change. + // Main thread should still be producing frames. EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); +} + +// When clip-path: none exists as part of an animation, we use the cull rect to +// constrain the animation bounds. This is done for perf reasons. However, with +// a perspective transform, the CullRectUpdater will early-out as it can't +// estimate the maximum painting bounds. Because of this, we have to fall back - +// and we have to do it before the CullRectUpdater even runs. +TEST_F(ClipPathPaintDefinitionTest, FallbackForClipPathNoneWithPerspective) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(10% at 30% 30%); + } + 75% { + clip-path: circle(30% at 30% 30%); + } + 100% { + clip-path: none; + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .perspectivetf { + transform: perspective(200px); + } + </style> + <div id="parent"> + <div id="target" style="width: 100px; height: 100px;"> + </div> + </div> + )HTML"); + + Element* parent = GetElementById("parent"); + Element* element = GetElementById("target"); + parent->setAttribute(html_names::kClassAttr, AtomicString("perspectivetf")); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + StartAndVerifyNonEligibleClipPathAnimation(element, 1000); +} + +// This is a variation of the above test. The perspective transform is added +// later, rather than immediately. This ensures the animation is still +// functioning properly in this case. +TEST_F(ClipPathPaintDefinitionTest, + FallbackForClipPathNoneWithDelayedPerspective) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(10% at 30% 30%); + } + 75% { + clip-path: circle(30% at 30% 30%); + } + 100% { + clip-path: none; + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .perspectivetf { + transform: perspective(200px); + } + </style> + <div id="parent"> + <div id="target" style="width: 100px; height: 100px;"> + </div> + </div> + )HTML"); + + Element* parent = GetElementById("parent"); + Element* element = GetElementById("target"); + + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); + + // Advance the animation time and add the perspective transform to the parent. + UpdateAndAdvanceTimeTo(1250); + parent->setAttribute(html_names::kClassAttr, AtomicString("perspectivetf")); + + // Next main frame should proceed ordinarily + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kComposited, element, + UpdatesNeededForNextFrame::kMainThreadPropertyInvalidation); + + // However, the animation will fall back during pre-paint. + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + // Animation should still producve frames on main as normal + UpdateAndAdvanceTimeTo(2001); + + // Main thread should still be producing frames. + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); +} + +// Same as the above - with a descendant transform animation, the +// CullRectUpdater can't estimate bounds, since the paint area will be updated +// on the compositor thread (in theory - you could calculate the maximum +// transformations of any given tf anim and then propagate those changes, but +// that would be extremely complex and this is not done for that reason). +// Because of this, we fall back for the same reasons. +TEST_F(ClipPathPaintDefinitionTest, + FallbackWithNoneKeyframeAndChildTransformAnimation) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(10% at 30% 30%); + } + 75% { + clip-path: circle(30% at 30% 30%); + } + 100% { + clip-path: none; + } + } + @keyframes transform { + 0% { + transform: translateX(0px); + } + 100% { + transform: translateX(100px); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .child-animation { + animation: transform 8s steps(2, jump-end); + } + </style> + <div id="target" style="width: 100px; height: 100px;"> + <div id="child" style="width: 50px; height: 50px;"> + </div> + </div> + )HTML"); + InitPaintArtifactCompositor(); + + Element* child = GetElementById("child"); + child->setAttribute(html_names::kClassAttr, AtomicString("child-animation")); + + UpdateAllLifecyclePhasesForTest(); + StartAllWaitingAnimationsOnCompositor(child, 0); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = + StartAndVerifyNonEligibleClipPathAnimation(element, 1000); + + UpdateAndAdvanceTimeTo(2001); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); +} + +// This is a variation of the above test. The descendant transform animation is +// added later, rather than immediately. This ensures the clip-path animation +// still falls back correctly in this case. +TEST_F(ClipPathPaintDefinitionTest, + FallbackWithNoneKeyframeAndDelayedChildTransformAnimation) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(10% at 30% 30%); + } + 75% { + clip-path: circle(30% at 30% 30%); + } + 100% { + clip-path: none; + } + } + @keyframes transform { + 0% { + transform: translateX(0px); + } + 100% { + transform: translateX(100px); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .child-animation { + animation: transform 4s steps(2, jump-end); + } + </style> + <div id="target" style="width: 100px; height: 100px;"> + <div id="child" style="width: 50px; height: 50px;"> + </div> + </div> + )HTML"); + + Element* element = GetElementById("target"); + Element* child = GetElementById("child"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); + + // Advance the animation time and add a descendant transform animation. + UpdateAndAdvanceTimeTo(1250); + child->setAttribute(html_names::kClassAttr, AtomicString("child-animation")); + + // Next main frame should proceed ordinarily. + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kComposited, element, + UpdatesNeededForNextFrame::kMainThreadPropertyInvalidation); + + // However, the animation will fall back during pre-paint. + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + // So we get no other unexpected updates. + StartAllWaitingAnimationsOnCompositor(child, 1250); + + // Main thread should still be producing frames. + UpdateAndAdvanceTimeTo(2001); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); +} + +// Will-change: contents is a very old disqualifier for composited animations. +// Setting up the transform/opacity/filter animations requires allocating render +// surfaces, which is expensive, and we'd like to avoid that work if the +// contents are just going to change anyway. It's unclear how this work compares +// to native paint worklet, which without a synthesized clip (something that is +// only created for effects with render surface reasons), do not create a +// textures at all. In the future, this may be allowed, depending on perf +// testing. +TEST_F(ClipPathPaintDefinitionTest, FallbackForWillChangeContents) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(50% at 50% 50%); + } + 100% { + clip-path: circle(30% at 30% 30%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + + .willchangecontents { + will-change: contents; + } + </style> + <div id ="target" style="width: 100px; height: 100px"> + </div> + )HTML"); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, + AtomicString("animation willchangecontents")); + + StartAndVerifyNonEligibleClipPathAnimation(element, 1000); +} + +// This is a variation of the above test. will-change: contents is added later, +// rather than immediately. This ensures the animation is still functioning +// properly in this case. As implied above - this fallback could in theory be +// removed, but we keep it around for now to avoid potentially weird situations +// where will-change contents is added later and then something is done to get +// the animation to restart on the compositor (maybe a bounds change of a tf +// anim). In this case, paint status will be COMPOSITED but the failure reasons +// will not be kNoFailure, causing a stuck animation. In the future, we should +// explicitly handle this rather than hitting it with a hammer in the pre-paint +// tree walk. +TEST_F(ClipPathPaintDefinitionTest, FallbackForDelayedWillChangeContents) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(50% at 50% 50%); + } + 100% { + clip-path: circle(30% at 30% 30%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + + .willchangecontents { + will-change: contents; + } + </style> + <div id ="target" style="width: 100px; height: 100px"> + </div> + )HTML"); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); + + // Advance the animation time and add will-change: contents. + UpdateAndAdvanceTimeTo(1250); + + element->setAttribute(html_names::kClassAttr, + AtomicString("animation willchangecontents")); + + // Next main frame should proceed ordinarily. + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kComposited, element, + UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate); + + // However, the animation will fall back during pre-paint. + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + // Main thread should still be producing frames. + UpdateAndAdvanceTimeTo(2001); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); +} + +/* ----------------------------------------- */ +/* ANIMATION STATE CHANGE TESTS */ +/* For anims mutated by WAAPI, or CSS anims */ +/* directly mutated by JS */ +/* ----------------------------------------- */ + +// Test the case where we reverse a clip-path animation using javascript. In +// this case, we will need to resync the animation with cc, but compositing +// status should not change except being transiently set as needing a repaint. +TEST_F(ClipPathPaintDefinitionTest, ReverseClipPathAnimationNoUpdates) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(50% at 50% 50%); + } + 100% { + clip-path: circle(30% at 30% 30%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + </style> + <div id ="target" style="width: 100px; height: 100px"> + </div> + )HTML"); + + Element* element = GetElementById("target"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); + + UpdateAndAdvanceTimeTo(2000 + 1); + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kComposited, element, animation, + UpdatesNeededForNextFrame::kNoMainFrameUpdates); + + // Reverse the animation. + animation->updatePlaybackRate(-1); + + // Run lifecycle once more: animation should still be composited. Because it's + // the same animation, it shouldn't schedule an animation update. We do + // however, create a new paint worklet here, even though it contains no new + // information. In future, this could potentially be optimized out, but doing + // so would be complex and could cause errors. + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kComposited, element, animation, + static_cast<UpdatesNeededForNextFrame>( + UpdatesNeededForNextFrame::kPaintStatusReset | + UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate | + UpdatesNeededForNextFrame::kPaintInvalidated)); + + // Advance animation back (since it is reversed) to the 2nd frame. + UpdateAndAdvanceTimeTo(2000 + 1 + 1000 + 2); + + // Run lifecycle once more: repaints should be avoided even with negative + // playback rate. + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kComposited, element, animation, + UpdatesNeededForNextFrame::kNoMainFrameUpdates); } // Cancelling a clip path animation with web animations API should properly @@ -793,69 +1226,6 @@ UpdatesNeededForNextFrame::kPaintInvalidated)); } -// Clip-path animations with descendant transform animations must fall back to -// main thread due to difficulty determining animation bounds. -TEST_F(ClipPathPaintDefinitionTest, - FallbackWithNoneKeyframeAndChildTransformAnimation) { - SetBodyInnerHTML(R"HTML( - <style> - @keyframes clippath { - 0% { - clip-path: circle(30% at 30% 30%); - } - 100% { - clip-path: none; - } - } - @keyframes transform { - 0% { - transform: translateX(0px); - } - 100% { - transform: translateX(100px); - } - } - .animation { - animation: clippath 4s steps(4, jump-end); - } - .child-animation { - animation: transform 4s; - } - </style> - <div id="target" style="width: 100px; height: 100px;"> - <div id="child" style="width: 50px; height: 50px;"> - </div> - </div> - )HTML"); - InitPaintArtifactCompositor(); - - Element* element = GetElementById("target"); - Element* child = GetElementById("child"); - element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - child->setAttribute(html_names::kClassAttr, AtomicString("child-animation")); - - // Init clock. - UpdateAndAdvanceTimeTo(0); - - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kNotComposited, element, - UpdatesNeededForNextFrame::kAllUpdates); - - Animation* animation = GetFirstAnimation(element); - - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - UpdateAndAdvanceTimeTo(500); - - // Animation should still run, but the composited paint status should not - // change due to the child transform animation. - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); -} - // Test the case where a 2nd composited clip path animation causes a fallback to // the main thread. In this case, the paint properties should update to avoid // any crashes or paint worklets existing beyond their validity. @@ -917,14 +1287,12 @@ // status should not change. EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kScheduledAnimationUpdate); + UpdatesNeededForNextFrame::kMainThreadAnimationFrameNoInvalidation); UpdateAndAdvanceTimeTo(700 + 1001); EnsureCCClipPathInvariantsHoldThroughoutLifecycle( CompositedPaintStatus::kNotComposited, element, animation, - static_cast<UpdatesNeededForNextFrame>( - UpdatesNeededForNextFrame::kScheduledAnimationUpdate | - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate)); + UpdatesNeededForNextFrame::kMainThreadAnimationFrame); } TEST_F(ClipPathPaintDefinitionTest, @@ -989,61 +1357,6 @@ UpdatesNeededForNextFrame::kScheduledAnimationUpdate); } -// TODO(crbug.com/488090095): This test never should have passed and only did so -// because of a deficiency in the test framework. This should be un-disabled -// when the last part of crrev.com/c/7625319 is checked in. -TEST_F(ClipPathPaintDefinitionTest, - DISABLED_FallbackForDelayedWillChangeContents) { - SetBodyInnerHTML(R"HTML( - <style> - @keyframes clippath { - 0% { - clip-path: circle(50% at 50% 50%); - } - 100% { - clip-path: circle(30% at 30% 30%); - } - } - .animation { - animation: clippath 4s steps(4, jump-end); - } - - .willchangecontents { - will-change: contents; - } - </style> - <div id ="target" style="width: 100px; height: 100px"> - </div> - )HTML"); - - Element* element = GetElementById("target"); - element->setAttribute(html_names::kClassAttr, AtomicString("animation")); - - Animation* animation = StartAndVerifyEligibleClipPathAnimation(element, 1000); - - // Advance the animation time and add will-change: contents. - UpdateAndAdvanceTimeTo(1250); - - element->setAttribute(html_names::kClassAttr, - AtomicString("animation willchangecontents")); - - // Next main frame should proceed ordinarily. - EnsureCCClipPathInvariantsHoldStyleAndLayout( - CompositedPaintStatus::kComposited, element, - UpdatesNeededForNextFrame::kNeedsPaintPropertyUpdate); - - // However, the animation will fall back during pre-paint. - EnsureCCClipPathInvariantsHoldThroughoutPainting( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kAllUpdates); - - // Main thread should still be producing frames. - UpdateAndAdvanceTimeTo(2001); - EnsureCCClipPathInvariantsHoldThroughoutLifecycle( - CompositedPaintStatus::kNotComposited, element, animation, - UpdatesNeededForNextFrame::kMainThreadAnimationFrame); -} - // Test that the special animation restart for percent translate animations does // not trigger lifecycle issues for cc clippaths. TEST_F(ClipPathPaintDefinitionTest, ChangeDimensionPecentTranslateAnim) { @@ -1141,7 +1454,7 @@ element->setAttribute(html_names::kClassAttr, AtomicString("")); // Run all lifecycle phases except paint. This should trigger a transition - // retarget, and resolve the clip path status of this brand new ransition as + // retarget, and resolve the clip path status of this brand new transition as // kComposited, as clip-path status is resolved early in pre-paint. GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint( DocumentUpdateReason::kTest); @@ -1277,6 +1590,15 @@ UpdateAllLifecyclePhasesForTest(); } +/* ----------------------------------------- */ +/* ANIMATION BOUNDING RECT TESTS */ +/* ----------------------------------------- */ + +// The animation bounding rect is the rect that contains all keyframes, +// including underlying value (for delays) and potential extrapolation (for +// complex easing). This is needed so that we won't create unbounded paint +// chunks (or mask textures) for synthesized clips, which causes perf issues. + TEST_F(ClipPathPaintDefinitionTest, BoundingRectCorrectForSimpleKeyframeUnion) { SetBodyInnerHTML(R"HTML( <style> @@ -1401,4 +1723,140 @@ EXPECT_TRUE(generator_bounds->Contains(expected_bounds)); } +// Fragmentless boxes do not get the paint property updates that clip-path +// animations depend on, however, since there is nothing to animation in +// this case, there is no issue. This test and AnimationOnTableCol mainly +// check that no status (D)CHECKs. +TEST_F(ClipPathPaintDefinitionTest, AnimationOnTableColGroup) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(20% at 20% 20%); + } + 100% { + clip-path: circle(20% at 70% 70%); + } + } + .animation { + /* We explicitly don't go past the first step here, because + changes in the clip-path property keep the layout object + marked for a paint property update */ + animation: clippath 4s steps(4, jump-end); + } + .invalidatepaint { + background-color: red; + } + </style> + <table> + <colgroup id="target"> + <col> + <col> + </colgroup> + <tr> + <td id="incidental"></td> + <td></td> + </tr> + </table> + )HTML"); + + Element* element = GetElementById("target"); + Element* incidental = GetElementById("incidental"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + // Init clock. + UpdateAndAdvanceTimeTo(0); + + // Fallback should occur in pre-paint, rather than leaving a persistent + // kNeedsRepaint. Currently, this requires all updates, but in the future, + // animations like this may be made to not tick as they can't possibly + // have a visual output. If that''s the case, UpdatesNeededForNextFrame + // may need to be adjusted. + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kNotComposited, element, + UpdatesNeededForNextFrame::kAllUpdates); + Animation* animation = GetFirstAnimation(element); + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + // Init clock. + UpdateAndAdvanceTimeTo(50); + + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kScheduledAnimationUpdate); + + incidental->setAttribute(html_names::kClassAttr, + AtomicString("invalidatepaint")); + UpdateAndAdvanceTimeTo(100); + + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kScheduledAnimationUpdate); +} + +// See AnimationOnTableColGroup for a description of what this tests for. +TEST_F(ClipPathPaintDefinitionTest, AnimationOnTableCol) { + SetBodyInnerHTML(R"HTML( + <style> + @keyframes clippath { + 0% { + clip-path: circle(20% at 20% 20%); + } + 100% { + clip-path: circle(20% at 70% 70%); + } + } + .animation { + animation: clippath 4s steps(4, jump-end); + } + .invalidatepaint { + background-color: red; + } + </style> + <table> + <colgroup id="parent"> + <col id="target"> + <col> + </colgroup> + <tr> + <td></td> + <td></td> + </tr> + </table> + )HTML"); + + Element* element = GetElementById("target"); + Element* parent = GetElementById("parent"); + element->setAttribute(html_names::kClassAttr, AtomicString("animation")); + + UpdateAndAdvanceTimeTo(0); + + EnsureCCClipPathInvariantsHoldStyleAndLayout( + CompositedPaintStatus::kNotComposited, element, + UpdatesNeededForNextFrame::kAllUpdates); + Animation* animation = GetFirstAnimation(element); + EnsureCCClipPathInvariantsHoldThroughoutPainting( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kAllUpdates); + + UpdateAllLifecyclePhasesForTest(); + + UpdateAndAdvanceTimeTo(50); + + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kScheduledAnimationUpdate); + + // Invaidate the group so that the pre-paint tree walk will reach the actual + // col. + parent->setAttribute(html_names::kClassAttr, AtomicString("invalidatepaint")); + UpdateAndAdvanceTimeTo(100); + + EnsureCCClipPathInvariantsHoldThroughoutLifecycle( + CompositedPaintStatus::kNotComposited, element, animation, + UpdatesNeededForNextFrame::kScheduledAnimationUpdate); +} + } // namespace blink
diff --git a/third_party/blink/renderer/modules/encoding/text_decoder.cc b/third_party/blink/renderer/modules/encoding/text_decoder.cc index d6f97f1..4af198de 100644 --- a/third_party/blink/renderer/modules/encoding/text_decoder.cc +++ b/third_party/blink/renderer/modules/encoding/text_decoder.cc
@@ -69,7 +69,7 @@ TextDecoder::~TextDecoder() = default; String TextDecoder::encoding() const { - String name = encoding_.GetName().GetString().DeprecatedLower(); + String name = encoding_.GetName().GetString().LowerASCII(); // Where possible, encoding aliases should be handled by changes to Chromium's // ICU or Blink's WTF. The same codec is used, but WTF maintains a different // name/identity for these.
diff --git a/third_party/blink/renderer/modules/encoding/text_encoder.cc b/third_party/blink/renderer/modules/encoding/text_encoder.cc index 80aac2c..81ea51ff 100644 --- a/third_party/blink/renderer/modules/encoding/text_encoder.cc +++ b/third_party/blink/renderer/modules/encoding/text_encoder.cc
@@ -59,7 +59,7 @@ TextEncoder::~TextEncoder() = default; String TextEncoder::encoding() const { - String name = encoding_.GetName().GetString().DeprecatedLower(); + String name = encoding_.GetName().GetString().LowerASCII(); DCHECK_EQ(name, "utf-8"); return name; }
diff --git a/third_party/blink/renderer/modules/filesystem/dom_file_system_base.cc b/third_party/blink/renderer/modules/filesystem/dom_file_system_base.cc index a83f8eda..aac916f 100644 --- a/third_party/blink/renderer/modules/filesystem/dom_file_system_base.cc +++ b/third_party/blink/renderer/modules/filesystem/dom_file_system_base.cc
@@ -85,7 +85,7 @@ KURL DOMFileSystemBase::CreateFileSystemRootURL( const String& origin, mojom::blink::FileSystemType type) { - String type_string; + StringView type_string; if (type == mojom::blink::FileSystemType::kTemporary) type_string = kTemporaryPathPrefix; else if (type == mojom::blink::FileSystemType::kPersistent) @@ -111,6 +111,10 @@ KURL DOMFileSystemBase::CreateFileSystemURL(const String& full_path) const { DCHECK(DOMFilePath::IsAbsolute(full_path)); + // Remove the extra leading slash and URI encode. + String encoded_full_path = + EncodeWithUrlEscapeSequences(StringView(full_path, 1)); + if (GetType() == mojom::blink::FileSystemType::kExternal) { // For external filesystem originString could be different from what we have // in m_filesystemRootURL. @@ -120,9 +124,8 @@ result.Append('/'); result.Append(kExternalPathPrefix); result.Append(filesystem_root_url_.GetPath()); - // Remove the extra leading slash. - result.Append(EncodeWithUrlEscapeSequences(full_path.Substring(1))); - return KURL(result.ToString()); + result.Append(encoded_full_path); + return KURL(result.ReleaseString()); } // For regular types we can just append the entry's fullPath to the @@ -130,9 +133,7 @@ // 'filesystem:<origin>/<typePrefix>'. DCHECK(!filesystem_root_url_.IsEmpty()); KURL url = filesystem_root_url_; - // Remove the extra leading slash. - url.SetPath(StrCat( - {url.GetPath(), EncodeWithUrlEscapeSequences(full_path.Substring(1))})); + url.SetPath(StrCat({url.GetPath(), encoded_full_path})); return url; }
diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc index 560630b..e85de9ff8 100644 --- a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc +++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc
@@ -28,6 +28,7 @@ #include <memory> #include "base/compiler_specific.h" +#include "base/containers/span.h" #include "base/synchronization/waitable_event.h" #include "base/trace_event/trace_event.h" #include "services/metrics/public/cpp/ukm_builders.h" @@ -299,12 +300,8 @@ for (unsigned channel = 0; channel < backing_input_buffer->numberOfChannels(); ++channel) { - const float* source = static_cast<float*>( - backing_input_buffer->getChannelData(channel)->buffer()->Data()); - float* destination = static_cast<float*>( - external_input_buffer_->getChannelData(channel)->buffer()->Data()); - UNSAFE_TODO(memcpy(destination, source, - backing_input_buffer->length() * sizeof(float))); + external_input_buffer_->getChannelData(channel)->AsSpan().copy_from( + backing_input_buffer->getChannelData(channel)->AsSpan()); } } } @@ -339,12 +336,8 @@ for (unsigned channel = 0; channel < backing_output_buffer->numberOfChannels(); ++channel) { - const float* source = static_cast<float*>( - external_output_buffer_->getChannelData(channel)->buffer()->Data()); - float* destination = static_cast<float*>( - backing_output_buffer->getChannelData(channel)->buffer()->Data()); - UNSAFE_TODO(memcpy(destination, source, - backing_output_buffer->length() * sizeof(float))); + backing_output_buffer->getChannelData(channel)->AsSpan().copy_from( + external_output_buffer_->getChannelData(channel)->AsSpan()); } } }
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h index 3e17af1..0790b75 100644 --- a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h +++ b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
@@ -96,6 +96,7 @@ friend class PaintChunker; friend class PaintController; friend class PaintControllerCycleScope; + friend class ClipPathPaintDefinitionTest; void MarkForValidation() const { marked_for_validation_ = 1; } bool IsMarkedForValidation() const { return marked_for_validation_; }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_response.cc b/third_party/blink/renderer/platform/loader/fetch/resource_response.cc index b83c6b8..fad57c9 100644 --- a/third_party/blink/renderer/platform/loader/fetch/resource_response.cc +++ b/third_party/blink/renderer/platform/loader/fetch/resource_response.cc
@@ -431,12 +431,12 @@ bool ResourceResponse::IsAttachment() const { static const char kAttachmentString[] = "attachment"; - String value = http_header_fields_.Get(http_names::kContentDisposition); - wtf_size_t loc = value.find(';'); - if (loc != kNotFound) - value = value.Left(loc); - value = value.StripWhiteSpace(); - return EqualIgnoringAsciiCase(value, kAttachmentString); + const AtomicString& header_value = + http_header_fields_.Get(http_names::kContentDisposition); + const StringView attachment_value = StringView(header_value) + .substr(0, header_value.find(';')) + .StripWhiteSpace(); + return EqualIgnoringAsciiCase(attachment_value, kAttachmentString); } AtomicString ResourceResponse::HttpContentType() const {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 1d094ca..9d14bd2 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1585,7 +1585,6 @@ { name: "CSSLineClampLineBreakingEllipsis", depends_on: ["CSSLineClamp"], - status: "experimental", }, { // This flag makes `(-webkit-)line-clamp: <integer>` clamp by both lines @@ -3888,7 +3887,7 @@ // for encapsulated content within <textarea> and <input> elements. // crbug.com/421421332 name: "OpaqueRange", - status: "test", + status: "experimental", }, { // Restrict popover invoker lookup to the same TreeScope as the element. @@ -5443,6 +5442,10 @@ status: "experimental", }, { + name: "SyntheticMouseHoverOverInactivePage", + status: "stable", + }, + { name: "SystemDefaultAccentColors", status: "stable", base_feature: "none",
diff --git a/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc b/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc index 2e8d795..b2b47aa 100644 --- a/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc +++ b/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc
@@ -32,6 +32,10 @@ PredictorFactory::GetPredictorTypeFromName(predictor_name); predictor_ = PredictorFactory::GetPredictor(predictor_type); + // Initialize the synthetic predictor. In this base refactor, it uses + // the same predictor type as real events. + synthetic_predictor_ = PredictorFactory::GetPredictor(predictor_type); + filtering_enabled_ = base::FeatureList::IsEnabled(blink::features::kFilteringScrollPrediction); @@ -113,7 +117,8 @@ if (should_resample_scroll_events_) { ResampleEvent(frame_time, frame_interval, - event_with_callback->event_pointer(), trace_id); + event_with_callback->event_pointer(), trace_id, + /*use_synthetic_predictor=*/false); // Sync the predicted `delta_y` to `metrics` for AverageLag metric. auto* metrics = event_with_callback->metrics() ? event_with_callback->metrics()->AsScrollUpdate() @@ -150,7 +155,7 @@ ui::LatencyInfo latency_info; latency_info.set_trace_id(base::trace_event::GetNextGlobalTraceId()); ResampleEvent(frame_time, frame_interval, &gesture_event, - latency_info.trace_id()); + latency_info.trace_id(), /*use_synthetic_predictor=*/true); // TODO(b/329346768): We should also add a new `BEGIN` stage, instead of // re-using the one that is explicitly about the `content::RenderWidgetHost`. @@ -209,20 +214,22 @@ if (!last_prediction_update_timestamp_.is_null() && prediction_time - last_prediction_update_timestamp_ > - predictor_->MaxResampleTime()) { + synthetic_predictor_->MaxResampleTime()) { return false; } - return predictor_->HasPrediction(); + return synthetic_predictor_->HasPrediction(); } void ScrollPredictor::Reset() { predictor_->Reset(); + synthetic_predictor_->Reset(); if (filtering_enabled_) { filter_ = filter_factory_->CreateFilter(); } current_event_accumulated_delta_ = gfx::PointF(); last_predicted_accumulated_delta_ = gfx::PointF(); + last_real_delta_ = gfx::Vector2dF(); last_prediction_update_timestamp_ = base::TimeTicks(); // Reset the timestamp metrics_handler_.Reset(); } @@ -244,11 +251,13 @@ } if (last_prediction_update_timestamp_ < gesture_event.TimeStamp()) { - predictor_->Update( - {current_event_accumulated_delta_ + - gfx::Vector2dF(gesture_event.data.scroll_update.delta_x, - gesture_event.data.scroll_update.delta_y), - gesture_event.TimeStamp()}); + ui::InputPredictor::InputData data = { + current_event_accumulated_delta_ + + gfx::Vector2dF(gesture_event.data.scroll_update.delta_x, + gesture_event.data.scroll_update.delta_y), + gesture_event.TimeStamp()}; + predictor_->Update(data); + synthetic_predictor_->Update(data); last_prediction_update_timestamp_ = gesture_event.TimeStamp(); } } @@ -279,6 +288,7 @@ // each event once. if (last_prediction_update_timestamp_ < gesture_event.TimeStamp()) { predictor_->Update(data); + synthetic_predictor_->Update(data); last_prediction_update_timestamp_ = gesture_event.TimeStamp(); } @@ -290,25 +300,29 @@ void ScrollPredictor::ResampleEvent(base::TimeTicks frame_time, base::TimeDelta frame_interval, WebInputEvent* event, - int64_t trace_id) { + int64_t trace_id, + bool use_synthetic_predictor) { + ui::InputPredictor* predictor = + use_synthetic_predictor ? synthetic_predictor_.get() : predictor_.get(); + DCHECK(predictor); DCHECK(event->GetType() == WebInputEvent::Type::kGestureScrollUpdate); WebGestureEvent* gesture_event = static_cast<WebGestureEvent*>(event); + float original_delta_x = gesture_event->data.scroll_update.delta_x; + float original_delta_y = gesture_event->data.scroll_update.delta_y; + TRACE_EVENT_BEGIN( "input", "ScrollPredictor::ResampleScrollEvents", [&](perfetto::EventContext ctx) { auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); auto* scroll_data = event->set_scroll_deltas(); scroll_data->set_trace_id(trace_id); - scroll_data->set_original_delta_x( - gesture_event->data.scroll_update.delta_x); - scroll_data->set_original_delta_y( - gesture_event->data.scroll_update.delta_y); + scroll_data->set_original_delta_x(original_delta_x); + scroll_data->set_original_delta_y(original_delta_y); }); gfx::PointF predicted_accumulated_delta = last_predicted_accumulated_delta_ + - gfx::Vector2dF(gesture_event->data.scroll_update.delta_x, - gesture_event->data.scroll_update.delta_y); + gfx::Vector2dF(original_delta_x, original_delta_y); base::TimeDelta prediction_delta = frame_time - gesture_event->TimeStamp(); bool predicted = false; @@ -316,12 +330,16 @@ // For resampling, we don't want to predict too far away because the result // will likely be inaccurate in that case. We cut off the prediction to the // maximum available for the current predictor - prediction_delta = std::min(prediction_delta, predictor_->MaxResampleTime()); + prediction_delta = std::min(prediction_delta, predictor->MaxResampleTime()); base::TimeTicks prediction_time = gesture_event->TimeStamp() + prediction_delta; - auto result = predictor_->GeneratePrediction(prediction_time, frame_interval); + if (!use_synthetic_predictor) { + last_real_delta_ = gfx::Vector2dF(original_delta_x, original_delta_y); + } + + auto result = predictor->GeneratePrediction(prediction_time, frame_interval); if (result) { predicted_accumulated_delta = result->pos; gesture_event->SetTimeStamp(result->time_stamp); @@ -341,14 +359,16 @@ // direction to the original event. gfx::Vector2dF new_delta = predicted_accumulated_delta - last_predicted_accumulated_delta_; + + const gfx::Vector2dF& reference_delta = + !use_synthetic_predictor + ? gfx::Vector2dF(original_delta_x, original_delta_y) + : last_real_delta_; + gesture_event->data.scroll_update.delta_x = - (new_delta.x() * gesture_event->data.scroll_update.delta_x < 0) - ? 0 - : new_delta.x(); + (new_delta.x() * reference_delta.x() < 0) ? 0 : new_delta.x(); gesture_event->data.scroll_update.delta_y = - (new_delta.y() * gesture_event->data.scroll_update.delta_y < 0) - ? 0 - : new_delta.y(); + (new_delta.y() * reference_delta.y() < 0) ? 0 : new_delta.y(); TRACE_EVENT_END("input", [&](perfetto::EventContext ctx) { auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); auto* scroll_data = event->set_scroll_deltas();
diff --git a/third_party/blink/renderer/platform/widget/input/scroll_predictor.h b/third_party/blink/renderer/platform/widget/input/scroll_predictor.h index 0c10b52..b3bc7ceb 100644 --- a/third_party/blink/renderer/platform/widget/input/scroll_predictor.h +++ b/third_party/blink/renderer/platform/widget/input/scroll_predictor.h
@@ -74,13 +74,19 @@ void ResampleEvent(base::TimeTicks frame_time, base::TimeDelta frame_interval, WebInputEvent* event, - int64_t trace_id); + int64_t trace_id, + bool use_synthetic_predictor); // Reports metrics scores UMA histogram based on the metrics defined // in |PredictionMetricsHandler| void EvaluatePrediction(); std::unique_ptr<ui::InputPredictor> predictor_; + // Predictor used specifically for generating synthetic scroll events to fill + // gaps between real input events (e.g., at VSync). This allows using a + // different prediction algorithm (like Kalman) for synthetic gap-filling + // while keeping the primary algorithm for real events. + std::unique_ptr<ui::InputPredictor> synthetic_predictor_; std::unique_ptr<ui::InputFilter> filter_; std::unique_ptr<FilterFactory> filter_factory_; @@ -100,6 +106,10 @@ // and delta_y for the resampled/predicted event. gfx::PointF last_predicted_accumulated_delta_; + // Caches the scroll direction of the last real touch event to clamp + // synthetic jitter and prevent "backward" scrolls. + gfx::Vector2dF last_real_delta_; + // Whether current scroll event should be resampled. bool should_resample_scroll_events_ = false;
diff --git a/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc b/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc index 576e85bf..043d718 100644 --- a/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc +++ b/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc
@@ -35,12 +35,28 @@ void SetUp() override { original_events_.clear(); scroll_predictor_ = std::make_unique<ScrollPredictor>(); - scroll_predictor_->predictor_ = std::make_unique<ui::EmptyPredictor>(); + SetPredictor(std::make_unique<ui::EmptyPredictor>()); + SetSyntheticPredictor(std::make_unique<ui::EmptyPredictor>()); + } + + void SetPredictor(std::unique_ptr<ui::InputPredictor> predictor) { + scroll_predictor_->predictor_ = std::move(predictor); + } + + void SetSyntheticPredictor(std::unique_ptr<ui::InputPredictor> predictor) { + scroll_predictor_->synthetic_predictor_ = std::move(predictor); + } + + ui::InputPredictor* predictor() { + return scroll_predictor_->predictor_.get(); + } + + ui::InputPredictor* synthetic_predictor() { + return scroll_predictor_->synthetic_predictor_.get(); } void SetUpLSQPredictor() { - scroll_predictor_->predictor_ = - std::make_unique<ui::LeastSquaresPredictor>(); + SetPredictor(std::make_unique<ui::LeastSquaresPredictor>()); } std::unique_ptr<WebInputEvent> CreateGestureScrollUpdate( @@ -102,7 +118,14 @@ base::TimeTicks frame_time = WebInputEvent::GetStaticTimeStampForTests() + base::Milliseconds(time_delta_in_milliseconds); // Tests with 60Hz. - return scroll_predictor_->predictor_->GeneratePrediction(frame_time); + return predictor()->GeneratePrediction(frame_time); + } + + std::unique_ptr<ui::InputPredictor::InputData> SyntheticPredictionAvailable( + double time_delta_in_milliseconds = 0) { + base::TimeTicks frame_time = WebInputEvent::GetStaticTimeStampForTests() + + base::Milliseconds(time_delta_in_milliseconds); + return synthetic_predictor()->GeneratePrediction(frame_time); } gfx::PointF GetLastAccumulatedDelta() { @@ -686,5 +709,38 @@ } } +TEST_F(ScrollPredictorTest, AlgorithmDivergence) { + // 1. Manually assign different algorithms to each role. + // Real events use Linear, Synthetic uses Empty (static). + SetPredictor(std::make_unique<ui::LinearPredictor>( + ui::LinearPredictor::EquationOrder::kFirstOrder)); + SetSyntheticPredictor(std::make_unique<ui::EmptyPredictor>()); + + SendGestureScrollBegin(); + + // 2. Send 2 GSUs to establish a velocity for Linear. + // Event 1 at t=10ms, delta = -10 (total accumulated = -10) + // Event 2 at t=20ms, delta = -10 (total accumulated = -20) + // Velocity = 1 unit/ms. + for (int i = 1; i <= 2; ++i) { + std::unique_ptr<WebInputEvent> gsu = + CreateGestureScrollUpdate(0, -10, 10 * i); + HandleResampleScrollEvents(gsu, 10 * i); + } + + // 3. Request predictions for t=30ms (a 10ms look-ahead from t=20ms). + auto real_result = PredictionAvailable(30); + auto synthetic_result = SyntheticPredictionAvailable(30); + + ASSERT_TRUE(real_result); + ASSERT_TRUE(synthetic_result); + + // 4. Verify divergence. + // Linear should predict -30 (-20 + 1u/ms * 10ms). + // Empty should return exactly the last sample (-20). + EXPECT_EQ(real_result->pos.y(), -30); + EXPECT_EQ(synthetic_result->pos.y(), -20); +} + } // namespace test } // namespace blink
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index b398ec8..02b08e0 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -6669,8 +6669,6 @@ crbug.com/487338359 external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-012.html [ Crash Failure ] -crbug.com/487338358 external/wpt/css/css-gaps/flex/flex-gap-decorations-053.html [ Failure ] - crbug.com/445971864 external/wpt/css/css-gaps/multicol/multicol-gap-decorations-007.html [ Failure ] crbug.com/357648037 external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-005.html [ Failure ] crbug.com/357648037 external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-006.html [ Failure ] @@ -8488,85 +8486,103 @@ # line-clamp crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-011.tentative.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-035.tentative.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-auto-041.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-003.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-004.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-005.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-006.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-007.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-008.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-balance-009.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-017.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-009.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-012.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-013.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-014.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-015.tentative.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-016.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-017.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-018.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-024.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-025.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-027.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-028.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-029.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-030.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-035.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-003.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Failure ] crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-001.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-002.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-003.tentative.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-inline/baseline-source/baseline-source-first-002.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-inline/baseline-source/baseline-source-last-001.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-inline/baseline-source/baseline-source-last-002.html [ Failure ] -crbug.com/40336192 external/wpt/css/css-inline/baseline-source/baseline-source-last-003.html [ Failure ] -# disable-css-line-clamp-line-breaking-ellipsis -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-041.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-004.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-005.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-007.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-008.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-017.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-009.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-012.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-013.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-014.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-016.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-017.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-018.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-025.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-027.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-028.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-029.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-003.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-035.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-001.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-002.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-003.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-first-002.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-001.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-002.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-003.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-002.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-003.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-004.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-007.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-002.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-003.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-004.html [ Failure ] +crbug.com/40336192 external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-007.html [ Failure ] +# css-line-clamp-line-breaking-ellipsis +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-041.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-004.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-005.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-007.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-008.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-017.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-009.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-012.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-013.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-014.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-016.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-017.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-018.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-025.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-027.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-028.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-029.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-003.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-035.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-001.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-002.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-003.tentative.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-first-002.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-001.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-002.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-inline/baseline-source/baseline-source-last-003.html [ Failure ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-002.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-003.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-004.html [ Pass ] +crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-007.html [ Pass ] # css-line-clamp-lines-and-height crbug.com/40336192 virtual/css-line-clamp-lines-and-height/external/wpt/css/css-overflow/line-clamp/line-clamp-011.tentative.html [ Pass ] crbug.com/40336192 virtual/css-line-clamp-lines-and-height/external/wpt/css/css-overflow/line-clamp/line-clamp-035.tentative.html [ Pass ] crbug.com/40336192 virtual/css-line-clamp-lines-and-height/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-with-max-height.tentative.html [ Pass ] # disable-css-line-clamp -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-035.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-040.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-047.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-050.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-001.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-004.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-005.html [ Failure ] @@ -8600,7 +8616,6 @@ crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-003.tentative.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-004.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-005.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-011.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-013.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-014.html [ Failure ] @@ -8629,7 +8644,6 @@ crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-038.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-039.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-040.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-041.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-042.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-043.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-044.html [ Failure ] @@ -8643,10 +8657,6 @@ crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-with-ruby-005.tentative.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-001.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-002.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-004.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-005.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-007.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-008.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-010.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-011.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-balance-012.html [ Failure ] @@ -8666,7 +8676,6 @@ crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-014.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-015.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-016.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-017.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-018.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-019.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-abspos-020.html [ Failure ] @@ -8700,52 +8709,20 @@ crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-floats-008.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-floats-009.tentative.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-with-floats-010.tentative.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-002.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-003.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-004.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-005.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-006.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-009.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-010.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-012.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-013.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-014.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-016.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-017.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-018.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-022.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-023.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-025.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-026.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-027.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-028.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-029.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-031.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-032.tentative.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-001.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-002.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-003.html [ Failure ] crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/block-ellipsis-repaint-004.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-001.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-002.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-003.tentative.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-inline/baseline-source/baseline-source-first-002.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-inline/baseline-source/baseline-source-last-001.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-inline/baseline-source/baseline-source-last-002.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-inline/baseline-source/baseline-source-last-003.html [ Pass ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-002.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-003.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-004.html [ Failure ] -crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-007.html [ Failure ] # backdrop-filter-mirror-edge crbug.com/904592 external/wpt/css/filter-effects/backdrop-filter-svg-blur.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index 44b4b9f..5dab1b8 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -505,7 +505,7 @@ "owners": ["samomekarajr@microsoft.com", "kbabbitt@microsoft.com"] }, { - "prefix": "disable-css-line-clamp-line-breaking-ellipsis", + "prefix": "css-line-clamp-line-breaking-ellipsis", "platforms": [ "Linux" ], @@ -552,7 +552,7 @@ "paint/invalidation/text-line-clamp-truncation.html" ], "args": [ - "--disable-blink-features=CSSLineClampLineBreakingEllipsis" + "--enable-blink-features=CSSLineClamp,CSSLineClampLineBreakingEllipsis" ], "owners": [ "abotella@igalia.com" @@ -747,20 +747,7 @@ "external/wpt/css/css-overflow/line-clamp", "external/wpt/css/css-overflow/parsing", "external/wpt/css/css-overflow/inheritance.html", - "external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html", - "external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html", - "external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html", - "external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-001.tentative.html", - "external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-002.tentative.html", - "external/wpt/css/css-grid/alignment/grid-align-baseline-line-clamp-003.tentative.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-first-001.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-first-002.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-first-003.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-last-001.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-last-002.html", - "external/wpt/css/css-inline/baseline-source/baseline-source-last-003.html", "external/wpt/css/css-inline/text-box-trim/text-box-trim-line-clamp-001.html", - "external/wpt/css/css-inline/text-box-trim/text-box-trim-line-clamp-002.html", "external/wpt/css/css-text/white-space/text-wrap-balance-005.html", "external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-001.html", "external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-002.html", @@ -770,17 +757,7 @@ "external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-006.html", "external/wpt/css/css-text/white-space/text-wrap-balance-line-clamp-007.html", "external/wpt/css/css-text/white-space/text-wrap-pretty-line-clamp-crash-001.html", - "external/wpt/css/css-ui/text-overflow-string-001.html", - "external/wpt/css/css-ui/text-overflow-string-002.html", - "external/wpt/css/css-ui/text-overflow-string-003.html", - "external/wpt/css/css-ui/text-overflow-string-004.html", - "external/wpt/css/css-ui/text-overflow-string-005.html", - "external/wpt/css/css-ui/text-overflow-string-006.html", - "fast/deprecated-flexbox/dynamically-change-line-clamp.html", - "fast/deprecated-flexbox/line-clamp-crash.html", - "fast/deprecated-flexbox/line-clamp-removed-dynamically.html", - "fast/overflow", - "paint/invalidation/text-line-clamp-truncation.html" + "fast/overflow" ], "args": [ "--disable-blink-features=CSSLineClamp"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-053-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-053-ref.html index dfc9bda..77644bb 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-053-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-053-ref.html
@@ -45,8 +45,8 @@ <div class="rule" style="top: 62px; left: 199.5px;"></div> <div class="rule" style="top: 62px; left: 309.5px;"></div> -<div class="row-rule" style="left: 2px; width: 80.5px;"></div> -<div class="row-rule" style="left: 101.5px; width: 56.125px;"></div> +<div class="row-rule" style="left: 2px; width: 80px;"></div> +<div class="row-rule" style="left: 102px; width: 55.625px;"></div> <div class="row-rule" style="left: 163.875px; width: 28.125px;"></div> <div class="row-rule" style="left: 212px; width: 28.125px;"></div> <div class="row-rule" style="left: 246.375px; width: 55.625px;"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-055-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-055-ref.html index 7601fb8..eae9d385 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-055-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/flex-gap-decorations-055-ref.html
@@ -48,7 +48,7 @@ <div class="row-rule" style="left: 2px; width: 50px;"></div> <div class="row-rule" style="left: 132px; width: 16.25px;"></div> <div class="row-rule" style="left: 255.75px; width: 16.25px;"></div> -<div class="row-rule" style="left: 338.25px; width: 63.75px;"></div> +<div class="row-rule" style="left: 352px; width: 50px;"></div> <div id="flexbox"> <div class="item"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020-ref.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020-ref.html new file mode 100644 index 0000000..fc83c421 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020-ref.html
@@ -0,0 +1,57 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-gaps-1/"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0; + } + .multicol { + column-count: 5; + width: 600px; + column-gap: 1px; + height: 101px; + } + #flex { + display: flex; + flex-direction: column; + flex-wrap: wrap; + height: 500px; + border: 1px solid black; + column-gap: 3px; + row-gap: 3px; + } + + #flex > div { + background: lightblue; + } + + .row-decoration { + background: gold; + position: absolute; + } + + .column-decoration { + background: green; + position: absolute; + } +</style> + +<div class="column-decoration" style="height: 98.5px; width: 3px; top: 1px; left: 58px;"></div> +<div class="column-decoration" style="height: 99.5px; width: 3px; top: 0px; left: 178px;"></div> +<div class="column-decoration" style="height: 99.5px; width: 3px; top: 0px; left: 298.5px;"></div> +<div class="column-decoration" style="height: 99.5px; width: 3px; top: 0px; left: 418.5px;"></div> +<div class="column-decoration" style="height: 98.5px; width: 3px; top: 0px; left: 539px;"></div> + +<div class="row-decoration" style="width: 57px; height: 3px; top: 0.5px; left: 121px;"></div> +<div class="row-decoration" style="width: 57px; height: 3px; top: 53.5px; left: 121px;"></div> +<div class="row-decoration" style="width: 58px; height: 3px; top: 6px; left: 241px;"></div> + +<div class="multicol"> + <div id="flex" style=""> + <div style="height: 100px;"></div> + <div style="height: 50px;"></div> + <div style="height: 50px;"></div> + <div style="height: 250px;"></div> + <div style="height: 400px;"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020.html b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020.html new file mode 100644 index 0000000..55c915c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-gaps/flex/fragmentation/flex-gap-decorations-fragmentation-020.html
@@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title> + CSS Gap Decorations: Fragmented flex container gaps are painted when a fragment processes a line not in + ascending order. +</title> +<link rel="help" href="https://www.w3.org/TR/css-gaps-1/"> +<link rel="match" href="flex-gap-decorations-fragmentation-020-ref.html"> +<link rel="author" title="Javier Contreras" href="mailto:javiercon@microsoft.com"> +<style> + body { + margin: 0px; + } + .multicol { + column-count: 5; + width: 600px; + column-gap: 1px; + height: 101px; + } + #flex { + display: flex; + flex-direction: column; + flex-wrap: wrap; + height: 500px; + border: 1px solid black; + column-gap: 3px; + column-rule: 3px solid green; + row-gap: 3px; + row-rule: 3px solid gold; + } + + #flex > div { + background: lightblue; + } +</style> +<div class="multicol"> + <div id="flex" style=""> + <div style="height: 100px;"></div> + <div style="height: 50px;"></div> + <div style="height: 50px;"></div> + <div style="height: 250px;"></div> + <div style="height: 400px;"></div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window-expected.txt b/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window-expected.txt deleted file mode 100644 index 420e943..0000000 --- a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -Harness Error. harness_status.status = 1 , harness_status.message = Uncaught Error: assert_equals: script1 executes synchronously, and thus observes only itself and previous scripts expected 5 but got 6 -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js b/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js index a1be3e1d..b1e0720 100644 --- a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js +++ b/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js
@@ -18,17 +18,17 @@ window.script1Executed = false; script1.innerText = ` script1Executed = true; - assert_equals(document.scripts.length, kBaselineNumberOfScripts + 2, - "script1 executes synchronously, and thus observes only itself and " + - "previous scripts"); + assert_equals(document.scripts.length, kBaselineNumberOfScripts + 3, + "script1 executes after all DOM insertions complete, observing all " + + "scripts"); `; window.script2Executed = false; script2.innerText = ` script2Executed = true; assert_equals(document.scripts.length, kBaselineNumberOfScripts + 3, - "script2 executes synchronously, and thus observes itself and all " + - "previous scripts"); + "script2 executes after all DOM insertions complete, observing all " + + "scripts"); `; assert_false(script0Executed, "Script0 does not execute before append()"); @@ -43,6 +43,5 @@ "Script1 executes synchronously during fragment append()"); assert_true(script2Executed, "Script2 executes synchronously during fragment append()"); -}, "Script node insertion is not atomic with regard to execution. Each " + - "script is synchronously executed during the HTML element insertion " + - "steps hook"); +}, "Script execution happens after all batched DOM insertions complete " + + "through post-connection steps");
diff --git a/third_party/blink/web_tests/external/wpt/dom/ranges/tentative/OpaqueRange-auto-disconnect.html b/third_party/blink/web_tests/external/wpt/dom/ranges/tentative/OpaqueRange-auto-disconnect.html new file mode 100644 index 0000000..e1dcb79 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/dom/ranges/tentative/OpaqueRange-auto-disconnect.html
@@ -0,0 +1,112 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body></body> +<script> +const controls = ['textarea', 'input']; + +function setupControl(control) { + document.body.innerHTML = control === 'textarea' + ? '<textarea>Hello</textarea>' + : '<input type="text" value="Hello">'; + return document.body.firstElementChild; +} + +function assertDisconnected(range, description) { + assert_equals(range.startOffset, 0, description + ": startOffset resets to 0"); + assert_equals(range.endOffset, 0, description + ": endOffset resets to 0"); + assert_true(range.collapsed, description + ": range is collapsed"); +} + +controls.forEach(control => { + test(() => { + const el = setupControl(control); + const range = el.createValueRange(1, 4); + el.remove(); + assertDisconnected(range, control); + }, `OpaqueRange disconnected on element removal (${control})`); + + test(() => { + const el = setupControl(control); + const ranges = [ + el.createValueRange(0, 3), + el.createValueRange(2, 5), + ]; + el.remove(); + for (const [i, range] of ranges.entries()) { + assertDisconnected(range, `range ${i}`); + } + }, `All OpaqueRanges disconnected on element removal (${control})`); + + test(() => { + const el = setupControl(control); + const oldRange = el.createValueRange(1, 4); + el.remove(); + assertDisconnected(oldRange, "old range after removal"); + + document.body.appendChild(el); + const newRange = el.createValueRange(0, 5); + assert_equals(newRange.startOffset, 0); + assert_equals(newRange.endOffset, 5); + }, `Element accepts new OpaqueRanges after re-insertion (${control})`); + + test(() => { + document.body.innerHTML = control === 'textarea' + ? '<div><textarea>Hello</textarea></div>' + : '<div><input type="text" value="Hello"></div>'; + const div = document.body.firstElementChild; + const el = div.firstElementChild; + const range = el.createValueRange(1, 4); + div.remove(); + assertDisconnected(range, "ancestor removal"); + }, `OpaqueRange disconnected on ancestor removal (${control})`); + + test(() => { + const el = setupControl(control); + const range = el.createValueRange(1, 4); + document.body.innerHTML = ''; + assertDisconnected(range, "innerHTML replacement"); + }, `OpaqueRange disconnected on implicit removal via innerHTML (${control})`); + + test(() => { + const el = setupControl(control); + const range = el.createValueRange(1, 4); + range.disconnect(); + assertDisconnected(range, "manually disconnected"); + el.remove(); + assertDisconnected(range, "after element removal"); + }, `No error when element removed after manual disconnect (${control})`); + + test(() => { + const el = setupControl(control); + const range = el.createValueRange(1, 4); + const otherDoc = document.implementation.createHTMLDocument(); + otherDoc.body.appendChild(document.adoptNode(el)); + assertDisconnected(range, "adoptNode"); + }, `OpaqueRange disconnected when element adopted into another document (${control})`); +}); + +for (const newType of ["color", "search"]) { + test(() => { + document.body.innerHTML = '<input type="text" value="Hello">'; + const input = document.body.firstElementChild; + const range = input.createValueRange(1, 4); + input.type = newType; + assertDisconnected(range, `type changed to ${newType}`); + }, `OpaqueRange disconnected on input type change to "${newType}".`); +} + +test(() => { + document.body.innerHTML = '<input type="text" value="Hello">'; + const input = document.body.firstElementChild; + const oldRange = input.createValueRange(1, 4); + input.type = "color"; + assertDisconnected(oldRange, "after type changed to color"); + + input.type = "text"; + input.value = "World"; + const newRange = input.createValueRange(0, 5); + assert_equals(newRange.startOffset, 0); + assert_equals(newRange.endOffset, 5); +}, "Input accepts new OpaqueRanges after type changes back to text."); +</script>
diff --git a/third_party/blink/web_tests/platform/linux/virtual/css-line-clamp-line-breaking-ellipsis/paint/invalidation/control-clip-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/css-line-clamp-line-breaking-ellipsis/paint/invalidation/control-clip-expected.txt new file mode 100644 index 0000000..9dbd1ad --- /dev/null +++ b/third_party/blink/web_tests/platform/linux/virtual/css-line-clamp-line-breaking-ellipsis/paint/invalidation/control-clip-expected.txt
@@ -0,0 +1,15 @@ +{ + "layers": [ + { + "name": "Scrolling background of LayoutView #document", + "bounds": [800, 600], + "contentsOpaque": true, + "backgroundColor": "#FFFFFF", + "invalidations": [ + [8, 80, 200, 30], + [75, 117, 66, 16] + ] + } + ] +} +
diff --git a/third_party/blink/web_tests/platform/linux/virtual/text-antialias/ellipsis-in-justified-text-expected.png b/third_party/blink/web_tests/platform/linux/virtual/text-antialias/ellipsis-in-justified-text-expected.png index 241f43f..612d369 100644 --- a/third_party/blink/web_tests/platform/linux/virtual/text-antialias/ellipsis-in-justified-text-expected.png +++ b/third_party/blink/web_tests/platform/linux/virtual/text-antialias/ellipsis-in-justified-text-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/overflow/line-clamp-expected.png b/third_party/blink/web_tests/platform/mac/fast/overflow/line-clamp-expected.png index 9a300a10..348c050 100644 --- a/third_party/blink/web_tests/platform/mac/fast/overflow/line-clamp-expected.png +++ b/third_party/blink/web_tests/platform/mac/fast/overflow/line-clamp-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/text-antialias/ellipsis-in-justified-text-expected.png b/third_party/blink/web_tests/platform/mac/virtual/text-antialias/ellipsis-in-justified-text-expected.png index 28fdacb..e7f0bfc 100644 --- a/third_party/blink/web_tests/platform/mac/virtual/text-antialias/ellipsis-in-justified-text-expected.png +++ b/third_party/blink/web_tests/platform/mac/virtual/text-antialias/ellipsis-in-justified-text-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/text-antialias/ellipsis-in-justified-text-expected.png b/third_party/blink/web_tests/platform/win/virtual/text-antialias/ellipsis-in-justified-text-expected.png index 6290709..a2495de 100644 --- a/third_party/blink/web_tests/platform/win/virtual/text-antialias/ellipsis-in-justified-text-expected.png +++ b/third_party/blink/web_tests/platform/win/virtual/text-antialias/ellipsis-in-justified-text-expected.png Binary files differ
diff --git a/third_party/blink/web_tests/virtual/css-line-clamp-line-breaking-ellipsis/README.md b/third_party/blink/web_tests/virtual/css-line-clamp-line-breaking-ellipsis/README.md new file mode 100644 index 0000000..0f8fdf5 --- /dev/null +++ b/third_party/blink/web_tests/virtual/css-line-clamp-line-breaking-ellipsis/README.md
@@ -0,0 +1,5 @@ +Tests with the CSSLineClampLineBreakingEllipsis feature. + +This feature makes the (-webkit-)line-clamp ellipsis be taken into account when +line breaking, as well as making it be taken into account for bidi reordering +and text alignment/justification.
diff --git a/third_party/blink/web_tests/virtual/disable-css-line-clamp-line-breaking-ellipsis/README.md b/third_party/blink/web_tests/virtual/disable-css-line-clamp-line-breaking-ellipsis/README.md deleted file mode 100644 index d966bea2..0000000 --- a/third_party/blink/web_tests/virtual/disable-css-line-clamp-line-breaking-ellipsis/README.md +++ /dev/null
@@ -1,6 +0,0 @@ -Tests with the CSSLineClampLineBreakingEllipsis feature disabled. - -This feature makes the (-webkit-)line-clamp ellipsis be taken into account when -line breaking, as well as making it be taken into account for bidi reordering -and text alignment/justification. With it disabled, the ellipsis behaves -similarly to `text-overflow: ellipsis`.
diff --git a/third_party/boringssl/src b/third_party/boringssl/src index 5774eca..745d3eb 160000 --- a/third_party/boringssl/src +++ b/third_party/boringssl/src
@@ -1 +1 @@ -Subproject commit 5774eca6004ed7c7467bd644c057797ca96b65f2 +Subproject commit 745d3ebf4064a4ea7c0a115ecbba5affa2609bcd
diff --git a/third_party/dawn b/third_party/dawn index ad8d51f..33a083f 160000 --- a/third_party/dawn +++ b/third_party/dawn
@@ -1 +1 @@ -Subproject commit ad8d51fbdd599d8dd0e2997f88900ac89f404513 +Subproject commit 33a083f7a9e71cfe7e8a9b05c94fda00e5715900
diff --git a/third_party/eigen3/src b/third_party/eigen3/src index dd826ed..eea4d31 160000 --- a/third_party/eigen3/src +++ b/third_party/eigen3/src
@@ -1 +1 @@ -Subproject commit dd826edb42520814dab9239ec81f32ab91a4afe4 +Subproject commit eea4d31f58572591bdaa3b654227e48efb495b96
diff --git a/third_party/jni_zero/codegen/called_by_native_header.py b/third_party/jni_zero/codegen/called_by_native_header.py index 509dd2a..6fd59d41 100644 --- a/third_party/jni_zero/codegen/called_by_native_header.py +++ b/third_party/jni_zero/codegen/called_by_native_header.py
@@ -152,7 +152,7 @@ plist.append('JNIEnv* env') if not field.static: plist.append('const jni_zero::JavaRef<jobject>& obj') - plist.append(f'{_param_type_cpp(field.java_type)} value') + plist.append(f'{_param_type_cpp_non_mirror(field.java_type)} value') with sb.block(after='\n'): if field.static: @@ -198,7 +198,13 @@ return ret -def _return_type_cpp_mirror(cbn): +def _return_type_cpp_mirror_self(cbn): + """ + Try to make the return type to be ScopedJavaLocalRef<JMyClass> where MyClass + is the name of the Java class that this @CalledByNative method belongs to. + Fall back to the non mirror version of the method if unsuccessful. + Return True if successful, and return False if unsuccessful. + """ java_class_name = cbn.java_class.nested_name returned_mirrored_class = f'jni_zero::ScopedJavaLocalRef<J{java_class_name}>' if cbn.is_constructor: @@ -209,7 +215,33 @@ return _return_type_cpp_non_mirror(cbn.return_type), False -def _param_type_cpp(java_type): +def _return_type_cpp_mirror_others(cbn): + """ + Try to make the return type to be ScopedJavaLocalRef<JMyClass> where MyClass + is the name of the Java class that this @CalledByNative method belongs to. + If unsuccessful, try to make the return type to be + ScopedJavaLocalRef<JReturnTypeClass> where ReturnTypeClass is the name of + the Java class of the return type in Java. If still unsuccessful, + fall back to the non mirror version of the method. + Return True if returned JReturnTypeClass, and return False otherwise. + """ + java_class_name = cbn.java_class.nested_name + returned_mirrored_class = f'jni_zero::ScopedJavaLocalRef<J{java_class_name}>' + if cbn.is_constructor: + return returned_mirrored_class, False + elif cbn.return_type.enable_mirror(cbn.java_class): + return returned_mirrored_class, False + elif cbn.return_type.enable_mirror(): + java_class_name = cbn.return_type.java_class.nested_name + cpp_class_namespace = cbn.return_type.java_class.package_with_colons + returned_mirrored_class = (f'jni_zero::ScopedJavaLocalRef' + f'<::{cpp_class_namespace}::J{java_class_name}>') + return returned_mirrored_class, True + else: + return _return_type_cpp_non_mirror(cbn.return_type), False + + +def _param_type_cpp_non_mirror(java_type): if type_str := java_type.converted_type: if java_type.is_primitive(): return type_str @@ -224,6 +256,15 @@ return f'const jni_zero::JavaRef<{ret}>&' +def _param_type_cpp_mirror(java_type): + if java_type.enable_mirror(): + java_class_name = java_type.java_class.nested_name + cpp_class_namespace = java_type.java_class.package_with_colons + return (f'const jni_zero::JavaRef' + f'<::{cpp_class_namespace}::J{java_class_name}>&') + return _param_type_cpp_non_mirror(java_type) + + def _prep_param(sb, param): """Returns the snippet to use for the parameter.""" ret = param.cpp_name() @@ -256,17 +297,6 @@ return f'Call{call}Method' -def _sanitize_namespace(namespace_string): - """ - Add the string 1 to each component of the namespace. - This is done so that we can safely use jni_zero::JavaRef in the namespace - org::jni_zero without having to use ::jni_zero::JavaRef. - """ - components = namespace_string.split('::') - transformed_components = [part + '1' for part in components] - return '::'.join(transformed_components) - - def _sanitize_method_name(method_name_string): """ Add the string 1 to the method name if it is a C++ reserved keyword. @@ -288,7 +318,7 @@ else: return_type = cbn.return_type is_void = return_type.is_void() - return_type_cpp, returned_mirrored_class = _return_type_cpp_mirror(cbn) + return_type_cpp, returned_mirrored_class = _return_type_cpp_mirror_self(cbn) if cbn.is_system_class: sb('[[maybe_unused]] ') @@ -298,7 +328,7 @@ plist.append('JNIEnv* env') if not reciever_arg_is_class: plist.append('const jni_zero::JavaRef<jobject>& obj') - plist.extend(f'{_param_type_cpp(p.java_type)} {p.cpp_name()}' + plist.extend(f'{_param_type_cpp_non_mirror(p.java_type)} {p.cpp_name()}' for p in cbn.params) with sb.block(after='\n'): @@ -361,86 +391,88 @@ f'{return_rvalue})') -def mirrored_cpp_class_declaration(sb, java_class, called_by_natives, - jni_namespace): +def mirrored_cpp_class_lazy_definition(sb, + java_class, + using_jni_namespace=False, + jni_namespace=None): java_class_name = java_class.nested_name cpp_class_superclass = java_types.CPP_TYPE_BY_JAVA_TYPE.get( java_class.full_name_with_slashes, 'jobject') - sanitized_namespace = _sanitize_namespace(java_class.package_with_colons) - with sb.namespace(sanitized_namespace): - if jni_namespace: - sb(f'using namespace {jni_namespace};\n') - sb('\n') - sb(f'''\ -class _J{java_class_name}; -using J{java_class_name} = _J{java_class_name}*; + cpp_class_namespace = java_class.package_with_colons + macro_name = (f'_JNI_ZERO_{cpp_class_namespace.replace("::", "_")}' + f'_J{java_class_name}_DEFINED') -class _J{java_class_name} : public _{cpp_class_superclass}''') - with sb.block(after=';'): - sb('public:\n') - for cbn in called_by_natives: - with sb.statement(): - mirrored_cpp_method_declaration(sb, cbn, include_class=False) - sb('\n') + sb(f'#ifndef {macro_name}\n') + with sb.namespace(cpp_class_namespace, skip_newline=True): + sb(f'class _J{java_class_name} : public _{cpp_class_superclass} {{}};\n') + sb(f'using J{java_class_name} = _J{java_class_name}*;\n') + sb(f'#define {macro_name}\n') + sb('#endif\n') sb('\n') - with sb.namespace(jni_namespace): - sb(f'using J{java_class_name} = ') - sb(f'{sanitized_namespace}::J{java_class_name};\n') - sb(f'using J{java_class_name}Class = ') - sb(f'{sanitized_namespace}::_J{java_class_name};\n') - sb('\n') + if using_jni_namespace: + with sb.namespace(jni_namespace): + sb(f'using J{java_class_name} = ') + sb(f'::{cpp_class_namespace}::J{java_class_name};\n') + sb(f'using J{java_class_name}Class = ') + sb(f'::jni_zero::internal::_CalledByNatives<J{java_class_name}>;\n') + sb('\n') -def mirrored_cpp_method_declaration(sb, cbn, include_class): - if not include_class and (cbn.static or cbn.is_constructor): - sb('static ') - sb(f'{_return_type_cpp_mirror(cbn)[0]} ') - if include_class: - sb(f'_J{cbn.java_class.nested_name}::') - sb(f'{_sanitize_method_name(cbn.method_id_function_name)}') - with sb.param_list() as plist: - plist.append('JNIEnv* env') - plist.extend(f'{_param_type_cpp(p.java_type)} {p.cpp_name()}' - for p in cbn.params) - - -def mirrored_cpp_class_definition(sb, called_by_natives, jni_namespace): - if not called_by_natives: - return - - java_class = called_by_natives[0].java_class - sanitized_namespace = _sanitize_namespace(java_class.package_with_colons) - with sb.namespace(sanitized_namespace): - if jni_namespace: - sb(f'using namespace {jni_namespace};\n') - sb('\n') +def mirrored_cpp_class_actual_implementation(sb, java_class, called_by_natives): + java_class_name = java_class.nested_name + sb('template<>\n') + sb(f'class _CalledByNatives<J{java_class_name}>') + with sb.block(after=';'): + sb('public:\n') for cbn in called_by_natives: - mirrored_cpp_method_definition(sb, cbn) + mirrored_cpp_method(sb, cbn) sb('\n') sb('\n') -def mirrored_cpp_method_definition(sb, cbn): - sb('inline ') - mirrored_cpp_method_declaration(sb, cbn, include_class=True) - +def mirrored_cpp_method(sb, cbn): java_class_name = cbn.java_class.nested_name is_static = cbn.static or cbn.is_constructor + method_name_cpp = _sanitize_method_name(cbn.method_id_function_name) + return_type_cpp, returned_mirrored_class = _return_type_cpp_mirror_others(cbn) + + if is_static: + sb('static ') + sb(f'{return_type_cpp} {method_name_cpp}') + with sb.param_list() as plist: + plist.append('JNIEnv* env') + plist.extend(f'{_param_type_cpp_mirror(p.java_type)} {p.cpp_name()}' + for p in cbn.params) + if not is_static: + sb(' const') + with sb.block(): if not is_static: - sb('auto this_obj = ') - sb(f'jni_zero::internal::AsJavaRef<J{java_class_name}>(this);\n') + sb('auto this_obj = reinterpret_cast') + sb(f'<const JavaRef<J{java_class_name}>*>(this);\n') + with sb.statement(): - sb('return ') + if returned_mirrored_class: + sb('auto ret = ') + else: + sb('return ') sb(f'Java_{java_class_name}_{cbn.method_id_function_name}') with sb.param_list() as plist: plist.append('env') if not is_static: - plist.append('this_obj') + plist.append('*this_obj') for p in cbn.params: expr = p.cpp_name() if java_type := p.java_type.converted_type: if not p.java_type.is_primitive(): expr = f'std::move({expr})' plist.append(expr) + + if returned_mirrored_class: + return_type_java_class_name = cbn.return_type.java_class.nested_name + return_type_namespace = cbn.return_type.java_class.package_with_colons + return_type_mirrored_class = (f'::{return_type_namespace}' + f'::J{return_type_java_class_name}') + sb(f'return jni_zero::Cast<{return_type_mirrored_class}>') + sb(f'(env, std::move(ret));\n')
diff --git a/third_party/jni_zero/common.py b/third_party/jni_zero/common.py index 866fd53..9dc3888 100644 --- a/third_party/jni_zero/common.py +++ b/third_party/jni_zero/common.py
@@ -107,17 +107,21 @@ self('\n') @contextlib.contextmanager - def namespace(self, namespace_name): + def namespace(self, namespace_name, skip_newline=False): if namespace_name is None: yield return value = f' {namespace_name}' if namespace_name else '' - self(f'namespace{value} {{\n\n') + self(f'namespace{value} {{\n') + if not skip_newline: + self('\n') yield + if not skip_newline: + self('\n') if self._in_cpp_macro: - self(f'\n}} /* namespace{value} */\n') + self(f'}} /* namespace{value} */\n') else: - self(f'\n}} // namespace{value}\n') + self(f'}} // namespace{value}\n') @contextlib.contextmanager def block(self, *, indent=2, after=None, no_trailing_newline=False):
diff --git a/third_party/jni_zero/java_refs.h b/third_party/jni_zero/java_refs.h index a9a659c1..3acce9c 100644 --- a/third_party/jni_zero/java_refs.h +++ b/third_party/jni_zero/java_refs.h
@@ -62,6 +62,14 @@ // Should only be used by the JNI Zero code generator. template <typename T> JavaRef<T> AsJavaRef(const T& obj); + +// Forward declaration of template class that contains @CalledByNative methods. +template <typename T> +class _CalledByNatives; + +// Concept to check if the _CalledByNatives<T> specialization is defined. +template <typename T> +concept HasCalledByNatives = requires { sizeof(_CalledByNatives<T>); }; } // namespace internal // Template specialization of JavaRef, which acts as the base class for all @@ -164,7 +172,14 @@ T obj() const { return static_cast<T>(JavaRef<jobject>::obj()); } - T operator->() const { return obj(); } + // Define this only when the _jni.h header has been #included. + const internal::_CalledByNatives<T>* operator->() const + requires internal::HasCalledByNatives<T> + { + // CalledByNatives does the reverse reinterpret_cast<>. + // This approach optimizes better than passing |this| as a parameter. + return reinterpret_cast<const internal::_CalledByNatives<T>*>(this); + } // Get a JavaObjectArrayReader for the array pointed to by this reference. // Only defined for JavaRef<jobjectArray>.
diff --git a/third_party/jni_zero/java_types.py b/third_party/jni_zero/java_types.py index 62d7ac23a..a499fdc 100644 --- a/third_party/jni_zero/java_types.py +++ b/third_party/jni_zero/java_types.py
@@ -277,10 +277,12 @@ """Converts to types used over JNI boundary.""" return self if self.is_primitive() else OBJECT - def enable_mirror(self, java_class): + def enable_mirror(self, java_class=None): """Whether to use a jobject subclass e.g. JMyClass.""" - return (not self.converted_type and self.array_dimensions == 0 - and self.java_class == java_class) + return (self.java_class and self.java_class.full_name_with_slashes + not in CPP_TYPE_BY_JAVA_TYPE and not self.converted_type + and self.array_dimensions == 0 + and (not java_class or self.java_class == java_class)) @dataclasses.dataclass(frozen=True)
diff --git a/third_party/jni_zero/jni_generator.py b/third_party/jni_zero/jni_generator.py index fc257d9c..f4c195b 100644 --- a/third_party/jni_zero/jni_generator.py +++ b/third_party/jni_zero/jni_generator.py
@@ -316,6 +316,15 @@ return sorted(classes) + def GetClassesToBeLazilyDefined(self): + classes = set() + for n in self.called_by_natives: + for t in list(n.signature.param_types) + [n.return_type]: + if not t.enable_mirror(): + continue + classes.add(t.java_class) + return sorted(classes) + def RemoveTestOnlyNatives(self): self.natives = [n for n in self.natives if not n.is_test_only] @@ -394,14 +403,9 @@ by_java_class = _GroupByJavaClass(jni_obj) with shared_sb.section('C++ classes that mirror the Java classes:'): - if jni_obj.jni_namespace: - shared_sb('// Declare the namespace so that it can be used by ' - '"using namespace"\n') - shared_sb(f'namespace {jni_obj.jni_namespace} {{}}\n') - shared_sb('\n') - for java_class, cbns in by_java_class.items(): - called_by_native_header.mirrored_cpp_class_declaration( - shared_sb, java_class, cbns, jni_obj.jni_namespace) + for java_class in by_java_class: + called_by_native_header.mirrored_cpp_class_lazy_definition( + shared_sb, java_class, True, jni_obj.jni_namespace) if add_natives_macro_definition: natives_header.natives_macro_definition( @@ -461,11 +465,27 @@ called_by_native_header.field_definition(unshared_sb, jni_obj.java_class, field) - if jni_obj.called_by_natives: - with unshared_sb.section('C++ classes that mirror the Java classes:'): - for _, cbns in by_java_class.items(): - called_by_native_header.mirrored_cpp_class_definition( - unshared_sb, cbns, jni_obj.jni_namespace) + with unshared_sb.section('Lazy definition of C++ classes JMyClass:'): + lazily_defined_classes = jni_obj.GetClassesToBeLazilyDefined() + for lazily_defined_class in lazily_defined_classes: + if lazily_defined_class in by_java_class: + continue + called_by_native_header.mirrored_cpp_class_lazy_definition( + unshared_sb, lazily_defined_class) + + with unshared_sb.section('Actual implementations of C++ classes JMyClass:'): + if jni_obj.jni_namespace: + unshared_sb('// Declare the namespace so that it can be used by ' + '"using namespace"\n') + unshared_sb(f'namespace {jni_obj.jni_namespace} {{}}\n') + unshared_sb('\n') + with unshared_sb.namespace('jni_zero::internal'): + if jni_obj.jni_namespace: + unshared_sb(f'using namespace {jni_obj.jni_namespace};\n') + unshared_sb('\n') + for java_class, cbns in by_java_class.items(): + called_by_native_header.mirrored_cpp_class_actual_implementation( + unshared_sb, java_class, cbns) shared_sb(shared_epilogue) unshared_sb(unshared_epilogue)
diff --git a/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_jni.h.golden b/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_jni.h.golden index e48e096b8..5348478 100644 --- a/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_jni.h.golden +++ b/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_jni.h.golden
@@ -1122,176 +1122,236 @@ } // namespace jni_zero::tests -// C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_java_util_JLinkedList_DEFINED +namespace java::util { +class _JLinkedList : public _jobject {}; +using JLinkedList = _JLinkedList*; +} // namespace java::util +#define _JNI_ZERO_java_util_JLinkedList_DEFINED +#endif + +#ifndef _JNI_ZERO_java_util_JMap_DEFINED +namespace java::util { +class _JMap : public _jobject {}; +using JMap = _JMap*; +} // namespace java::util +#define _JNI_ZERO_java_util_JMap_DEFINED +#endif + +#ifndef _JNI_ZERO_org_jni_zero_JInnerEnum_DEFINED +namespace org::jni_zero { +class _JInnerEnum : public _jobject {}; +using JInnerEnum = _JInnerEnum*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInnerEnum_DEFINED +#endif + +#ifndef _JNI_ZERO_org_jni_zero_JInnerInterface_DEFINED +namespace org::jni_zero { +class _JInnerInterface : public _jobject {}; +using JInnerInterface = _JInnerInterface*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInnerInterface_DEFINED +#endif + +#ifndef _JNI_ZERO_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +namespace org::jni_zero::extrapackage { +class _JImportsTinySample : public _jobject {}; +using JImportsTinySample = _JImportsTinySample*; +} // namespace org::jni_zero::extrapackage +#define _JNI_ZERO_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +#endif + + +// Actual implementations of C++ classes JMyClass: +// Declare the namespace so that it can be used by "using namespace" +namespace jni_zero::tests {} + +namespace jni_zero::internal { using namespace jni_zero::tests; -inline jni_zero::ScopedJavaLocalRef<JSampleForTests> _JSampleForTests::Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar) { - return Java_SampleForTests_Constructor(env, foo, bar); -} +template<> +class _CalledByNatives<JSampleForTests> { + public: + static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( + JNIEnv* env, + JniIntWrapper foo, + JniIntWrapper bar) { + return Java_SampleForTests_Constructor(env, foo, bar); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_addStructA(env, this_obj, a, b); -} + jni_zero::ScopedJavaLocalRef<::org::jni_zero::JInnerStructA> addStructA( + JNIEnv* env, + const jni_zero::JavaRef<::org::jni_zero::JInnerStructA>& a, + const jni_zero::JavaRef<::org::jni_zero::extrapackage::JImportsTinySample>& b) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + auto ret = Java_SampleForTests_addStructA(env, *this_obj, a, b); + return jni_zero::Cast<::org::jni_zero::JInnerStructA>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jclass> _JSampleForTests::getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getClass(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jclass> getClass( + JNIEnv* env, + const jni_zero::JavaRef<jclass>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getClass(env, *this_obj, arg0); + } -inline std::string _JSampleForTests::getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getFirstString(env, this_obj, std::move(array), std::move(finalArg)); -} + std::string getFirstString( + JNIEnv* env, + std::vector<const char*> const& array, + const char* const& finalArg) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getFirstString( + env, + *this_obj, + std::move(array), + std::move(finalArg)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum(JNIEnv* env) { - return Java_SampleForTests_getInnerEnum(env); -} + static jni_zero::ScopedJavaLocalRef<::org::jni_zero::JInnerEnum> getInnerEnum(JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerEnum(env); + return jni_zero::Cast<::org::jni_zero::JInnerEnum>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum( - JNIEnv* env, - JniIntWrapper a) { - return Java_SampleForTests_getInnerEnum(env, a); -} + static jni_zero::ScopedJavaLocalRef<::org::jni_zero::extrapackage::JImportsTinySample> getInnerEnum( + JNIEnv* env, + JniIntWrapper a) { + auto ret = Java_SampleForTests_getInnerEnum(env, a); + return jni_zero::Cast<::org::jni_zero::extrapackage::JImportsTinySample>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerInterface(JNIEnv* env) { - return Java_SampleForTests_getInnerInterface(env); -} + static jni_zero::ScopedJavaLocalRef<::org::jni_zero::JInnerInterface> getInnerInterface( + JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerInterface(env); + return jni_zero::Cast<::org::jni_zero::JInnerInterface>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jthrowable> _JSampleForTests::getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getThrowable(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( + JNIEnv* env, + const jni_zero::JavaRef<jthrowable>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getThrowable(env, *this_obj, arg0); + } -inline void _JSampleForTests::iterateAndDoSomething(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_iterateAndDoSomething(env, this_obj); -} + void iterateAndDoSomething(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_iterateAndDoSomething(env, *this_obj); + } -inline jint _JSampleForTests::javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_javaMethod(env, this_obj, _jcaller, ret); -} + jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_javaMethod(env, *this_obj, _jcaller, ret); + } -inline std::vector<int32_t> _JSampleForTests::jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_jniTypesAndAnnotations(env, this_obj, foo, std::move(bar), baz, bat); -} + std::vector<int32_t> jniTypesAndAnnotations( + JNIEnv* env, + MyEnum foo, + std::vector<int32_t> const& bar, + JniIntWrapper baz, + long bat) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_jniTypesAndAnnotations( + env, + *this_obj, + foo, + std::move(bar), + baz, + bat); + } -inline std::vector<jni_zero::ScopedJavaLocalRef<jobject>> _JSampleForTests::listTest1( - JNIEnv* env, - std::vector<std::string> const& items) { - return Java_SampleForTests_listTest1(env, std::move(items)); -} + static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( + JNIEnv* env, + std::vector<std::string> const& items) { + return Java_SampleForTests_listTest1(env, std::move(items)); + } -inline std::map<std::string, std::string> _JSampleForTests::mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0) { - return Java_SampleForTests_mapTest1(env, std::move(arg0)); -} + static std::map<std::string, std::string> mapTest1( + JNIEnv* env, + std::map<std::string, std::string> const& arg0) { + return Java_SampleForTests_mapTest1(env, std::move(arg0)); + } -inline void _JSampleForTests::methodThatThrowsException(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodThatThrowsException(env, this_obj); -} + void methodThatThrowsException(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodThatThrowsException(env, *this_obj); + } -inline jboolean _JSampleForTests::methodWithAnnotationParamAssignment(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithAnnotationParamAssignment(env, this_obj); -} + jboolean methodWithAnnotationParamAssignment(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithAnnotationParamAssignment(env, *this_obj); + } -inline void _JSampleForTests::methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithGenericParams(env, this_obj, _env, bar); -} + void methodWithGenericParams( + JNIEnv* env, + const jni_zero::JavaRef<::java::util::JMap>& _env, + const jni_zero::JavaRef<::java::util::JLinkedList>& bar) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithGenericParams(env, *this_obj, _env, bar); + } -inline void _JSampleForTests::packagePrivateJavaMethod(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_packagePrivateJavaMethod(env, this_obj); -} + void packagePrivateJavaMethod(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_packagePrivateJavaMethod(env, *this_obj); + } -inline std::vector<bool> _JSampleForTests::primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d) { - return Java_SampleForTests_primitiveArrays( - env, - std::move(b), - std::move(c), - std::move(s), - std::move(i), - std::move(l), - std::move(f), - std::move(d)); -} + static std::vector<bool> primitiveArrays( + JNIEnv* env, + std::vector<uint8_t> const& b, + std::vector<uint16_t> const& c, + std::vector<int16_t> const& s, + std::vector<int32_t> const& i, + std::vector<int64_t> const& l, + std::vector<float> const& f, + std::vector<double> const& d) { + return Java_SampleForTests_primitiveArrays( + env, + std::move(b), + std::move(c), + std::move(s), + std::move(i), + std::move(l), + std::move(f), + std::move(d)); + } -inline jboolean _JSampleForTests::staticJavaMethod(JNIEnv* env) { - return Java_SampleForTests_staticJavaMethod(env); -} + static jboolean staticJavaMethod(JNIEnv* env) { + return Java_SampleForTests_staticJavaMethod(env); + } + +}; + +template<> +class _CalledByNatives<JInnerStructA> { + public: + static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( + JNIEnv* env, + jlong l, + JniIntWrapper i, + const jni_zero::JavaRef<jstring>& s) { + return Java_InnerStructA_create(env, l, i, s); + } + +}; + +template<> +class _CalledByNatives<JInnerStructB> { + public: + jlong getKey(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getKey(env, *this_obj); + } + + jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getValue(env, *this_obj); + } + +}; -} // namespace org1::jni_zero1 - -namespace org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jni_zero::ScopedJavaLocalRef<JInnerStructA> _JInnerStructA::create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s) { - return Java_InnerStructA_create(env, l, i, s); -} - - -} // namespace org1::jni_zero1 - -namespace org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jlong _JInnerStructB::getKey(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getKey(env, this_obj); -} - -inline jni_zero::ScopedJavaLocalRef<jstring> _JInnerStructB::getValue(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getValue(env, this_obj); -} - - -} // namespace org1::jni_zero1 - +} // namespace jni_zero::internal #endif // org_jni_1zero_SampleForTests_JNI
diff --git a/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_shared_jni.h.golden b/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_shared_jni.h.golden index 36a299e24..aee6588 100644 --- a/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testBidirectionalClass-SampleForTests_shared_jni.h.golden
@@ -12,147 +12,48 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -// Declare the namespace so that it can be used by "using namespace" -namespace jni_zero::tests {} - -namespace org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JSampleForTests; +#ifndef _JNI_ZERO_org_jni_zero_JSampleForTests_DEFINED +namespace org::jni_zero { +class _JSampleForTests : public _jobject {}; using JSampleForTests = _JSampleForTests*; - -class _JSampleForTests : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar); - - jni_zero::ScopedJavaLocalRef<jobject> addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b); - - jni_zero::ScopedJavaLocalRef<jclass> getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0); - - std::string getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env, JniIntWrapper a); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerInterface(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0); - - void iterateAndDoSomething(JNIEnv* env); - - jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret); - - std::vector<int32_t> jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat); - - static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( - JNIEnv* env, - std::vector<std::string> const& items); - - static std::map<std::string, std::string> mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0); - - void methodThatThrowsException(JNIEnv* env); - - jboolean methodWithAnnotationParamAssignment(JNIEnv* env); - - void methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar); - - void packagePrivateJavaMethod(JNIEnv* env); - - static std::vector<bool> primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d); - - static jboolean staticJavaMethod(JNIEnv* env); - -}; - -} // namespace org1::jni_zero1 +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleForTests_DEFINED +#endif namespace jni_zero::tests { -using JSampleForTests = org1::jni_zero1::JSampleForTests; -using JSampleForTestsClass = org1::jni_zero1::_JSampleForTests; +using JSampleForTests = ::org::jni_zero::JSampleForTests; +using JSampleForTestsClass = ::jni_zero::internal::_CalledByNatives<JSampleForTests>; } // namespace jni_zero::tests -namespace org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructA; +#ifndef _JNI_ZERO_org_jni_zero_JInnerStructA_DEFINED +namespace org::jni_zero { +class _JInnerStructA : public _jobject {}; using JInnerStructA = _JInnerStructA*; - -class _JInnerStructA : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s); - -}; - -} // namespace org1::jni_zero1 +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInnerStructA_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructA = org1::jni_zero1::JInnerStructA; -using JInnerStructAClass = org1::jni_zero1::_JInnerStructA; +using JInnerStructA = ::org::jni_zero::JInnerStructA; +using JInnerStructAClass = ::jni_zero::internal::_CalledByNatives<JInnerStructA>; } // namespace jni_zero::tests -namespace org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructB; +#ifndef _JNI_ZERO_org_jni_zero_JInnerStructB_DEFINED +namespace org::jni_zero { +class _JInnerStructB : public _jobject {}; using JInnerStructB = _JInnerStructB*; - -class _JInnerStructB : public _jobject { - public: - jlong getKey(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env); - -}; - -} // namespace org1::jni_zero1 +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInnerStructB_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructB = org1::jni_zero1::JInnerStructB; -using JInnerStructBClass = org1::jni_zero1::_JInnerStructB; +using JInnerStructB = ::org::jni_zero::JInnerStructB; +using JInnerStructBClass = ::jni_zero::internal::_CalledByNatives<JInnerStructB>; } // namespace jni_zero::tests
diff --git a/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_jni.h.golden b/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_jni.h.golden index af90fa3..4e6ac13 100644 --- a/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_jni.h.golden +++ b/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_jni.h.golden
@@ -301,62 +301,103 @@ } -// C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_java_lang_JInteger_DEFINED +namespace java::lang { +class _JInteger : public _jobject {}; +using JInteger = _JInteger*; +} // namespace java::lang +#define _JNI_ZERO_java_lang_JInteger_DEFINED +#endif -inline void _JSampleBidirectionalNonProxy::addStructB( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& caller, - const jni_zero::JavaRef<jobject>& b) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleBidirectionalNonProxy>(this); - return Java_SampleBidirectionalNonProxy_addStructB(env, this_obj, caller, b); -} +#ifndef _JNI_ZERO_java_lang_JStringBuilder_DEFINED +namespace java::lang { +class _JStringBuilder : public _jobject {}; +using JStringBuilder = _JStringBuilder*; +} // namespace java::lang +#define _JNI_ZERO_java_lang_JStringBuilder_DEFINED +#endif -inline void _JSampleBidirectionalNonProxy::setBool( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& b, - const jni_zero::JavaRef<jobject>& i) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleBidirectionalNonProxy>(this); - return Java_SampleBidirectionalNonProxy_setBool(env, this_obj, b, i); -} +#ifndef _JNI_ZERO_org_jni_zero_JBoolean_DEFINED +namespace org::jni_zero { +class _JBoolean : public _jobject {}; +using JBoolean = _JBoolean*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JBoolean_DEFINED +#endif -inline void _JSampleBidirectionalNonProxy::setStringBuilder( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& sb) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleBidirectionalNonProxy>(this); - return Java_SampleBidirectionalNonProxy_setStringBuilder(env, this_obj, sb); -} +#ifndef _JNI_ZERO_org_jni_zero_JSampleForTests_DEFINED +namespace org::jni_zero { +class _JSampleForTests : public _jobject {}; +using JSampleForTests = _JSampleForTests*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleForTests_DEFINED +#endif -inline double _JSampleBidirectionalNonProxy::testMethodWithNoParam(JNIEnv* env) { - return Java_SampleBidirectionalNonProxy_testMethodWithNoParam(env); -} - -inline void _JSampleBidirectionalNonProxy::testMethodWithParam(JNIEnv* env, JniIntWrapper iParam) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleBidirectionalNonProxy>(this); - return Java_SampleBidirectionalNonProxy_testMethodWithParam(env, this_obj, iParam); -} - -inline jni_zero::ScopedJavaLocalRef<jstring> _JSampleBidirectionalNonProxy::testMethodWithParamAndReturn( - JNIEnv* env, - JniIntWrapper iParam) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleBidirectionalNonProxy>(this); - return Java_SampleBidirectionalNonProxy_testMethodWithParamAndReturn(env, this_obj, iParam); -} - -inline jni_zero::ScopedJavaLocalRef<jstring> _JSampleBidirectionalNonProxy::testStaticMethodWithNoParam( - JNIEnv* env) { - return Java_SampleBidirectionalNonProxy_testStaticMethodWithNoParam(env); -} - -inline int32_t _JSampleBidirectionalNonProxy::testStaticMethodWithParam( - JNIEnv* env, - JniIntWrapper iParam) { - return Java_SampleBidirectionalNonProxy_testStaticMethodWithParam(env, iParam); -} +#ifndef _JNI_ZERO_org_jni_zero_JInnerStructB_DEFINED +namespace org::jni_zero { +class _JInnerStructB : public _jobject {}; +using JInnerStructB = _JInnerStructB*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInnerStructB_DEFINED +#endif -} // namespace org1::jni_zero1 +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { +template<> +class _CalledByNatives<JSampleBidirectionalNonProxy> { + public: + void addStructB( + JNIEnv* env, + const jni_zero::JavaRef<::org::jni_zero::JSampleForTests>& caller, + const jni_zero::JavaRef<::org::jni_zero::JInnerStructB>& b) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleBidirectionalNonProxy>*>(this); + return Java_SampleBidirectionalNonProxy_addStructB(env, *this_obj, caller, b); + } + + void setBool( + JNIEnv* env, + const jni_zero::JavaRef<::org::jni_zero::JBoolean>& b, + const jni_zero::JavaRef<::java::lang::JInteger>& i) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleBidirectionalNonProxy>*>(this); + return Java_SampleBidirectionalNonProxy_setBool(env, *this_obj, b, i); + } + + void setStringBuilder(JNIEnv* env, const jni_zero::JavaRef<::java::lang::JStringBuilder>& sb) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleBidirectionalNonProxy>*>(this); + return Java_SampleBidirectionalNonProxy_setStringBuilder(env, *this_obj, sb); + } + + static double testMethodWithNoParam(JNIEnv* env) { + return Java_SampleBidirectionalNonProxy_testMethodWithNoParam(env); + } + + void testMethodWithParam(JNIEnv* env, JniIntWrapper iParam) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleBidirectionalNonProxy>*>(this); + return Java_SampleBidirectionalNonProxy_testMethodWithParam(env, *this_obj, iParam); + } + + jni_zero::ScopedJavaLocalRef<jstring> testMethodWithParamAndReturn( + JNIEnv* env, + JniIntWrapper iParam) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleBidirectionalNonProxy>*>(this); + return Java_SampleBidirectionalNonProxy_testMethodWithParamAndReturn(env, *this_obj, iParam); + } + + static jni_zero::ScopedJavaLocalRef<jstring> testStaticMethodWithNoParam(JNIEnv* env) { + return Java_SampleBidirectionalNonProxy_testStaticMethodWithNoParam(env); + } + + static int32_t testStaticMethodWithParam(JNIEnv* env, JniIntWrapper iParam) { + return Java_SampleBidirectionalNonProxy_testStaticMethodWithParam(env, iParam); + } + +}; + + +} // namespace jni_zero::internal #endif // org_jni_1zero_SampleBidirectionalNonProxy_JNI
diff --git a/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_shared_jni.h.golden b/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_shared_jni.h.golden index aa858a0..c45b52c 100644 --- a/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testBirectionalNonProxy-SampleBidirectionalNonProxy_shared_jni.h.golden
@@ -12,43 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleBidirectionalNonProxy; +#ifndef _JNI_ZERO_org_jni_zero_JSampleBidirectionalNonProxy_DEFINED +namespace org::jni_zero { +class _JSampleBidirectionalNonProxy : public _jobject {}; using JSampleBidirectionalNonProxy = _JSampleBidirectionalNonProxy*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleBidirectionalNonProxy_DEFINED +#endif -class _JSampleBidirectionalNonProxy : public _jobject { - public: - void addStructB( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& caller, - const jni_zero::JavaRef<jobject>& b); - - void setBool( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& b, - const jni_zero::JavaRef<jobject>& i); - - void setStringBuilder(JNIEnv* env, const jni_zero::JavaRef<jobject>& sb); - - static double testMethodWithNoParam(JNIEnv* env); - - void testMethodWithParam(JNIEnv* env, JniIntWrapper iParam); - - jni_zero::ScopedJavaLocalRef<jstring> testMethodWithParamAndReturn( - JNIEnv* env, - JniIntWrapper iParam); - - static jni_zero::ScopedJavaLocalRef<jstring> testStaticMethodWithNoParam(JNIEnv* env); - - static int32_t testStaticMethodWithParam(JNIEnv* env, JniIntWrapper iParam); - -}; - -} // namespace org1::jni_zero1 - -using JSampleBidirectionalNonProxy = org1::jni_zero1::JSampleBidirectionalNonProxy; -using JSampleBidirectionalNonProxyClass = org1::jni_zero1::_JSampleBidirectionalNonProxy; +using JSampleBidirectionalNonProxy = ::org::jni_zero::JSampleBidirectionalNonProxy; +using JSampleBidirectionalNonProxyClass = ::jni_zero::internal::_CalledByNatives<JSampleBidirectionalNonProxy>;
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden index 0f2b013..32d28686 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden
@@ -878,5 +878,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleForAnnotationProcessor> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleForAnnotationProcessor_JNI
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_shared_jni.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_shared_jni.h.golden index 33ad9e6f4..f58ba0b 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleForAnnotationProcessor; +#ifndef _JNI_ZERO_org_jni_zero_JSampleForAnnotationProcessor_DEFINED +namespace org::jni_zero { +class _JSampleForAnnotationProcessor : public _jobject {}; using JSampleForAnnotationProcessor = _JSampleForAnnotationProcessor*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleForAnnotationProcessor_DEFINED +#endif -class _JSampleForAnnotationProcessor : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleForAnnotationProcessor = org1::jni_zero1::JSampleForAnnotationProcessor; -using JSampleForAnnotationProcessorClass = org1::jni_zero1::_JSampleForAnnotationProcessor; +using JSampleForAnnotationProcessor = ::org::jni_zero::JSampleForAnnotationProcessor; +using JSampleForAnnotationProcessorClass = ::jni_zero::internal::_CalledByNatives<JSampleForAnnotationProcessor>;
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_jni.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_jni.h.golden index 6bcf9ce..7f03d7f9 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_jni.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_jni.h.golden
@@ -65,5 +65,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleModule> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleModule_JNI
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_shared_jni.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_shared_jni.h.golden index a137e00..49a419b 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyJniWithModules-SampleModule_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleModule; +#ifndef _JNI_ZERO_org_jni_zero_JSampleModule_DEFINED +namespace org::jni_zero { +class _JSampleModule : public _jobject {}; using JSampleModule = _JSampleModule*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleModule_DEFINED +#endif -class _JSampleModule : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleModule = org1::jni_zero1::JSampleModule; -using JSampleModuleClass = org1::jni_zero1::_JSampleModule; +using JSampleModule = ::org::jni_zero::JSampleModule; +using JSampleModuleClass = ::jni_zero::internal::_CalledByNatives<JSampleModule>;
diff --git a/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_jni.h.golden b/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_jni.h.golden index 07c4140..ffbca67 100644 --- a/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_jni.h.golden +++ b/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_jni.h.golden
@@ -374,5 +374,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleProxyEdgeCases> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleProxyEdgeCases_JNI
diff --git a/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_shared_jni.h.golden b/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_shared_jni.h.golden index ba1bff4..7f5c355 100644 --- a/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testForTestingKeptHash-SampleProxyEdgeCases_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleProxyEdgeCases; +#ifndef _JNI_ZERO_org_jni_zero_JSampleProxyEdgeCases_DEFINED +namespace org::jni_zero { +class _JSampleProxyEdgeCases : public _jobject {}; using JSampleProxyEdgeCases = _JSampleProxyEdgeCases*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleProxyEdgeCases_DEFINED +#endif -class _JSampleProxyEdgeCases : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleProxyEdgeCases = org1::jni_zero1::JSampleProxyEdgeCases; -using JSampleProxyEdgeCasesClass = org1::jni_zero1::_JSampleProxyEdgeCases; +using JSampleProxyEdgeCases = ::org::jni_zero::JSampleProxyEdgeCases; +using JSampleProxyEdgeCasesClass = ::jni_zero::internal::_CalledByNatives<JSampleProxyEdgeCases>;
diff --git a/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_jni.h.golden b/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_jni.h.golden index 9295f1b..1a3d39b 100644 --- a/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_jni.h.golden +++ b/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_jni.h.golden
@@ -358,5 +358,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleProxyEdgeCases> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleProxyEdgeCases_JNI
diff --git a/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_shared_jni.h.golden b/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_shared_jni.h.golden index ba1bff4..7f5c355 100644 --- a/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testForTestingKeptMultiplexing-SampleProxyEdgeCases_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleProxyEdgeCases; +#ifndef _JNI_ZERO_org_jni_zero_JSampleProxyEdgeCases_DEFINED +namespace org::jni_zero { +class _JSampleProxyEdgeCases : public _jobject {}; using JSampleProxyEdgeCases = _JSampleProxyEdgeCases*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleProxyEdgeCases_DEFINED +#endif -class _JSampleProxyEdgeCases : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleProxyEdgeCases = org1::jni_zero1::JSampleProxyEdgeCases; -using JSampleProxyEdgeCasesClass = org1::jni_zero1::_JSampleProxyEdgeCases; +using JSampleProxyEdgeCases = ::org::jni_zero::JSampleProxyEdgeCases; +using JSampleProxyEdgeCasesClass = ::jni_zero::internal::_CalledByNatives<JSampleProxyEdgeCases>;
diff --git a/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_jni.h.golden b/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_jni.h.golden index b6a085d..99b8ce37 100644 --- a/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_jni.h.golden +++ b/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_jni.h.golden
@@ -295,70 +295,91 @@ } // namespace JNI_JavapClass -// C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_java_lang_JObject_DEFINED +namespace java::lang { +class _JObject : public _jobject {}; +using JObject = _JObject*; +} // namespace java::lang +#define _JNI_ZERO_java_lang_JObject_DEFINED +#endif + +#ifndef _JNI_ZERO_java_util_JArrayList_DEFINED +namespace java::util { +class _JArrayList : public _jobject {}; +using JArrayList = _JArrayList*; +} // namespace java::util +#define _JNI_ZERO_java_util_JArrayList_DEFINED +#endif + + +// Actual implementations of C++ classes JMyClass: +// Declare the namespace so that it can be used by "using namespace" +namespace JNI_JavapClass {} + +namespace jni_zero::internal { using namespace JNI_JavapClass; -inline jni_zero::ScopedJavaLocalRef<JJavapClass> _JJavapClass::Constructor(JNIEnv* env) { - return Java_JavapClass_Constructor(env); -} +template<> +class _CalledByNatives<JJavapClass> { + public: + static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor(JNIEnv* env) { + return Java_JavapClass_Constructor(env); + } -inline jni_zero::ScopedJavaLocalRef<JJavapClass> _JJavapClass::Constructor__boolean( - JNIEnv* env, - jboolean p0) { - return Java_JavapClass_Constructor__boolean(env, p0); -} + static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor__boolean(JNIEnv* env, jboolean p0) { + return Java_JavapClass_Constructor__boolean(env, p0); + } -inline jni_zero::ScopedJavaLocalRef<JJavapClass> _JJavapClass::Constructor__int( - JNIEnv* env, - JniIntWrapper p0) { - return Java_JavapClass_Constructor__int(env, p0); -} + static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor__int(JNIEnv* env, JniIntWrapper p0) { + return Java_JavapClass_Constructor__int(env, p0); + } -inline jint _JJavapClass::intMethod(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0) { - auto this_obj = jni_zero::internal::AsJavaRef<JJavapClass>(this); - return Java_JavapClass_intMethod(env, this_obj, p0); -} + jint intMethod(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0) const { + auto this_obj = reinterpret_cast<const JavaRef<JJavapClass>*>(this); + return Java_JavapClass_intMethod(env, *this_obj, p0); + } -inline void _JJavapClass::needsMangling__int(JNIEnv* env, JniIntWrapper p0) { - return Java_JavapClass_needsMangling__int(env, p0); -} + static void needsMangling__int(JNIEnv* env, JniIntWrapper p0) { + return Java_JavapClass_needsMangling__int(env, p0); + } -inline void _JJavapClass::needsMangling__String(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0) { - return Java_JavapClass_needsMangling__String(env, p0); -} + static void needsMangling__String(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0) { + return Java_JavapClass_needsMangling__String(env, p0); + } -inline void _JJavapClass::needsMangling__java_util_ArrayList( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& p0) { - return Java_JavapClass_needsMangling__java_util_ArrayList(env, p0); -} + static void needsMangling__java_util_ArrayList( + JNIEnv* env, + const jni_zero::JavaRef<::java::util::JArrayList>& p0) { + return Java_JavapClass_needsMangling__java_util_ArrayList(env, p0); + } -inline jni_zero::ScopedJavaLocalRef<jclass> _JJavapClass::objTest( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& p0, - const jni_zero::JavaRef<jobjectArray>& p1) { - auto this_obj = jni_zero::internal::AsJavaRef<JJavapClass>(this); - return Java_JavapClass_objTest(env, this_obj, p0, p1); -} + jni_zero::ScopedJavaLocalRef<jclass> objTest( + JNIEnv* env, + const jni_zero::JavaRef<::java::lang::JObject>& p0, + const jni_zero::JavaRef<jobjectArray>& p1) const { + auto this_obj = reinterpret_cast<const JavaRef<JJavapClass>*>(this); + return Java_JavapClass_objTest(env, *this_obj, p0, p1); + } -inline jint _JJavapClass::staticIntMethod( - JNIEnv* env, - const jni_zero::JavaRef<jstring>& p0, - const jni_zero::JavaRef<jobject>& p1) { - return Java_JavapClass_staticIntMethod(env, p0, p1); -} + static jint staticIntMethod( + JNIEnv* env, + const jni_zero::JavaRef<jstring>& p0, + const jni_zero::JavaRef<::org::jni_zero::JJavapClass>& p1) { + return Java_JavapClass_staticIntMethod(env, p0, p1); + } -inline jni_zero::ScopedJavaLocalRef<jobjectArray> _JJavapClass::staticIntMethod( - JNIEnv* env, - const jni_zero::JavaRef<jstring>& p0) { - return Java_JavapClass_staticIntMethod(env, p0); -} + static jni_zero::ScopedJavaLocalRef<jobjectArray> staticIntMethod( + JNIEnv* env, + const jni_zero::JavaRef<jstring>& p0) { + return Java_JavapClass_staticIntMethod(env, p0); + } + +}; -} // namespace org1::jni_zero1 - +} // namespace jni_zero::internal #endif // org_jni_1zero_JavapClass_JNI
diff --git a/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_shared_jni.h.golden b/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_shared_jni.h.golden index 6dd244a..6c9fff24 100644 --- a/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testFromClassFile-JavapClass_shared_jni.h.golden
@@ -12,56 +12,18 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -// Declare the namespace so that it can be used by "using namespace" -namespace JNI_JavapClass {} - -namespace org1::jni_zero1 { - -using namespace JNI_JavapClass; - -class _JJavapClass; +#ifndef _JNI_ZERO_org_jni_zero_JJavapClass_DEFINED +namespace org::jni_zero { +class _JJavapClass : public _jobject {}; using JJavapClass = _JJavapClass*; - -class _JJavapClass : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor(JNIEnv* env); - - static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor__boolean(JNIEnv* env, jboolean p0); - - static jni_zero::ScopedJavaLocalRef<JJavapClass> Constructor__int(JNIEnv* env, JniIntWrapper p0); - - jint intMethod(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0); - - static void needsMangling__int(JNIEnv* env, JniIntWrapper p0); - - static void needsMangling__String(JNIEnv* env, const jni_zero::JavaRef<jstring>& p0); - - static void needsMangling__java_util_ArrayList( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& p0); - - jni_zero::ScopedJavaLocalRef<jclass> objTest( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& p0, - const jni_zero::JavaRef<jobjectArray>& p1); - - static jint staticIntMethod( - JNIEnv* env, - const jni_zero::JavaRef<jstring>& p0, - const jni_zero::JavaRef<jobject>& p1); - - static jni_zero::ScopedJavaLocalRef<jobjectArray> staticIntMethod( - JNIEnv* env, - const jni_zero::JavaRef<jstring>& p0); - -}; - -} // namespace org1::jni_zero1 +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JJavapClass_DEFINED +#endif namespace JNI_JavapClass { -using JJavapClass = org1::jni_zero1::JJavapClass; -using JJavapClassClass = org1::jni_zero1::_JJavapClass; +using JJavapClass = ::org::jni_zero::JJavapClass; +using JJavapClassClass = ::jni_zero::internal::_CalledByNatives<JJavapClass>; } // namespace JNI_JavapClass
diff --git a/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_jni.h.golden b/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_jni.h.golden index e7ad23dc..c707236 100644 --- a/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_jni.h.golden +++ b/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_jni.h.golden
@@ -641,5 +641,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleNonProxy> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleNonProxy_JNI
diff --git a/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_shared_jni.h.golden b/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_shared_jni.h.golden index a312a28..806fe1b 100644 --- a/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testNonProxy-SampleNonProxy_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleNonProxy; +#ifndef _JNI_ZERO_org_jni_zero_JSampleNonProxy_DEFINED +namespace org::jni_zero { +class _JSampleNonProxy : public _jobject {}; using JSampleNonProxy = _JSampleNonProxy*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleNonProxy_DEFINED +#endif -class _JSampleNonProxy : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleNonProxy = org1::jni_zero1::JSampleNonProxy; -using JSampleNonProxyClass = org1::jni_zero1::_JSampleNonProxy; +using JSampleNonProxy = ::org::jni_zero::JSampleNonProxy; +using JSampleNonProxyClass = ::jni_zero::internal::_CalledByNatives<JSampleNonProxy>;
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_jni.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_jni.h.golden index 0baba7fa..df0ced6 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_jni.h.golden
@@ -1122,176 +1122,237 @@ } // namespace jni_zero::tests -// C++ classes that mirror the Java classes: -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_java_util_JLinkedList_DEFINED +namespace java::util { +class _JLinkedList : public _jobject {}; +using JLinkedList = _JLinkedList*; +} // namespace java::util +#define _JNI_ZERO_java_util_JLinkedList_DEFINED +#endif + +#ifndef _JNI_ZERO_java_util_JMap_DEFINED +namespace java::util { +class _JMap : public _jobject {}; +using JMap = _JMap*; +} // namespace java::util +#define _JNI_ZERO_java_util_JMap_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerEnum_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerEnum : public _jobject {}; +using JInnerEnum = _JInnerEnum*; +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerEnum_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerInterface_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerInterface : public _jobject {}; +using JInnerInterface = _JInnerInterface*; +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerInterface_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +namespace this::is::a::package::prefix::org::jni_zero::extrapackage { +class _JImportsTinySample : public _jobject {}; +using JImportsTinySample = _JImportsTinySample*; +} // namespace this::is::a::package::prefix::org::jni_zero::extrapackage +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +#endif + + +// Actual implementations of C++ classes JMyClass: +// Declare the namespace so that it can be used by "using namespace" +namespace jni_zero::tests {} + +namespace jni_zero::internal { using namespace jni_zero::tests; -inline jni_zero::ScopedJavaLocalRef<JSampleForTests> _JSampleForTests::Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar) { - return Java_SampleForTests_Constructor(env, foo, bar); -} +template<> +class _CalledByNatives<JSampleForTests> { + public: + static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( + JNIEnv* env, + JniIntWrapper foo, + JniIntWrapper bar) { + return Java_SampleForTests_Constructor(env, foo, bar); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_addStructA(env, this_obj, a, b); -} + jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerStructA> addStructA( + JNIEnv* env, + const jni_zero::JavaRef<::this::is::a::package::prefix::org::jni_zero::JInnerStructA>& a, + const jni_zero::JavaRef<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample>& b) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + auto ret = Java_SampleForTests_addStructA(env, *this_obj, a, b); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerStructA>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jclass> _JSampleForTests::getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getClass(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jclass> getClass( + JNIEnv* env, + const jni_zero::JavaRef<jclass>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getClass(env, *this_obj, arg0); + } -inline std::string _JSampleForTests::getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getFirstString(env, this_obj, std::move(array), std::move(finalArg)); -} + std::string getFirstString( + JNIEnv* env, + std::vector<const char*> const& array, + const char* const& finalArg) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getFirstString( + env, + *this_obj, + std::move(array), + std::move(finalArg)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum(JNIEnv* env) { - return Java_SampleForTests_getInnerEnum(env); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerEnum> getInnerEnum( + JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerEnum(env); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerEnum>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum( - JNIEnv* env, - JniIntWrapper a) { - return Java_SampleForTests_getInnerEnum(env, a); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample> getInnerEnum( + JNIEnv* env, + JniIntWrapper a) { + auto ret = Java_SampleForTests_getInnerEnum(env, a); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerInterface(JNIEnv* env) { - return Java_SampleForTests_getInnerInterface(env); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerInterface> getInnerInterface( + JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerInterface(env); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerInterface>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jthrowable> _JSampleForTests::getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getThrowable(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( + JNIEnv* env, + const jni_zero::JavaRef<jthrowable>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getThrowable(env, *this_obj, arg0); + } -inline void _JSampleForTests::iterateAndDoSomething(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_iterateAndDoSomething(env, this_obj); -} + void iterateAndDoSomething(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_iterateAndDoSomething(env, *this_obj); + } -inline jint _JSampleForTests::javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_javaMethod(env, this_obj, _jcaller, ret); -} + jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_javaMethod(env, *this_obj, _jcaller, ret); + } -inline std::vector<int32_t> _JSampleForTests::jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_jniTypesAndAnnotations(env, this_obj, foo, std::move(bar), baz, bat); -} + std::vector<int32_t> jniTypesAndAnnotations( + JNIEnv* env, + MyEnum foo, + std::vector<int32_t> const& bar, + JniIntWrapper baz, + long bat) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_jniTypesAndAnnotations( + env, + *this_obj, + foo, + std::move(bar), + baz, + bat); + } -inline std::vector<jni_zero::ScopedJavaLocalRef<jobject>> _JSampleForTests::listTest1( - JNIEnv* env, - std::vector<std::string> const& items) { - return Java_SampleForTests_listTest1(env, std::move(items)); -} + static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( + JNIEnv* env, + std::vector<std::string> const& items) { + return Java_SampleForTests_listTest1(env, std::move(items)); + } -inline std::map<std::string, std::string> _JSampleForTests::mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0) { - return Java_SampleForTests_mapTest1(env, std::move(arg0)); -} + static std::map<std::string, std::string> mapTest1( + JNIEnv* env, + std::map<std::string, std::string> const& arg0) { + return Java_SampleForTests_mapTest1(env, std::move(arg0)); + } -inline void _JSampleForTests::methodThatThrowsException(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodThatThrowsException(env, this_obj); -} + void methodThatThrowsException(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodThatThrowsException(env, *this_obj); + } -inline jboolean _JSampleForTests::methodWithAnnotationParamAssignment(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithAnnotationParamAssignment(env, this_obj); -} + jboolean methodWithAnnotationParamAssignment(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithAnnotationParamAssignment(env, *this_obj); + } -inline void _JSampleForTests::methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithGenericParams(env, this_obj, _env, bar); -} + void methodWithGenericParams( + JNIEnv* env, + const jni_zero::JavaRef<::java::util::JMap>& _env, + const jni_zero::JavaRef<::java::util::JLinkedList>& bar) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithGenericParams(env, *this_obj, _env, bar); + } -inline void _JSampleForTests::packagePrivateJavaMethod(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_packagePrivateJavaMethod(env, this_obj); -} + void packagePrivateJavaMethod(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_packagePrivateJavaMethod(env, *this_obj); + } -inline std::vector<bool> _JSampleForTests::primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d) { - return Java_SampleForTests_primitiveArrays( - env, - std::move(b), - std::move(c), - std::move(s), - std::move(i), - std::move(l), - std::move(f), - std::move(d)); -} + static std::vector<bool> primitiveArrays( + JNIEnv* env, + std::vector<uint8_t> const& b, + std::vector<uint16_t> const& c, + std::vector<int16_t> const& s, + std::vector<int32_t> const& i, + std::vector<int64_t> const& l, + std::vector<float> const& f, + std::vector<double> const& d) { + return Java_SampleForTests_primitiveArrays( + env, + std::move(b), + std::move(c), + std::move(s), + std::move(i), + std::move(l), + std::move(f), + std::move(d)); + } -inline jboolean _JSampleForTests::staticJavaMethod(JNIEnv* env) { - return Java_SampleForTests_staticJavaMethod(env); -} + static jboolean staticJavaMethod(JNIEnv* env) { + return Java_SampleForTests_staticJavaMethod(env); + } + +}; + +template<> +class _CalledByNatives<JInnerStructA> { + public: + static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( + JNIEnv* env, + jlong l, + JniIntWrapper i, + const jni_zero::JavaRef<jstring>& s) { + return Java_InnerStructA_create(env, l, i, s); + } + +}; + +template<> +class _CalledByNatives<JInnerStructB> { + public: + jlong getKey(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getKey(env, *this_obj); + } + + jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getValue(env, *this_obj); + } + +}; -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jni_zero::ScopedJavaLocalRef<JInnerStructA> _JInnerStructA::create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s) { - return Java_InnerStructA_create(env, l, i, s); -} - - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jlong _JInnerStructB::getKey(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getKey(env, this_obj); -} - -inline jni_zero::ScopedJavaLocalRef<jstring> _JInnerStructB::getValue(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getValue(env, this_obj); -} - - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - +} // namespace jni_zero::internal #endif // this_is_a_package_prefix_org_jni_1zero_SampleForTests_JNI
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_shared_jni.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_shared_jni.h.golden index eb2eae3..20bb91e9 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixGenerator-SampleForTests_shared_jni.h.golden
@@ -12,147 +12,48 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -// Declare the namespace so that it can be used by "using namespace" -namespace jni_zero::tests {} - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JSampleForTests; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JSampleForTests_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JSampleForTests : public _jobject {}; using JSampleForTests = _JSampleForTests*; - -class _JSampleForTests : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar); - - jni_zero::ScopedJavaLocalRef<jobject> addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b); - - jni_zero::ScopedJavaLocalRef<jclass> getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0); - - std::string getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env, JniIntWrapper a); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerInterface(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0); - - void iterateAndDoSomething(JNIEnv* env); - - jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret); - - std::vector<int32_t> jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat); - - static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( - JNIEnv* env, - std::vector<std::string> const& items); - - static std::map<std::string, std::string> mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0); - - void methodThatThrowsException(JNIEnv* env); - - jboolean methodWithAnnotationParamAssignment(JNIEnv* env); - - void methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar); - - void packagePrivateJavaMethod(JNIEnv* env); - - static std::vector<bool> primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d); - - static jboolean staticJavaMethod(JNIEnv* env); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JSampleForTests_DEFINED +#endif namespace jni_zero::tests { -using JSampleForTests = this1::is1::a1::package1::prefix1::org1::jni_zero1::JSampleForTests; -using JSampleForTestsClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JSampleForTests; +using JSampleForTests = ::this::is::a::package::prefix::org::jni_zero::JSampleForTests; +using JSampleForTestsClass = ::jni_zero::internal::_CalledByNatives<JSampleForTests>; } // namespace jni_zero::tests -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructA; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructA_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerStructA : public _jobject {}; using JInnerStructA = _JInnerStructA*; - -class _JInnerStructA : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructA_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructA = this1::is1::a1::package1::prefix1::org1::jni_zero1::JInnerStructA; -using JInnerStructAClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JInnerStructA; +using JInnerStructA = ::this::is::a::package::prefix::org::jni_zero::JInnerStructA; +using JInnerStructAClass = ::jni_zero::internal::_CalledByNatives<JInnerStructA>; } // namespace jni_zero::tests -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructB; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructB_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerStructB : public _jobject {}; using JInnerStructB = _JInnerStructB*; - -class _JInnerStructB : public _jobject { - public: - jlong getKey(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructB_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructB = this1::is1::a1::package1::prefix1::org1::jni_zero1::JInnerStructB; -using JInnerStructBClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JInnerStructB; +using JInnerStructB = ::this::is::a::package::prefix::org::jni_zero::JInnerStructB; +using JInnerStructBClass = ::jni_zero::internal::_CalledByNatives<JInnerStructB>; } // namespace jni_zero::tests
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_jni.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_jni.h.golden index 0baba7fa..df0ced6 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_jni.h.golden
@@ -1122,176 +1122,237 @@ } // namespace jni_zero::tests -// C++ classes that mirror the Java classes: -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_java_util_JLinkedList_DEFINED +namespace java::util { +class _JLinkedList : public _jobject {}; +using JLinkedList = _JLinkedList*; +} // namespace java::util +#define _JNI_ZERO_java_util_JLinkedList_DEFINED +#endif + +#ifndef _JNI_ZERO_java_util_JMap_DEFINED +namespace java::util { +class _JMap : public _jobject {}; +using JMap = _JMap*; +} // namespace java::util +#define _JNI_ZERO_java_util_JMap_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerEnum_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerEnum : public _jobject {}; +using JInnerEnum = _JInnerEnum*; +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerEnum_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerInterface_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerInterface : public _jobject {}; +using JInnerInterface = _JInnerInterface*; +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerInterface_DEFINED +#endif + +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +namespace this::is::a::package::prefix::org::jni_zero::extrapackage { +class _JImportsTinySample : public _jobject {}; +using JImportsTinySample = _JImportsTinySample*; +} // namespace this::is::a::package::prefix::org::jni_zero::extrapackage +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +#endif + + +// Actual implementations of C++ classes JMyClass: +// Declare the namespace so that it can be used by "using namespace" +namespace jni_zero::tests {} + +namespace jni_zero::internal { using namespace jni_zero::tests; -inline jni_zero::ScopedJavaLocalRef<JSampleForTests> _JSampleForTests::Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar) { - return Java_SampleForTests_Constructor(env, foo, bar); -} +template<> +class _CalledByNatives<JSampleForTests> { + public: + static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( + JNIEnv* env, + JniIntWrapper foo, + JniIntWrapper bar) { + return Java_SampleForTests_Constructor(env, foo, bar); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_addStructA(env, this_obj, a, b); -} + jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerStructA> addStructA( + JNIEnv* env, + const jni_zero::JavaRef<::this::is::a::package::prefix::org::jni_zero::JInnerStructA>& a, + const jni_zero::JavaRef<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample>& b) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + auto ret = Java_SampleForTests_addStructA(env, *this_obj, a, b); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerStructA>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jclass> _JSampleForTests::getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getClass(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jclass> getClass( + JNIEnv* env, + const jni_zero::JavaRef<jclass>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getClass(env, *this_obj, arg0); + } -inline std::string _JSampleForTests::getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getFirstString(env, this_obj, std::move(array), std::move(finalArg)); -} + std::string getFirstString( + JNIEnv* env, + std::vector<const char*> const& array, + const char* const& finalArg) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getFirstString( + env, + *this_obj, + std::move(array), + std::move(finalArg)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum(JNIEnv* env) { - return Java_SampleForTests_getInnerEnum(env); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerEnum> getInnerEnum( + JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerEnum(env); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerEnum>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerEnum( - JNIEnv* env, - JniIntWrapper a) { - return Java_SampleForTests_getInnerEnum(env, a); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample> getInnerEnum( + JNIEnv* env, + JniIntWrapper a) { + auto ret = Java_SampleForTests_getInnerEnum(env, a); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::extrapackage::JImportsTinySample>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jobject> _JSampleForTests::getInnerInterface(JNIEnv* env) { - return Java_SampleForTests_getInnerInterface(env); -} + static jni_zero::ScopedJavaLocalRef<::this::is::a::package::prefix::org::jni_zero::JInnerInterface> getInnerInterface( + JNIEnv* env) { + auto ret = Java_SampleForTests_getInnerInterface(env); + return jni_zero::Cast<::this::is::a::package::prefix::org::jni_zero::JInnerInterface>(env, std::move(ret)); + } -inline jni_zero::ScopedJavaLocalRef<jthrowable> _JSampleForTests::getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_getThrowable(env, this_obj, arg0); -} + jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( + JNIEnv* env, + const jni_zero::JavaRef<jthrowable>& arg0) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_getThrowable(env, *this_obj, arg0); + } -inline void _JSampleForTests::iterateAndDoSomething(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_iterateAndDoSomething(env, this_obj); -} + void iterateAndDoSomething(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_iterateAndDoSomething(env, *this_obj); + } -inline jint _JSampleForTests::javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_javaMethod(env, this_obj, _jcaller, ret); -} + jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_javaMethod(env, *this_obj, _jcaller, ret); + } -inline std::vector<int32_t> _JSampleForTests::jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_jniTypesAndAnnotations(env, this_obj, foo, std::move(bar), baz, bat); -} + std::vector<int32_t> jniTypesAndAnnotations( + JNIEnv* env, + MyEnum foo, + std::vector<int32_t> const& bar, + JniIntWrapper baz, + long bat) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_jniTypesAndAnnotations( + env, + *this_obj, + foo, + std::move(bar), + baz, + bat); + } -inline std::vector<jni_zero::ScopedJavaLocalRef<jobject>> _JSampleForTests::listTest1( - JNIEnv* env, - std::vector<std::string> const& items) { - return Java_SampleForTests_listTest1(env, std::move(items)); -} + static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( + JNIEnv* env, + std::vector<std::string> const& items) { + return Java_SampleForTests_listTest1(env, std::move(items)); + } -inline std::map<std::string, std::string> _JSampleForTests::mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0) { - return Java_SampleForTests_mapTest1(env, std::move(arg0)); -} + static std::map<std::string, std::string> mapTest1( + JNIEnv* env, + std::map<std::string, std::string> const& arg0) { + return Java_SampleForTests_mapTest1(env, std::move(arg0)); + } -inline void _JSampleForTests::methodThatThrowsException(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodThatThrowsException(env, this_obj); -} + void methodThatThrowsException(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodThatThrowsException(env, *this_obj); + } -inline jboolean _JSampleForTests::methodWithAnnotationParamAssignment(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithAnnotationParamAssignment(env, this_obj); -} + jboolean methodWithAnnotationParamAssignment(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithAnnotationParamAssignment(env, *this_obj); + } -inline void _JSampleForTests::methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_methodWithGenericParams(env, this_obj, _env, bar); -} + void methodWithGenericParams( + JNIEnv* env, + const jni_zero::JavaRef<::java::util::JMap>& _env, + const jni_zero::JavaRef<::java::util::JLinkedList>& bar) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_methodWithGenericParams(env, *this_obj, _env, bar); + } -inline void _JSampleForTests::packagePrivateJavaMethod(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JSampleForTests>(this); - return Java_SampleForTests_packagePrivateJavaMethod(env, this_obj); -} + void packagePrivateJavaMethod(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JSampleForTests>*>(this); + return Java_SampleForTests_packagePrivateJavaMethod(env, *this_obj); + } -inline std::vector<bool> _JSampleForTests::primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d) { - return Java_SampleForTests_primitiveArrays( - env, - std::move(b), - std::move(c), - std::move(s), - std::move(i), - std::move(l), - std::move(f), - std::move(d)); -} + static std::vector<bool> primitiveArrays( + JNIEnv* env, + std::vector<uint8_t> const& b, + std::vector<uint16_t> const& c, + std::vector<int16_t> const& s, + std::vector<int32_t> const& i, + std::vector<int64_t> const& l, + std::vector<float> const& f, + std::vector<double> const& d) { + return Java_SampleForTests_primitiveArrays( + env, + std::move(b), + std::move(c), + std::move(s), + std::move(i), + std::move(l), + std::move(f), + std::move(d)); + } -inline jboolean _JSampleForTests::staticJavaMethod(JNIEnv* env) { - return Java_SampleForTests_staticJavaMethod(env); -} + static jboolean staticJavaMethod(JNIEnv* env) { + return Java_SampleForTests_staticJavaMethod(env); + } + +}; + +template<> +class _CalledByNatives<JInnerStructA> { + public: + static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( + JNIEnv* env, + jlong l, + JniIntWrapper i, + const jni_zero::JavaRef<jstring>& s) { + return Java_InnerStructA_create(env, l, i, s); + } + +}; + +template<> +class _CalledByNatives<JInnerStructB> { + public: + jlong getKey(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getKey(env, *this_obj); + } + + jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env) const { + auto this_obj = reinterpret_cast<const JavaRef<JInnerStructB>*>(this); + return Java_InnerStructB_getValue(env, *this_obj); + } + +}; -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jni_zero::ScopedJavaLocalRef<JInnerStructA> _JInnerStructA::create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s) { - return Java_InnerStructA_create(env, l, i, s); -} - - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -inline jlong _JInnerStructB::getKey(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getKey(env, this_obj); -} - -inline jni_zero::ScopedJavaLocalRef<jstring> _JInnerStructB::getValue(JNIEnv* env) { - auto this_obj = jni_zero::internal::AsJavaRef<JInnerStructB>(this); - return Java_InnerStructB_getValue(env, this_obj); -} - - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 - +} // namespace jni_zero::internal #endif // this_is_a_package_prefix_org_jni_1zero_SampleForTests_JNI
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_shared_jni.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_shared_jni.h.golden index eb2eae3..20bb91e9 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithFilter-SampleForTests_shared_jni.h.golden
@@ -12,147 +12,48 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -// Declare the namespace so that it can be used by "using namespace" -namespace jni_zero::tests {} - -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JSampleForTests; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JSampleForTests_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JSampleForTests : public _jobject {}; using JSampleForTests = _JSampleForTests*; - -class _JSampleForTests : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JSampleForTests> Constructor( - JNIEnv* env, - JniIntWrapper foo, - JniIntWrapper bar); - - jni_zero::ScopedJavaLocalRef<jobject> addStructA( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& a, - const jni_zero::JavaRef<jobject>& b); - - jni_zero::ScopedJavaLocalRef<jclass> getClass( - JNIEnv* env, - const jni_zero::JavaRef<jclass>& arg0); - - std::string getFirstString( - JNIEnv* env, - std::vector<const char*> const& array, - const char* const& finalArg); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerEnum(JNIEnv* env, JniIntWrapper a); - - static jni_zero::ScopedJavaLocalRef<jobject> getInnerInterface(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jthrowable> getThrowable( - JNIEnv* env, - const jni_zero::JavaRef<jthrowable>& arg0); - - void iterateAndDoSomething(JNIEnv* env); - - jint javaMethod(JNIEnv* env, JniIntWrapper _jcaller, JniIntWrapper ret); - - std::vector<int32_t> jniTypesAndAnnotations( - JNIEnv* env, - MyEnum foo, - std::vector<int32_t> const& bar, - JniIntWrapper baz, - long bat); - - static std::vector<jni_zero::ScopedJavaLocalRef<jobject>> listTest1( - JNIEnv* env, - std::vector<std::string> const& items); - - static std::map<std::string, std::string> mapTest1( - JNIEnv* env, - std::map<std::string, std::string> const& arg0); - - void methodThatThrowsException(JNIEnv* env); - - jboolean methodWithAnnotationParamAssignment(JNIEnv* env); - - void methodWithGenericParams( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& _env, - const jni_zero::JavaRef<jobject>& bar); - - void packagePrivateJavaMethod(JNIEnv* env); - - static std::vector<bool> primitiveArrays( - JNIEnv* env, - std::vector<uint8_t> const& b, - std::vector<uint16_t> const& c, - std::vector<int16_t> const& s, - std::vector<int32_t> const& i, - std::vector<int64_t> const& l, - std::vector<float> const& f, - std::vector<double> const& d); - - static jboolean staticJavaMethod(JNIEnv* env); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JSampleForTests_DEFINED +#endif namespace jni_zero::tests { -using JSampleForTests = this1::is1::a1::package1::prefix1::org1::jni_zero1::JSampleForTests; -using JSampleForTestsClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JSampleForTests; +using JSampleForTests = ::this::is::a::package::prefix::org::jni_zero::JSampleForTests; +using JSampleForTestsClass = ::jni_zero::internal::_CalledByNatives<JSampleForTests>; } // namespace jni_zero::tests -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructA; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructA_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerStructA : public _jobject {}; using JInnerStructA = _JInnerStructA*; - -class _JInnerStructA : public _jobject { - public: - static jni_zero::ScopedJavaLocalRef<JInnerStructA> create( - JNIEnv* env, - jlong l, - JniIntWrapper i, - const jni_zero::JavaRef<jstring>& s); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructA_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructA = this1::is1::a1::package1::prefix1::org1::jni_zero1::JInnerStructA; -using JInnerStructAClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JInnerStructA; +using JInnerStructA = ::this::is::a::package::prefix::org::jni_zero::JInnerStructA; +using JInnerStructAClass = ::jni_zero::internal::_CalledByNatives<JInnerStructA>; } // namespace jni_zero::tests -namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 { - -using namespace jni_zero::tests; - -class _JInnerStructB; +#ifndef _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructB_DEFINED +namespace this::is::a::package::prefix::org::jni_zero { +class _JInnerStructB : public _jobject {}; using JInnerStructB = _JInnerStructB*; - -class _JInnerStructB : public _jobject { - public: - jlong getKey(JNIEnv* env); - - jni_zero::ScopedJavaLocalRef<jstring> getValue(JNIEnv* env); - -}; - -} // namespace this1::is1::a1::package1::prefix1::org1::jni_zero1 +} // namespace this::is::a::package::prefix::org::jni_zero +#define _JNI_ZERO_this_is_a_package_prefix_org_jni_zero_JInnerStructB_DEFINED +#endif namespace jni_zero::tests { -using JInnerStructB = this1::is1::a1::package1::prefix1::org1::jni_zero1::JInnerStructB; -using JInnerStructBClass = this1::is1::a1::package1::prefix1::org1::jni_zero1::_JInnerStructB; +using JInnerStructB = ::this::is::a::package::prefix::org::jni_zero::JInnerStructB; +using JInnerStructBClass = ::jni_zero::internal::_CalledByNatives<JInnerStructB>; } // namespace jni_zero::tests
diff --git a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden index f5ed0b1..1446dcc4 100644 --- a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden
@@ -878,5 +878,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JSampleForAnnotationProcessor> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_SampleForAnnotationProcessor_JNI
diff --git a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_shared_jni.h.golden b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_shared_jni.h.golden index 33ad9e6f4..f58ba0b 100644 --- a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleForAnnotationProcessor; +#ifndef _JNI_ZERO_org_jni_zero_JSampleForAnnotationProcessor_DEFINED +namespace org::jni_zero { +class _JSampleForAnnotationProcessor : public _jobject {}; using JSampleForAnnotationProcessor = _JSampleForAnnotationProcessor*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleForAnnotationProcessor_DEFINED +#endif -class _JSampleForAnnotationProcessor : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JSampleForAnnotationProcessor = org1::jni_zero1::JSampleForAnnotationProcessor; -using JSampleForAnnotationProcessorClass = org1::jni_zero1::_JSampleForAnnotationProcessor; +using JSampleForAnnotationProcessor = ::org::jni_zero::JSampleForAnnotationProcessor; +using JSampleForAnnotationProcessorClass = ::jni_zero::internal::_CalledByNatives<JSampleForAnnotationProcessor>;
diff --git a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_jni.h.golden b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_jni.h.golden index b3e6bede..c1a22f5 100644 --- a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_jni.h.golden
@@ -41,5 +41,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JImportsTinySample> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_extrapackage_ImportsTinySample_JNI
diff --git a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_shared_jni.h.golden b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_shared_jni.h.golden index de0d556..f94f94a 100644 --- a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-ImportsTinySample_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1::extrapackage1 { - -class _JImportsTinySample; +#ifndef _JNI_ZERO_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +namespace org::jni_zero::extrapackage { +class _JImportsTinySample : public _jobject {}; using JImportsTinySample = _JImportsTinySample*; +} // namespace org::jni_zero::extrapackage +#define _JNI_ZERO_org_jni_zero_extrapackage_JImportsTinySample_DEFINED +#endif -class _JImportsTinySample : public _jobject { - public: -}; - -} // namespace org1::jni_zero1::extrapackage1 - -using JImportsTinySample = org1::jni_zero1::extrapackage1::JImportsTinySample; -using JImportsTinySampleClass = org1::jni_zero1::extrapackage1::_JImportsTinySample; +using JImportsTinySample = ::org::jni_zero::extrapackage::JImportsTinySample; +using JImportsTinySampleClass = ::jni_zero::internal::_CalledByNatives<JImportsTinySample>;
diff --git a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_jni.h.golden b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_jni.h.golden index 7c324c0..b3ef09c 100644 --- a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_jni.h.golden
@@ -72,5 +72,18 @@ } +// Lazy definition of C++ classes JMyClass: + +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { + +template<> +class _CalledByNatives<JTinySample> { + public: +}; + + +} // namespace jni_zero::internal + #endif // org_jni_1zero_TinySample_JNI
diff --git a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_shared_jni.h.golden b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_shared_jni.h.golden index 6687fd5..e663836 100644 --- a/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPlaceholdersOverlapping-TinySample_shared_jni.h.golden
@@ -12,19 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JTinySample; +#ifndef _JNI_ZERO_org_jni_zero_JTinySample_DEFINED +namespace org::jni_zero { +class _JTinySample : public _jobject {}; using JTinySample = _JTinySample*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JTinySample_DEFINED +#endif -class _JTinySample : public _jobject { - public: -}; - -} // namespace org1::jni_zero1 - -using JTinySample = org1::jni_zero1::JTinySample; -using JTinySampleClass = org1::jni_zero1::_JTinySample; +using JTinySample = ::org::jni_zero::JTinySample; +using JTinySampleClass = ::jni_zero::internal::_CalledByNatives<JTinySample>;
diff --git a/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_jni.h.golden b/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_jni.h.golden index 6b648de..6cfccdf 100644 --- a/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_jni.h.golden +++ b/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_jni.h.golden
@@ -155,18 +155,30 @@ } -// C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -inline void _JSampleUniqueAnnotations::useBar1( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& inner) { - return Java_SampleUniqueAnnotations_useBar1(env, inner); -} +// Lazy definition of C++ classes JMyClass: +#ifndef _JNI_ZERO_org_jni_zero_JInner_DEFINED +namespace org::jni_zero { +class _JInner : public _jobject {}; +using JInner = _JInner*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JInner_DEFINED +#endif -} // namespace org1::jni_zero1 +// Actual implementations of C++ classes JMyClass: +namespace jni_zero::internal { +template<> +class _CalledByNatives<JSampleUniqueAnnotations> { + public: + static void useBar1(JNIEnv* env, const jni_zero::JavaRef<::org::jni_zero::JInner>& inner) { + return Java_SampleUniqueAnnotations_useBar1(env, inner); + } + +}; + + +} // namespace jni_zero::internal #endif // org_jni_1zero_SampleUniqueAnnotations_JNI
diff --git a/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_shared_jni.h.golden b/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_shared_jni.h.golden index a1fc9048..d31ca60 100644 --- a/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_shared_jni.h.golden +++ b/third_party/jni_zero/test/golden/testUniqueAnnotations-SampleUniqueAnnotations_shared_jni.h.golden
@@ -12,21 +12,16 @@ #include "extra_include.h" // C++ classes that mirror the Java classes: -namespace org1::jni_zero1 { - -class _JSampleUniqueAnnotations; +#ifndef _JNI_ZERO_org_jni_zero_JSampleUniqueAnnotations_DEFINED +namespace org::jni_zero { +class _JSampleUniqueAnnotations : public _jobject {}; using JSampleUniqueAnnotations = _JSampleUniqueAnnotations*; +} // namespace org::jni_zero +#define _JNI_ZERO_org_jni_zero_JSampleUniqueAnnotations_DEFINED +#endif -class _JSampleUniqueAnnotations : public _jobject { - public: - static void useBar1(JNIEnv* env, const jni_zero::JavaRef<jobject>& inner); - -}; - -} // namespace org1::jni_zero1 - -using JSampleUniqueAnnotations = org1::jni_zero1::JSampleUniqueAnnotations; -using JSampleUniqueAnnotationsClass = org1::jni_zero1::_JSampleUniqueAnnotations; +using JSampleUniqueAnnotations = ::org::jni_zero::JSampleUniqueAnnotations; +using JSampleUniqueAnnotationsClass = ::jni_zero::internal::_CalledByNatives<JSampleUniqueAnnotations>;
diff --git a/third_party/skia b/third_party/skia index 23eba72..a94df1c 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit 23eba721990ca397ed94756cc4d3a902319722ee +Subproject commit a94df1cdabb03d3ead2486413bc2999b500aa4f8
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src index 46b1e00..d83d9363 160000 --- a/third_party/spirv-tools/src +++ b/third_party/spirv-tools/src
@@ -1 +1 @@ -Subproject commit 46b1e005696db761fd19067cda0d6d93e4d0b63c +Subproject commit d83d9363b0b4466b78331f73ab64550416389d95
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps index 534fffb..b4ca123 160000 --- a/third_party/vulkan-deps +++ b/third_party/vulkan-deps
@@ -1 +1 @@ -Subproject commit 534fffbfc1cd503ab60af3fc2c5e657b26004420 +Subproject commit b4ca123cdf076f8798b3ca0b78bfb5106ba4d410
diff --git a/third_party/vulkan-utility-libraries/src b/third_party/vulkan-utility-libraries/src index 738ec97..6878b60f 160000 --- a/third_party/vulkan-utility-libraries/src +++ b/third_party/vulkan-utility-libraries/src
@@ -1 +1 @@ -Subproject commit 738ec97a3f659dd6469bff3c4078ef981b0a343f +Subproject commit 6878b60ffdabe0ce79f02163c4430c27d21d1e41
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec index 643f7b6e..046c0a3 100644 --- a/tools/gritsettings/resource_ids.spec +++ b/tools/gritsettings/resource_ids.spec
@@ -606,7 +606,7 @@ "includes": [4660], }, "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/webui_toolbar/resources.grd": { - "META": {"sizes": {"includes": [20]}}, + "META": {"sizes": {"includes": [30]}}, "includes": [4670], }, "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/sandbox_internals/resources.grd": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 4794b17a..9bee7102 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -5506,6 +5506,7 @@ <int value="-2147009295" label="ERROR_INSTALL_PACKAGE_NOT_FOUND"/> <int value="-2146959355" label="CO_E_SERVER_EXEC_FAILURE"/> <int value="-2146959352" label="CO_E_SERVER_STOPPING"/> + <int value="-2146893802" label="NTE_BAD_KEYSET"/> <int value="-2146893051" label="SEC_E_SECPKG_NOT_FOUND"/> <int value="-2146893002" label="SEC_E_WRONG_CREDENTIAL_HANDLE"/> <int value="-2146893001" label="SEC_E_CRYPTO_SYSTEM_INVALID"/> @@ -11319,6 +11320,7 @@ <int value="-1064729659" label="LauncherFuzzyMatchAcrossProviders:enabled"/> <int value="-1064302126" label="OmniboxAlternateMatchDescriptionSeparator:enabled"/> + <int value="-1063670802" label="AndroidHistoryClustering:disabled"/> <int value="-1062534902" label="SyncingCompromisedCredentials:disabled"/> <int value="-1062119671" label="enable-password-force-saving"/> <int value="-1061944215" label="NewTabPageTriggerForPrefetch:disabled"/> @@ -19906,6 +19908,7 @@ <int value="1837847580" label="MutingCompromisedCredentials:disabled"/> <int value="1838110766" label="PrivateNetworkAccessSendPreflights:disabled"/> <int value="1838369942" label="EnableGetDebugdLogsInParallel:disabled"/> + <int value="1838872079" label="AndroidHistoryClustering:enabled"/> <int value="1838990777" label="V8Future:enabled"/> <int value="1839005510" label="MutingCompromisedCredentials:enabled"/> <int value="1839740266" label="LocationHardReload:disabled"/>
diff --git a/tools/metrics/histograms/metadata/actor/enums.xml b/tools/metrics/histograms/metadata/actor/enums.xml index 32c3972a..8773011 100644 --- a/tools/metrics/histograms/metadata/actor/enums.xml +++ b/tools/metrics/histograms/metadata/actor/enums.xml
@@ -124,11 +124,48 @@ <int value="1205" label="ScriptToolMissingRequiredSubmitButton"/> <int value="1206" label="ScriptToolCancelled"/> <int value="1300" label="ActorUiError"/> - <int value="1400" label="LoadAndExtractContentExtractionFailed"/> </enum> <!-- LINT.ThenChange(//chrome/common/actor.mojom:ActionResultCode) --> +<!-- LINT.IfChange(ActorLoginAccountTypes) --> + +<enum name="ActorLoginAccountTypes"> + <int value="0" label="None"/> + <int value="1" label="Password"/> + <int value="2" label="Federated"/> + <int value="3" label="Password and Federated"/> +</enum> + +<!-- LINT.ThenChange(//components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h:ActorLoginAccountTypes) --> + +<!-- LINT.IfChange(ActorLoginFederatedLoginResult) --> + +<enum name="ActorLoginFederatedLoginResult"> + <int value="0" label="Success"/> + <int value="2" label="AccountNotLoggedIn"/> + <int value="3" label="AccountIsSignUp"/> + <int value="4" label="AccountNotAvailable"/> + <int value="5" label="IdpReturnedError"/> + <int value="6" label="IdpNetworkError"/> + <int value="7" label="TokenRequestAborted"/> + <int value="8" label="FrameNotActive"/> + <int value="9" label="ExpectedAccountNotPresent"/> + <int value="10" label="Timeout"/> +</enum> + +<!-- LINT.ThenChange(//components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h:ActorLoginFederatedLoginResult) --> + +<!-- LINT.IfChange(ActorLoginSelectedAccountType) --> + +<enum name="ActorLoginSelectedAccountType"> + <int value="0" label="None"/> + <int value="1" label="Password"/> + <int value="2" label="Federated"/> +</enum> + +<!-- LINT.ThenChange(//components/password_manager/core/browser/actor_login/internal/actor_login_metrics.h:ActorLoginSelectedAccountType) --> + <!-- LINT.IfChange(ActorObservationOutcome) --> <enum name="ActorObservationOutcome">
diff --git a/tools/metrics/histograms/metadata/actor/histograms.xml b/tools/metrics/histograms/metadata/actor/histograms.xml index f3f063f9..e281bea 100644 --- a/tools/metrics/histograms/metadata/actor/histograms.xml +++ b/tools/metrics/histograms/metadata/actor/histograms.xml
@@ -191,6 +191,121 @@ </summary> </histogram> +<histogram name="Actor.Login.AccountAutoSelected" enum="Boolean" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Recorded when the account used to login is automatically selected due to a + previous user choice to always allow logging in with that account. Recorded + once per actor login request, when the login attempt starts and the account + already had a permanent permission granted. + </summary> +</histogram> + +<histogram name="Actor.Login.AccountTypesShown" enum="ActorLoginAccountTypes" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + An enum of the result from a query to get credentials. Recorded once per + actor login request, after fetching the accounts from all sources is + completed. + </summary> +</histogram> + +<histogram name="Actor.Login.Federated.ContinuationShown" enum="Boolean" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Recorded when the FedCM continuation API is invoked during an actor + autologin flow. Recorded when the actor is notified that the user must take + over due to the continuation API. This is only ever recorded when the user + selects the federated account to login. + </summary> +</histogram> + +<histogram name="Actor.Login.Federated.HangingFedCmRequestExists" + enum="Boolean" expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Recorded when there is a hanging FedCM request during an actor login + request. Recorded at most once per actor login request. + </summary> +</histogram> + +<histogram name="Actor.Login.Federated.LoginResult" + enum="ActorLoginFederatedLoginResult" expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + An enum with the final result from a federated actor login request. Recorded + when the login flow completes (successfully or otherwise). + </summary> +</histogram> + +<histogram name="Actor.Login.GetCredentialsCompletedToAccountChosen" units="ms" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Returns the time from when a "get credentials" request completes + until an account is chosen. Recorded at most once per actor login request. + </summary> +</histogram> + +<histogram name="Actor.Login.GetCredentialsLatency" units="ms" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Returns the time from actor login request to when the "get + credentials" request is completed. Recorded once per actor login + request. + </summary> +</histogram> + +<histogram name="Actor.Login.NumAccountsShown" units="count" + expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + The number of accounts shown to the user during an actor login request. + Possible values: 0, 1, 2, 3+. Recorded once per actor login request, when + accounts are shown to the user. + </summary> +</histogram> + +<histogram name="Actor.Login.SelectedAccountType" + enum="ActorLoginSelectedAccountType" expires_after="2027-02-25"> + <owner>npm@google.com</owner> + <owner>ioanap@google.com</owner> + <owner>otara@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + An enum of the type of account chosen by the user to login during an actor + login request. Recorded once per actor login request. + </summary> +</histogram> + <histogram name="Actor.MayOriginGatePageTransition" enum="Boolean" expires_after="2026-07-26"> <owner>dylancutler@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml index 9fd9ee6..f6fea009 100644 --- a/tools/metrics/histograms/metadata/autofill/histograms.xml +++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -1787,7 +1787,7 @@ <histogram name="Autofill.AiAmountExtraction.Latency.{AiAmountExtractionLatencyResult}" - enum="AiAmountExtractionResult" expires_after="2026-06-01"> + units="ms" expires_after="2026-06-01"> <owner>jamessun@google.com</owner> <owner>payments-autofill-team@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml index f99b3d0..7e617772 100644 --- a/tools/metrics/histograms/metadata/extensions/enums.xml +++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -36,6 +36,15 @@ label="Assessment Assistant (gndmhdcefbhlchkhipcnnbkcmicncehk)"/> </enum> +<enum name="BackingStoreRestoreStatus"> + <int value="0" label="RESTORE_NONE"/> + <int value="1" label="DB_RESTORE_DELETE_SUCCESS"/> + <int value="2" label="DB_RESTORE_DELETE_FAILURE"/> + <int value="3" label="DB_RESTORE_REPAIR_SUCCESS"/> + <int value="4" label="VALUE_RESTORE_DELETE_SUCCESS"/> + <int value="5" label="VALUE_RESTORE_DELETE_FAILURE"/> +</enum> + <enum name="BadSyncDataReason"> <int value="0" label="Bad extension ID"/> <int value="1" label="Bad version"/> @@ -3276,6 +3285,13 @@ <int value="4" label="Non-webstore extension without hashes skipped"/> </enum> +<enum name="ExtensionsDatabaseOperation"> + <int value="0" label="get"/> + <int value="1" label="set"/> + <int value="2" label="remove"/> + <int value="3" label="clear"/> +</enum> + <enum name="ExtensionType"> <int value="0" label="UNKNOWN"/> <int value="1" label="EXTENSION"/> @@ -3627,6 +3643,14 @@ <int value="18" label="kErrorWriteFlatbuffer"/> </enum> +<enum name="ValueStoreStatusCode"> + <int value="0" label="OK"/> + <int value="1" label="CORRUPTION"/> + <int value="2" label="READ_ONLY"/> + <int value="3" label="QUOTA_EXCEEDED"/> + <int value="4" label="OTHER_ERROR"/> +</enum> + <enum name="VerifiedFileType"> <summary> The type of file that was verified in content verification. Keep in sync
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml index f41b5aa..b735d299 100644 --- a/tools/metrics/histograms/metadata/extensions/histograms.xml +++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -1011,6 +1011,47 @@ </summary> </histogram> +<histogram name="Extensions.Database.Local.ErrorByOperation" + enum="ExtensionsDatabaseOperation" expires_after="2026-08-31"> + <owner>jlulejian@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + Records the operation being attempted when a chrome.storage.local (LevelDB) + database error occurs. It is emitted during the execution of a storage API + call from an extension. + </summary> +</histogram> + +<histogram name="Extensions.Database.Local.RestoreStatus" + enum="BackingStoreRestoreStatus" expires_after="2026-08-31"> + <owner>jlulejian@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The result of a repair attempt on a corrupted chrome.storage.local LevelDB + database. This is emitted during an open or read operation if the underlying + database is found to be corrupted. + </summary> +</histogram> + +<histogram + name="Extensions.Database.Local.StatusCodeByOperation.{StorageOperation}" + enum="ValueStoreStatusCode" expires_after="2026-08-31"> + <owner>jlulejian@chromium.org</owner> + <owner>extensions-core@chromium.org</owner> + <summary> + The high-level ValueStore status of chrome.storage.local.{StorageOperation} + operations on the extensions database. It is emitted during the execution of + a storage API call by an extension, regardless of whether the operation + succeeded or failed. + </summary> + <token key="StorageOperation"> + <variant name="clear"/> + <variant name="get"/> + <variant name="remove"/> + <variant name="set"/> + </token> +</histogram> + <histogram name="Extensions.Database.Open{ExtensionsDatabaseOpen}" enum="LevelDBStatus" expires_after="never"> <!-- expires-never: core storage metric; consumed in separate dashboard (go/chrome-storage-dashboard) -->
diff --git a/tools/metrics/histograms/metadata/glic/enums.xml b/tools/metrics/histograms/metadata/glic/enums.xml index ff84bb8..40f2574e 100644 --- a/tools/metrics/histograms/metadata/glic/enums.xml +++ b/tools/metrics/histograms/metadata/glic/enums.xml
@@ -153,6 +153,19 @@ <!-- LINT.ThenChange(//chrome/browser/glic/fre/glic_fre.mojom:FreWebUiState) --> +<!-- LINT.IfChange(GeminiNavigationCaptureResult) --> + +<enum name="GeminiNavigationCaptureResult"> + <int value="0" label="Success"/> + <int value="1" label="Failure: Invalid URL"/> + <int value="2" label="Failure: Non-HTTPS scheme"/> + <int value="3" label="Failure: CID too long"/> + <int value="4" label="Failure: Target URL too long"/> + <int value="5" label="Failure: No target URL"/> +</enum> + +<!-- LINT.ThenChange(//chrome/browser/glic/glic_enums.h:GeminiNavigationCaptureResult) --> + <!-- LINT.IfChange(GlicAttachChangeReason) --> <enum name="GlicAttachChangeReason"> @@ -193,6 +206,7 @@ <int value="2" label="Switched Conversation"/> <int value="3" label="Recursive Daisy Chain"/> <int value="4" label="Side Panel Closed"/> + <int value="5" label="Tab Switched"/> </enum> <!-- LINT.ThenChange(//chrome/browser/glic/service/metrics/glic_instance_helper_metrics.h:DaisyChainFirstAction) -->
diff --git a/tools/metrics/histograms/metadata/glic/histograms.xml b/tools/metrics/histograms/metadata/glic/histograms.xml index 0e10986..b8951f8 100644 --- a/tools/metrics/histograms/metadata/glic/histograms.xml +++ b/tools/metrics/histograms/metadata/glic/histograms.xml
@@ -727,6 +727,37 @@ </summary> </histogram> +<histogram name="Glic.Instance.AutoOpenedPanel.FirstAction.{Source}" + enum="GlicDaisyChainFirstAction" expires_after="2026-12-31"> + <owner>birnie@google.com</owner> + <owner>basiaz@google.com</owner> + <summary> + Records the first action that occurs on a tab that had side panel + auto-opened through daisy chaining or features like web handoff and + auto-open pdf. Recorded when a significant action occurs (tab closed, side + panel closed, input submitted, conversation switched, daisy chain another + tab) or records "No Action" when tab helper is destroyed before + any action occurs. Only recorded for the source that triggered the opening. + + Tab switching and side panel closure are treated as ambiguous actions that + trigger a 5-second timer before logging. This is to catch cases where a user + switches tabs back and forth and then performs a more significant action + like input submitted or conversation switched. + + This metric should eventually replace + Glic.Instance.FirstActionInDaisyChainPanel. + </summary> + <token key="Source"> + <variant name="ActorAddTab" summary="actor adding a new tab"/> + <variant name="AutoOpenPdf" summary="auto-opened for a PDF"/> + <variant name="GlicContents" summary="clicking a link in the Glic panel"/> + <variant name="NewTab" summary="clicking a link that opens a new tab"/> + <variant name="TabContents" summary="clicking a link in the main tab"/> + <variant name="WebHandoff" + summary="opened via navigation capture / web handoff"/> + </token> +</histogram> + <histogram name="Glic.Instance.EventCounts" enum="GlicInstanceEvent" expires_after="2026-08-02"> <owner>birnie@google.com</owner> @@ -739,26 +770,6 @@ </summary> </histogram> -<histogram name="Glic.Instance.FirstActionInDaisyChainPanel.{Source}" - enum="GlicDaisyChainFirstAction" expires_after="2026-12-31"> - <owner>birnie@google.com</owner> - <owner>perrier@google.com</owner> - <summary> - Records the first action that occurs on a tab that was created by - daisy-chaining (opening a link from the side panel), broken down by the - source of the daisy chain. Recorded when a significant action occurs (tab - closed, side panel closed, input submitted, conversation switched, daisy - chain another tab) or records "No Action" when tab helper is - destroyed before any action occurs. - </summary> - <token key="Source"> - <variant name="ActorAddTab"/> - <variant name="GlicContents"/> - <variant name="NewTab"/> - <variant name="TabContents"/> - </token> -</histogram> - <histogram name="Glic.Instance.Floaty.InitialMode" enum="GlicWebClientMode" expires_after="2026-08-02"> <owner>birnie@google.com</owner> @@ -1432,6 +1443,24 @@ </token> </histogram> +<histogram name="Glic.NavigationCapture.{FeatureEnabledStatus}" + enum="GeminiNavigationCaptureResult" expires_after="2026-07-26"> + <owner>andreaxg@google.com</owner> + <owner>basiaz@google.com</owner> + <summary> + Records the outcome of navigation capture attempts when + {FeatureEnabledStatus}. Logged when the NavigationThrottle evaluates a + redirection target. Logs the first failure reason in the order they're + checked. + </summary> + <token key="FeatureEnabledStatus"> + <variant name="GlicWebContinuityFeatureDisabled" + summary="Web continuity is disabled or unavailable"/> + <variant name="GlicWebContinuityFeatureEnabled" + summary="Web continuity is enabled and available"/> + </token> +</histogram> + <histogram name="Glic.OsEntrypoint.Settings.Shortcut" enum="BooleanEnabled" expires_after="2026-07-26"> <owner>agale@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml index 4d518df7..234993c 100644 --- a/tools/metrics/histograms/metadata/memory/histograms.xml +++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -184,16 +184,11 @@ <variant name=".DawnSharedContext.Textures" summary="Only counting textures' memory used by the Graphite Dawn shared context."/> - <variant name=".DevTools.Sessions.ActiveCount" - summary="Reports the number of active DevTools sessions."/> <variant name=".Discardable" summary="Only counting memory used by Discardable manager."/> <variant name=".DurableMessages.AggregateMemoryUsage" summary="Reports the aggregate memory usage in megabytes by all DevToolsDurableMessageCollector instances."/> - <variant name=".DurableMessages.CollectorCount" - summary="Reports the number of DevToolsDurableMessageCollector - instances."/> <variant name=".ExtensionFunctions" summary="Only counting memory used by ExtensionFunctions."/> <variant name=".Extensions.ValueStore" @@ -584,6 +579,14 @@ <variant name=".AXPlatformWinLiveNodeCount" summary="Count of live accessibility platform nodes in the browser process."/> + <variant name=".DevTools.Sessions.ActiveCount" + summary="Reports the number of active DevTools sessions."/> + <variant name=".DurableMessages.AggregateMessageCount" + summary="Reports the aggregate message count by all + DevToolsDurableMessageCollector instances."/> + <variant name=".DurableMessages.CollectorCount" + summary="Reports the number of DevToolsDurableMessageCollector + instances."/> <variant name=".ServiceDiscardableManager" summary="Only counting memory used by the gpu discardable cache."/> <variant name=".ServiceDiscardableManager.AvgImageSize" @@ -754,9 +757,6 @@ </variants> <variants name="ProcessMemoryAllocatorTiny2"> - <variant name=".DurableMessages.AggregateMessageCount" - summary="Reports the aggregate message count by all - DevToolsDurableMessageCollector instances."/> <variant name=".Malloc.BRPQuarantinedCount" summary="Number of slots quarantined by BRP, recorded when USE_BACKUP_REF_PTR build flag is enabled."/>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml index 6e72e07..ffee70fe 100644 --- a/tools/metrics/histograms/metadata/net/histograms.xml +++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -5947,7 +5947,7 @@ </summary> </histogram> -<histogram name="Net.QuicSession.MTCLandmarkDelta.CurrentClient" +<histogram name="Net.QuicSession.MTCLandmarkDelta.CurrentClient{IsResumption}" units="MTC landmarks" expires_after="2026-08-23"> <owner>nharper@chromium.org</owner> <owner>mattm@chromium.org</owner> @@ -5959,9 +5959,16 @@ server's MTC). Only recorded for QUIC connections where the client supports verifying MTCs and the server supports sending MTCs. </summary> + <token key="IsResumption"> + <variant name="" + summary="aggregated across new connections and resumption"/> + <variant name=".NewConnection" summary="on a new connection"/> + <variant name=".Resumption" + summary="on a connection that Chrome is attempting to resume"/> + </token> </histogram> -<histogram name="Net.QuicSession.MTCLandmarkDelta.OldClient" +<histogram name="Net.QuicSession.MTCLandmarkDelta.OldClient{IsResumption}" units="MTC landmarks" expires_after="2026-08-23"> <owner>nharper@chromium.org</owner> <owner>mattm@chromium.org</owner> @@ -5973,6 +5980,13 @@ unable to verify the server's MTC). Only recorded for QUIC connections where the client supports verifying MTCs and the server supports sending MTCs. </summary> + <token key="IsResumption"> + <variant name="" + summary="aggregated across new connections and resumption"/> + <variant name=".NewConnection" summary="on a new connection"/> + <variant name=".Resumption" + summary="on a connection that Chrome is attempting to resume"/> + </token> </histogram> <histogram name="Net.QuicSession.MTCMetadataAge" units="Milliseconds" @@ -6886,7 +6900,7 @@ </summary> </histogram> -<histogram name="Net.QuicSession.TLSHandshakeBytes.MTC{IsResumption}" +<histogram name="Net.QuicSession.TLSHandshakeBytes.MTC2{IsResumption}" units="bytes" expires_after="2026-08-23"> <owner>nharper@chromium.org</owner> <owner>mattm@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml index 88c13a3..5dd00bdf 100644 --- a/tools/metrics/histograms/metadata/optimization/histograms.xml +++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -731,6 +731,16 @@ </summary> </histogram> +<histogram name="OptimizationGuide.GeneralizedSafetyChecker.ExecutionTime" + units="ms" expires_after="2026-09-03"> + <owner>zekunjiang@google.com</owner> + <owner>robertogden@chromium.org</owner> + <summary> + The duration of successful executions of the generalized safety model. + Recorded once for each text safety inference request. + </summary> +</histogram> + <histogram name="OptimizationGuide.HintCache.HintType.Loaded" enum="HintCacheStoreEntryType" expires_after="M145"> <owner>rajendrant@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml index 1d056e72..1e82205 100644 --- a/tools/metrics/ukm/ukm.xml +++ b/tools/metrics/ukm/ukm.xml
@@ -600,6 +600,72 @@ </metric> </event> +<event name="Actor.Login"> + <owner>npm@google.com</owner> + <owner>web-identity-eng@google.com</owner> + <summary> + Metrics recorded for an actor login request. Recorded once per request when + it completes or reaches a significant milestone. + </summary> + <metric name="AccountAutoSelected"> + <summary> + Recorded when the account used to login is automatically selected due to a + previous user choice to always allow logging in with that account. + </summary> + </metric> + <metric name="AccountTypesShown"> + <summary> + An enum of the result from a query to get credentials. + </summary> + </metric> + <metric name="FederatedContinuationShown"> + <summary> + Recorded when the FedCM continuation API is invoked during an actor + autologin flow. + </summary> + </metric> + <metric name="FederatedHangingFedCmRequestExists"> + <summary> + Recorded when there is a hanging FedCM request during an actor login + request. + </summary> + </metric> + <metric name="FederatedLoginResult"> + <summary> + An enum with the final result from a federated actor login request. + </summary> + </metric> + <metric name="FederatedLoginStatus"> + <summary> + Enum recorded when there is an identity provider requested by the + embedder. + </summary> + </metric> + <metric name="GetCredentialsCompletedToAccountChosen"> + <summary> + Returns the time (ms) from when a "get credentials" request + completes until an account is chosen. + </summary> + </metric> + <metric name="GetCredentialsLatency"> + <summary> + Returns the time (ms) from actor login request to when the "get + credentials" request is completed. + </summary> + </metric> + <metric name="NumAccountsShown"> + <summary> + The number of accounts shown to the user during an actor login request. + </summary> + </metric> + <metric name="SelectedAccountType"> + <summary> + An enum of the type of account chosen by the user to login during an actor + login request. + </summary> + </metric> +</event> + <event name="AdFrameLoad"> <owner>johnidel@chromium.org</owner> <owner>jkarlin@chromium.org</owner>
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn index 359208c..12c7a46 100644 --- a/ui/android/BUILD.gn +++ b/ui/android/BUILD.gn
@@ -92,7 +92,6 @@ deps = [ ":buildflags", ":java_enums_srcjar", - ":ui_android_jni_headers", "//base", "//cc/slim", "//components/viz/client", @@ -123,6 +122,8 @@ if (enable_new_texture_compressor) { deps += [ ":texture_compressor" ] } + + public_deps = [ ":ui_android_jni_headers" ] } java_cpp_features("java_features_srcjar") {
diff --git a/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java index f0e3b27..1a130c7 100644 --- a/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java +++ b/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java
@@ -5,6 +5,7 @@ package org.chromium.ui.base; import android.app.Activity; +import android.content.Context; import org.chromium.base.ActivityState; import org.chromium.base.ApplicationStatus; @@ -34,23 +35,24 @@ /** * Creates an Activity-specific WindowAndroid with associated intent functionality. * - * @param activity The activity associated with the WindowAndroid. + * @param context Context wrapping an activity associated with the WindowAndroid. * @param listenToActivityState Whether to listen to activity state changes. * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. - * @param insetObserver Observes window insets to track keyboard and layout changes. * @param trackOcclusion Whether to track occlusion of the window. */ - public static ActivityWindowAndroid create( - Activity activity, + public ActivityWindowAndroid( + Context context, boolean listenToActivityState, IntentRequestTracker intentRequestTracker, @Nullable InsetObserver insetObserver, boolean trackOcclusion) { - return new ActivityWindowAndroid( - activity, + this( + context, listenToActivityState, - new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), - new ActivityKeyboardVisibilityDelegate(new WeakReference<>(activity)), + new ActivityAndroidPermissionDelegate( + new WeakReference<>(ContextUtils.activityFromContext(context))), + new ActivityKeyboardVisibilityDelegate( + new WeakReference<>(ContextUtils.activityFromContext(context))), /* activityTopResumedSupported= */ false, intentRequestTracker, insetObserver, @@ -60,18 +62,43 @@ /** * Creates an Activity-specific WindowAndroid with associated intent functionality. * - * @param activity The activity associated with the WindowAndroid. + * @param context Context wrapping an activity associated with the WindowAndroid. * @param listenToActivityState Whether to listen to activity state changes. - * @param activityAndroidPermissionDelegate Delegates which handles android permissions. - * @param activityKeyboardVisibilityDelegate Delegate to handle keyboard visibility. - * @param activityTopResumedSupported If true, allows the activity to report top resumed state - * changes. + * @param keyboardVisibilityDelegate Delegate which handles keyboard visibility. * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. - * @param insetObserver Observes window insets to track keyboard and layout changes. * @param trackOcclusion Whether to track occlusion of the window. */ public ActivityWindowAndroid( - Activity activity, + Context context, + boolean listenToActivityState, + ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate, + boolean activityTopResumedSupported, + IntentRequestTracker intentRequestTracker, + InsetObserver insetObserver, + boolean trackOcclusion) { + this( + context, + listenToActivityState, + new ActivityAndroidPermissionDelegate( + new WeakReference<>(ContextUtils.activityFromContext(context))), + keyboardVisibilityDelegate, + activityTopResumedSupported, + intentRequestTracker, + insetObserver, + trackOcclusion); + } + + /** + * Creates an Activity-specific WindowAndroid with associated intent functionality. + * + * @param context Context wrapping an activity associated with the WindowAndroid. + * @param listenToActivityState Whether to listen to activity state changes. + * @param activityAndroidPermissionDelegate Delegates which handles android permissions. + * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. + * @param trackOcclusion Whether to track occlusion of the window. + */ + private ActivityWindowAndroid( + Context context, boolean listenToActivityState, ActivityAndroidPermissionDelegate activityAndroidPermissionDelegate, ActivityKeyboardVisibilityDelegate activityKeyboardVisibilityDelegate, @@ -80,11 +107,15 @@ @Nullable InsetObserver insetObserver, boolean trackOcclusion) { super( - activity, + context, activityTopResumedSupported, intentRequestTracker, insetObserver, trackOcclusion); + Activity activity = ContextUtils.activityFromContext(context); + if (activity == null) { + throw new IllegalArgumentException("Context is not and does not wrap an Activity"); + } mListenToActivityState = listenToActivityState; if (listenToActivityState) { ApplicationStatus.registerStateListenerForActivity(this, activity);
diff --git a/ui/android/window_android.cc b/ui/android/window_android.cc index 136c134..1aba90b2 100644 --- a/ui/android/window_android.cc +++ b/ui/android/window_android.cc
@@ -39,8 +39,8 @@ : window_(window) { if (++window_->selection_handles_active_count_ == 1) { JNIEnv* env = AttachCurrentThread(); - Java_WindowAndroid_onSelectionHandlesStateChanged( - env, window_->GetJavaObject(), true /* active */); + window_->java_window_->onSelectionHandlesStateChanged(env, + true /* active */); } } @@ -49,8 +49,8 @@ if (--window_->selection_handles_active_count_ == 0) { JNIEnv* env = AttachCurrentThread(); - Java_WindowAndroid_onSelectionHandlesStateChanged( - env, window_->GetJavaObject(), false /* active */); + window_->java_window_->onSelectionHandlesStateChanged(env, + false /* active */); } } @@ -60,7 +60,7 @@ WindowAndroid::ScopedWindowAndroidForTesting::~ScopedWindowAndroidForTesting() { JNIEnv* env = AttachCurrentThread(); - Java_WindowAndroid_destroy(env, window_->GetJavaObject()); + window_->java_window_->destroy(env); } void WindowAndroid::ScopedWindowAndroidForTesting::SetModalDialogManager( @@ -89,7 +89,7 @@ } WindowAndroid::WindowAndroid(JNIEnv* env, - const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<JWindowAndroid>& obj, int display_id, float scroll_factor, bool window_is_wide_color_gamut) @@ -106,8 +106,8 @@ delete this; } -ScopedJavaLocalRef<jobject> WindowAndroid::GetJavaObject() { - return base::android::ScopedJavaLocalRef<jobject>(java_window_); +ScopedJavaLocalRef<JWindowAndroid> WindowAndroid::GetJavaObject() { + return base::android::ScopedJavaLocalRef<JWindowAndroid>(java_window_); } WindowAndroid::~WindowAndroid() { @@ -115,13 +115,13 @@ DCHECK(!compositor_); RemoveAllChildren(true); DCHECK(!pointer_locking_view_); - Java_WindowAndroid_clearNativePointer(AttachCurrentThread(), GetJavaObject()); + java_window_->clearNativePointer(AttachCurrentThread()); } std::unique_ptr<WindowAndroid::ScopedWindowAndroidForTesting> WindowAndroid::CreateForTesting() { JNIEnv* env = AttachCurrentThread(); - long native_pointer = Java_WindowAndroid_createForTesting(env); + long native_pointer = JWindowAndroidClass::createForTesting(env); return std::make_unique<ScopedWindowAndroidForTesting>( reinterpret_cast<WindowAndroid*>(native_pointer)); } @@ -151,13 +151,13 @@ float WindowAndroid::GetRefreshRate() { JNIEnv* env = AttachCurrentThread(); - return Java_WindowAndroid_getRefreshRate(env, GetJavaObject()); + return java_window_->getRefreshRate(env); } gfx::OverlayTransform WindowAndroid::GetOverlayTransform() { JNIEnv* env = AttachCurrentThread(); return static_cast<gfx::OverlayTransform>( - Java_WindowAndroid_getOverlayTransform(env, GetJavaObject())); + java_window_->getOverlayTransform(env)); } std::vector<float> WindowAndroid::GetSupportedRefreshRates() { @@ -166,7 +166,7 @@ JNIEnv* env = AttachCurrentThread(); base::android::ScopedJavaLocalRef<jfloatArray> j_supported_refresh_rates = - Java_WindowAndroid_getSupportedRefreshRates(env, GetJavaObject()); + java_window_->getSupportedRefreshRates(env); std::vector<float> supported_refresh_rates; if (j_supported_refresh_rates) { base::android::JavaFloatArrayToFloatVector(env, j_supported_refresh_rates, @@ -182,8 +182,7 @@ } JNIEnv* env = AttachCurrentThread(); - Java_WindowAndroid_setPreferredRefreshRate(env, GetJavaObject(), - refresh_rate); + java_window_->setPreferredRefreshRate(env, refresh_rate); } void WindowAndroid::SetNeedsAnimate() { @@ -265,8 +264,7 @@ JNIEnv* env = AttachCurrentThread(); std::vector<int> values; base::android::JavaIntArrayToIntVector( - env, Java_WindowAndroid_getProgressBarConfig(env, GetJavaObject()), - &values); + env, java_window_->getProgressBarConfig(env), &values); ProgressBarConfig config; config.background_color = @@ -282,8 +280,7 @@ ModalDialogManagerBridge* WindowAndroid::GetModalDialogManagerBridge() { JNIEnv* env = AttachCurrentThread(); return reinterpret_cast<ModalDialogManagerBridge*>( - Java_WindowAndroid_getNativeModalDialogManagerBridge(env, - GetJavaObject())); + java_window_->getNativeModalDialogManagerBridge(env)); } void WindowAndroid::SetModalDialogManagerForTesting( @@ -295,27 +292,25 @@ void WindowAndroid::ShowToast(const std::string text) { JNIEnv* env = AttachCurrentThread(); - ui::Java_WindowAndroid_showToast( - env, GetJavaObject(), base::android::ConvertUTF8ToJavaString(env, text)); + java_window_->showToast(env, + base::android::ConvertUTF8ToJavaString(env, text)); } void WindowAndroid::SetWideColorEnabled(bool enabled) { JNIEnv* env = AttachCurrentThread(); - Java_WindowAndroid_setWideColorEnabled(env, GetJavaObject(), enabled); + java_window_->setWideColorEnabled(env, enabled); } bool WindowAndroid::HasPermission(const std::string& permission) { JNIEnv* env = AttachCurrentThread(); - return Java_WindowAndroid_hasPermission( - env, GetJavaObject(), - base::android::ConvertUTF8ToJavaString(env, permission)); + return java_window_->hasPermission( + env, base::android::ConvertUTF8ToJavaString(env, permission)); } bool WindowAndroid::CanRequestPermission(const std::string& permission) { JNIEnv* env = AttachCurrentThread(); - return Java_WindowAndroid_canRequestPermission( - env, GetJavaObject(), - base::android::ConvertUTF8ToJavaString(env, permission)); + return java_window_->canRequestPermission( + env, base::android::ConvertUTF8ToJavaString(env, permission)); } WindowAndroid* WindowAndroid::GetWindowAndroid() const { @@ -378,8 +373,7 @@ bool WindowAndroid::SetHasKeyboardCapture(bool keyboard_capture) { JNIEnv* env = AttachCurrentThread(); - return Java_WindowAndroid_setHasKeyboardCapture(env, GetJavaObject(), - keyboard_capture); + return java_window_->setHasKeyboardCapture(env, keyboard_capture); } std::optional<gfx::Rect> WindowAndroid::GetBoundsInScreenCoordinates() { @@ -388,7 +382,7 @@ JNIEnv* env = AttachCurrentThread(); base::android::ScopedJavaLocalRef<jintArray> j_bounds_array = - Java_WindowAndroid_getBoundsInScreenCoordinates(env, GetJavaObject()); + java_window_->getBoundsInScreenCoordinates(env); if (!j_bounds_array) { return std::nullopt; } @@ -423,7 +417,7 @@ // ---------------------------------------------------------------------------- static int64_t JNI_WindowAndroid_Init(JNIEnv* env, - const JavaRef<jobject>& obj, + const JavaRef<JWindowAndroid>& obj, int32_t sdk_display_id, float scroll_factor, bool window_is_wide_color_gamut) {
diff --git a/ui/android/window_android.h b/ui/android/window_android.h index 1c45d09..d89980a 100644 --- a/ui/android/window_android.h +++ b/ui/android/window_android.h
@@ -18,6 +18,7 @@ #include "third_party/blink/public/common/page/content_to_visible_time_reporter.h" #include "ui/android/progress_bar_config.h" #include "ui/android/ui_android_export.h" +#include "ui/android/ui_android_jni_headers/WindowAndroid_shared_jni.h" #include "ui/android/view_android.h" #include "ui/gfx/geometry/vector2d_f.h" #include "ui/gfx/overlay_transform.h" @@ -75,7 +76,7 @@ const base::android::JavaRef<jobject>& jwindow_android); WindowAndroid(JNIEnv* env, - const base::android::JavaRef<jobject>& obj, + const base::android::JavaRef<JWindowAndroid>& obj, int display_id, float scroll_factor, bool window_is_wide_color_gamut); @@ -87,7 +88,7 @@ void Destroy(JNIEnv* env); - base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); + base::android::ScopedJavaLocalRef<JWindowAndroid> GetJavaObject(); void AttachCompositor(WindowAndroidCompositor* compositor); void DetachCompositor(); @@ -201,7 +202,7 @@ // The ID of the display that this window belongs to. int display_id() const { return display_id_; } - base::android::ScopedJavaGlobalRef<jobject> java_window_; + base::android::ScopedJavaGlobalRef<JWindowAndroid> java_window_; const int display_id_; const bool window_is_wide_color_gamut_; raw_ptr<WindowAndroidCompositor> compositor_;
diff --git a/ui/color/win/native_color_mixers_win.cc b/ui/color/win/native_color_mixers_win.cc index 43214fd..40ae14a 100644 --- a/ui/color/win/native_color_mixers_win.cc +++ b/ui/color/win/native_color_mixers_win.cc
@@ -218,6 +218,7 @@ mixer[kColorButtonBackgroundProminentFocused] = {kColorNativeHighlight}; mixer[kColorHelpIconActive] = {kColorNativeHighlight}; mixer[kColorItemSelectionBackground] = {kColorNativeHighlight}; + mixer[kColorMenuButtonBackgroundSelected] = {kColorNativeHighlight}; mixer[kColorMenuSelectionBackground] = {kColorNativeHighlight}; mixer[kColorSubtleAccent] = {kColorNativeHighlight}; mixer[kColorTableBackgroundSelectedFocused] = {kColorNativeHighlight};
diff --git a/ui/ozone/platform/headless/headless_screen.cc b/ui/ozone/platform/headless/headless_screen.cc index 7aea2f3c..b206487b2 100644 --- a/ui/ozone/platform/headless/headless_screen.cc +++ b/ui/ozone/platform/headless/headless_screen.cc
@@ -10,7 +10,6 @@ #include "base/check_deref.h" #include "base/command_line.h" #include "base/containers/flat_set.h" -#include "base/notimplemented.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "components/headless/display_util/headless_display_util.h" @@ -162,8 +161,7 @@ } void HeadlessScreen::UpdateDisplay(const Display& display) { - // TODO(crbug.com/397350115): Implement. - NOTIMPLEMENTED(); + display_list_.UpdateDisplay(display); } void HeadlessScreen::RemoveDisplay(int64_t display_id) {
diff --git a/ui/webui/resources/cr_components/composebox/composebox.html.ts b/ui/webui/resources/cr_components/composebox/composebox.html.ts index 0b747d68..5979f77 100644 --- a/ui/webui/resources/cr_components/composebox/composebox.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox.html.ts
@@ -24,21 +24,21 @@ exportparts="composebox-background"> </search-animated-glow> ` : ''} - <ntp-error-scrim id="errorScrim" part="error-scrim" - ?compact-mode="${this.searchboxLayoutMode === 'Compact' && - this.files_.size === 0}" - .errorMessage="${this.errorMessage_}" - @dismiss-error-scrim="${this.onErrorScrimDismissed_}"> - </ntp-error-scrim> - <div id="composebox" part="composebox" ?inert="${this.errorMessage_}" - @keydown="${this.onKeydown_}" - @focusin="${this.handleComposeboxFocusIn_}" - @focusout="${this.handleComposeboxFocusOut_}" - @dragenter="${this.dragAndDropHandler_.handleDragEnter}" - @dragover="${this.dragAndDropHandler_.handleDragOver}" - @dragleave="${this.dragAndDropHandler_.handleDragLeave}" - @drop="${this.dragAndDropHandler_.handleDrop}" - @paste="${this.onPaste_}"> + <ntp-error-scrim id="errorScrim" part="error-scrim" + ?compact-mode="${this.searchboxLayoutMode === 'Compact' && + this.files_.size === 0}" + .errorMessage="${this.errorMessage_}" + @dismiss-error-scrim="${this.onDismissErrorScrim_}"> + </ntp-error-scrim> + <div id="composebox" part="composebox" ?inert="${this.errorMessage_}" + @keydown="${this.onKeydown_}" + @focusin="${this.onComposeboxFocusin_}" + @focusout="${this.onComposeboxFocusout_}" + @dragenter="${this.dragAndDropHandler_.handleDragEnter}" + @dragover="${this.dragAndDropHandler_.handleDragOver}" + @dragleave="${this.dragAndDropHandler_.handleDragLeave}" + @drop="${this.dragAndDropHandler_.handleDrop}" + @paste="${this.onPaste_}"> <div id="inputContainer" part="input-container"> <div id="textContainer" part="text-container"> <div id="iconContainer" part="icon-container"> @@ -56,11 +56,12 @@ placeholder="${this.inputPlaceholder_}" part="input" .value="${this.input_}" - @click="${this.updateCaret_}" - @keyup="${this.updateCaret_}" - @input="${this.handleInput_}" - @scroll="${this.handleScroll_}" - @focusin="${this.handleInputFocusIn_}"></textarea> + @click="${this.onInputClick_}" + @keyup="${this.onInputKeyup_}" + @input="${this.onInputInput_}" + @scroll="${this.onInputScroll_}" + @focusin="${this.onInputFocusin_}"></textarea> + </textarea> ${this.shouldShowSmartComposeInlineHint_() ? html` <div id="smartCompose" part="smart-compose"> <!-- Comments in between spans to eliminate spacing between @@ -88,7 +89,7 @@ <div id="context" part="context-entrypoint" class="${this.carouselOnTop_ && this.isCollapsible ? 'icon-fade' : ''}"> <cr-composebox-file-inputs id="fileInputs" - @on-file-change="${this.onFileChange_}" + @file-change="${this.onFileChange_}" .disableFileInputs="${this.shouldDisableFileInputs_()}"> ${this.searchboxLayoutMode === 'Compact' && !this.isOmniboxInCompactMode_ ? getContextMenuHtml.bind(this)() @@ -142,7 +143,7 @@ `: ''} ${this.shouldShowVoiceSearchAtBottom_() ? html` <cr-icon-button id="voiceSearchButton" class="voice-icon" part="voice-icon" - iron-icon="cr:mic" @click="${this.openAimVoiceSearch_}" + iron-icon="cr:mic" @click="${this.onVoiceSearchButtonClick_}" title="${this.i18n('voiceSearchButtonLabel')}"> </cr-icon-button> ` : ''} @@ -159,7 +160,7 @@ title="${this.i18n('lensSearchButtonLabel')}" @click="${this.onLensClick_}" ?disabled="${this.lensButtonDisabled}" - @mousedown="${this.onLensIconMouseDown_}"> + @mousedown="${this.onLensIconMousedown_}"> </cr-icon-button>` : ''} <!-- Elements rendered under the input container. --> <!-- TODO: Move the submit button and Lens icon into this slot. --> @@ -170,7 +171,7 @@ ${!this.searchboxNextEnabled ? getSubmitButtonHtml.bind(this)() : ''} </div> <cr-composebox-voice-search id="voiceSearch" - @voice-search-cancel="${this.onVoiceSearchClose_}" + @voice-search-cancel="${this.onVoiceSearchCancel_}" @voice-search-final-result="${this.onVoiceSearchFinalResult_}" @voice-search-error="${this.onVoiceSearchError_}" @transcript-update="${this.onTranscriptUpdate_}"
diff --git a/ui/webui/resources/cr_components/composebox/composebox.ts b/ui/webui/resources/cr_components/composebox/composebox.ts index 69b6179..be3e3df 100644 --- a/ui/webui/resources/cr_components/composebox/composebox.ts +++ b/ui/webui/resources/cr_components/composebox/composebox.ts
@@ -785,7 +785,7 @@ this.i18n('composeboxCancelButtonTitle'); } - protected onContextMenuContainerMouseDown_(e: FocusEvent) { + protected onContextMenuContainerMousedown_(e: FocusEvent) { // Special treatment for the "Tall" layout variants where not clicking on an // inner element should be treated as clicking on a non-focusable area. if (this.searchboxLayoutMode !== 'Compact' && @@ -1109,7 +1109,12 @@ this.deleteFile(e.detail.uuid, e.detail.fromUserAction); } - protected addTabContext_(e: CustomEvent<{ + protected onDeleteTabContext_( + e: CustomEvent<{uuid: UnguessableToken, fromUserAction?: boolean}>) { + this.deleteFile(e.detail.uuid, e.detail.fromUserAction); + } + + protected onAddTabContext_(e: CustomEvent<{ id: number, title: string, url: Url, @@ -1198,6 +1203,10 @@ } } + protected onSubmitContainerClick_(e: MouseEvent) { + this.submitQuery_(e); + } + protected async onContextMenuClosed_() { this.contextMenuOpened_ = false; @@ -1218,7 +1227,7 @@ this.tabSuggestions_ = [...tabs]; } - protected async getTabPreview_(e: CustomEvent<{ + protected async onGetTabPreview_(e: CustomEvent<{ tabId: number, onPreviewFetched: (previewDataUrl: string) => void, }>) { @@ -1258,7 +1267,7 @@ } } - protected openAimVoiceSearch_() { + protected onVoiceSearchButtonClick_() { this.inVoiceSearchMode_ = true; this.animationState = GlowAnimationState.LISTENING; this.fire('voice-search-action', {value: VoiceSearchAction.ACTIVATE}); @@ -1267,7 +1276,7 @@ this.$.voiceSearch.start(); } - protected onVoiceSearchClose_(e: CustomEvent<boolean>) { + protected onVoiceSearchCancel_(e: CustomEvent<boolean>) { // If closing was the user canceling voice search: if (e.detail) { // For contextual tasks composebox voice metrics. @@ -1329,7 +1338,7 @@ } } - protected onLensIconMouseDown_(e: MouseEvent) { + protected onLensIconMousedown_(e: MouseEvent) { // Prevent the composebox from expanding due to being focused by capturing // the mousedown event. This is needed to allow the Lens icon to be // clicked when the composebox does not have focus without expanding the @@ -1409,15 +1418,15 @@ this.handleToolClick_(e.detail.toolMode); } - protected handleDeepSearchClick_() { + protected onDeepSearchClick_() { this.handleToolClick_(ComposeboxToolMode.kDeepSearch); } - protected handleImageGenClick_() { + protected onCreateImageClick_() { this.handleToolClick_(ComposeboxToolMode.kImageGen); } - protected handleCanvasClick_() { + protected onCanvasClick_() { this.handleToolClick_(ComposeboxToolMode.kCanvas); } @@ -1452,13 +1461,13 @@ this.updateInputPlaceholder_(); } - protected onErrorScrimDismissed_() { + protected onDismissErrorScrim_() { this.errorMessage_ = ''; } // Sets the input property to compute the cancel button title without using // "$." syntax as this is not allowed in WillUpdate(). - protected handleInput_(e: Event) { + protected onInputInput_(e: Event) { const inputElement = e.target as HTMLInputElement; this.input_ = inputElement.value; @@ -1518,7 +1527,15 @@ } } - protected updateCaret_() { + protected onInputClick_() { + this.updateCaret_(); + } + + protected onInputKeyup_() { + this.updateCaret_(); + } + + private updateCaret_() { const caret = this.$.caret; const input = this.$.input; const mirror = this.$.mirror; @@ -1697,7 +1714,7 @@ } } - protected handleInputFocusIn_() { + protected onInputFocusin_() { // if there's a last queried input, it's guaranteed that at least // the verbatim match will exist. if (this.lastQueriedInput_) { @@ -1705,7 +1722,7 @@ } } - protected handleComposeboxFocusIn_(e: FocusEvent) { + protected onComposeboxFocusin_(e: FocusEvent) { // Exit early if the focus is still within the composebox. if (this.$.composebox.contains(e.relatedTarget as Node)) { return; @@ -1722,7 +1739,7 @@ this.fire('composebox-focus-in'); } - protected handleComposeboxFocusOut_(e: FocusEvent) { + protected onComposeboxFocusout_(e: FocusEvent) { // Exit early if the focus is still within the composebox. if (this.$.composebox.contains(e.relatedTarget as Node)) { return; @@ -1734,7 +1751,7 @@ this.fire('composebox-focus-out'); } - protected handleScroll_() { + protected onInputScroll_() { const smartCompose = this.shadowRoot.querySelector<HTMLElement>('#smartCompose'); if (!smartCompose) { @@ -1743,7 +1760,7 @@ smartCompose.scrollTop = this.$.input.scrollTop; } - protected handleSubmitFocusIn_() { + protected onSubmitContainerFocusin_() { // Matches should always be greater than 0 due to verbatim match. if (this.input_ && !this.selectedMatch_) { this.selectFirstMatch();
diff --git a/ui/webui/resources/cr_components/composebox/composebox_context_menu.html.ts b/ui/webui/resources/cr_components/composebox/composebox_context_menu.html.ts index b34d6d73..1cecc7b2 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_context_menu.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_context_menu.html.ts
@@ -14,7 +14,7 @@ return html` <div class="context-menu-container" id="contextMenuContainer" part="context-menu-and-tools" - @mousedown="${this.onContextMenuContainerMouseDown_}" + @mousedown="${this.onContextMenuContainerMousedown_}" @click="${this.onContextMenuContainerClick_}"> ${this.showMenuOnClick ? html` <cr-composebox-contextual-entrypoint-and-menu @@ -22,13 +22,13 @@ part="composebox-entrypoint" exportparts="context-menu-entrypoint-icon" class="upload-button no-overlap" - @add-tab-context="${this.addTabContext_}" - @delete-tab-context="${this.onDeleteFile_}" + @add-tab-context="${this.onAddTabContext_}" + @delete-tab-context="${this.onDeleteTabContext_}" @tool-click="${this.onToolClick_}" - @deep-search-click="${this.handleDeepSearchClick_}" - @create-image-click="${this.handleImageGenClick_}" + @deep-search-click="${this.onDeepSearchClick_}" + @create-image-click="${this.onCreateImageClick_}" @model-click="${this.onModelClick_}" - @get-tab-preview="${this.getTabPreview_}" + @get-tab-preview="${this.onGetTabPreview_}" @context-menu-closed="${this.onContextMenuClosed_ }" @context-menu-opened="${this.onContextMenuOpened_}" .showModelPicker="${this.showModelPicker_}" @@ -57,7 +57,7 @@ ${this.searchboxLayoutMode === 'Compact' && this.shouldShowVoiceSearch_() ? html` <cr-icon-button id="voiceSearchButton" class="voice-icon" part="voice-icon" iron-icon="cr:mic" - @click="${this.openAimVoiceSearch_}" + @click="${this.onVoiceSearchButtonClick_}" title="${this.i18n('voiceSearchButtonLabel')}"> </cr-icon-button> ` : ''} @@ -66,7 +66,7 @@ ${this.shouldShowVoiceSearch_() ? html` <cr-icon-button id="voiceSearchButton" class="voice-icon" part="voice-icon" iron-icon="cr:mic" - @click="${this.openAimVoiceSearch_}" + @click="${this.onVoiceSearchButtonClick_}" title="${this.i18n('voiceSearchButtonLabel')}"> </cr-icon-button> ` : ''}
diff --git a/ui/webui/resources/cr_components/composebox/composebox_file_inputs.html.ts b/ui/webui/resources/cr_components/composebox/composebox_file_inputs.html.ts index a0ec3af..1ca9e0a3 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_file_inputs.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_file_inputs.html.ts
@@ -11,8 +11,8 @@ return html`<!--_html_template_start_--> ${this.disableFileInputs ? html`<slot></slot>` : html` <div id="fileUploadWrapper" - @open-image-upload="${this.openImageUpload_}" - @open-file-upload="${this.openFileUpload_}"> + @open-image-upload="${this.onOpenImageUpload_}" + @open-file-upload="${this.onOpenFileUpload_}"> <slot></slot> </div> <input type="file"
diff --git a/ui/webui/resources/cr_components/composebox/composebox_file_inputs.ts b/ui/webui/resources/cr_components/composebox/composebox_file_inputs.ts index 66da0d4..bd81b8a 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_file_inputs.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_file_inputs.ts
@@ -38,12 +38,12 @@ protected accessor imageFileTypes_: string[] = loadTimeData.getString('composeboxImageFileTypes').split(','); - protected openFileUpload_() { + protected onOpenFileUpload_() { assert(this.$.fileInput); this.$.fileInput.click(); } - protected openImageUpload_() { + protected onOpenImageUpload_() { assert(this.$.imageInput); this.$.imageInput.click(); } @@ -51,7 +51,7 @@ protected onFileChange_(e: Event) { const input = e.target as HTMLInputElement; - this.fire('on-file-change', {files: input.files}); + this.fire('file-change', {files: input.files}); input.value = ''; } }
diff --git a/ui/webui/resources/cr_components/composebox/composebox_match.html.ts b/ui/webui/resources/cr_components/composebox/composebox_match.html.ts index b8adabb..b5c4acc 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_match.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_match.html.ts
@@ -21,7 +21,7 @@ part="match-remove-button" aria-label="${this.computeRemoveButtonAriaLabel_()}" @click="${this.onRemoveButtonClick_}" - @mousedown="${this.onRemoveButtonMouseDown_}" + @mousedown="${this.onRemoveButtonMousedown_}" title="${this.removeButtonTitle_}" ?hidden="${!this.match.supportsDeletion}" tabindex="2">
diff --git a/ui/webui/resources/cr_components/composebox/composebox_match.ts b/ui/webui/resources/cr_components/composebox/composebox_match.ts index 71c5125..27f8fb6b 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_match.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_match.ts
@@ -210,7 +210,7 @@ this.matchIndex, this.match.destinationUrl); } - protected onRemoveButtonMouseDown_(e: Event) { + protected onRemoveButtonMousedown_(e: Event) { e.preventDefault(); // Prevents default browser action (focus). } }
diff --git a/ui/webui/resources/cr_components/composebox/composebox_submit_button.html.ts b/ui/webui/resources/cr_components/composebox/composebox_submit_button.html.ts index c08fec6..d423d5b 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_submit_button.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_submit_button.html.ts
@@ -10,8 +10,8 @@ // clang-format off return html` <div id="submitContainer" class="icon-fade" part="submit" - tabindex="-1" @click="${this.submitQuery_}" - @focusin="${this.handleSubmitFocusIn_}"> + tabindex="-1" @click="${this.onSubmitContainerClick_}" + @focusin="${this.onSubmitContainerFocusin_}"> <div id="submitOverlay" part="submit-overlay" title="${this.i18n('composeboxSubmitButtonTitle')}"> </div>
diff --git a/ui/webui/resources/cr_components/composebox/composebox_tool_chips.html.ts b/ui/webui/resources/cr_components/composebox/composebox_tool_chips.html.ts index 4893f94..8e01b32 100644 --- a/ui/webui/resources/cr_components/composebox/composebox_tool_chips.html.ts +++ b/ui/webui/resources/cr_components/composebox/composebox_tool_chips.html.ts
@@ -21,7 +21,7 @@ 'removeToolChipAriaLabel', this.getToolChipLabel_(ComposeboxToolMode.kDeepSearch))}" ?visible="${true}" - @click="${this.handleDeepSearchClick_}"> + @click="${this.onDeepSearchClick_}"> </cr-composebox-tool-chip> ` : ''} ${this.activeToolMode_ === ComposeboxToolMode.kImageGen ? html` @@ -35,7 +35,7 @@ 'removeToolChipAriaLabel', this.getToolChipLabel_(ComposeboxToolMode.kImageGen))}" ?visible="${true}" - @click="${this.handleImageGenClick_}"> + @click="${this.onCreateImageClick_}"> </cr-composebox-tool-chip> ` : ''} ${this.activeToolMode_ === ComposeboxToolMode.kCanvas ? html` @@ -49,7 +49,7 @@ 'removeToolChipAriaLabel', this.getToolChipLabel_(ComposeboxToolMode.kCanvas))}" ?visible="${true}" - @click="${this.handleCanvasClick_}"> + @click="${this.onCanvasClick_}"> </cr-composebox-tool-chip> ` : ''}`; // clang-format on
diff --git a/ui/webui/resources/cr_components/composebox/contextual_action_menu.html.ts b/ui/webui/resources/cr_components/composebox/contextual_action_menu.html.ts index 1293345e..9fa4f220 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_action_menu.html.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_action_menu.html.ts
@@ -52,14 +52,14 @@ `: ''} ${this.imageUploadAllowed_ ? html` <button id="imageUpload" class="dropdown-item" role="menuitem" - @click="${this.openImageUpload_}" + @click="${this.onImageUploadClick_}" ?disabled="${this.imageUploadDisabled_}"> <cr-icon icon="composebox:imageUpload"></cr-icon> ${this.getInputTypeLabel_(InputType.kLensImage)} </button>` : ''} ${this.fileUploadAllowed_ ? html`<button id="fileUpload" class="dropdown-item" role="menuitem" - @click="${this.openFileUpload_}" + @click="${this.onFileUploadClick_}" ?disabled="${this.fileUploadDisabled_}"> <cr-icon icon="composebox:fileUpload"></cr-icon> ${this.getInputTypeLabel_(InputType.kLensFile)}
diff --git a/ui/webui/resources/cr_components/composebox/contextual_action_menu.ts b/ui/webui/resources/cr_components/composebox/contextual_action_menu.ts index 881f505d..0a11a5ca 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_action_menu.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_action_menu.ts
@@ -384,12 +384,12 @@ return this.tabPreviewsEnabled_ && this.tabPreviewUrl_ !== ''; } - protected openImageUpload_() { + protected onImageUploadClick_() { this.fire('open-image-upload'); this.$.menu.close(); } - protected openFileUpload_() { + protected onFileUploadClick_() { this.fire('open-file-upload'); this.$.menu.close(); }
diff --git a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.html.ts b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.html.ts index 5494bc8..e2d16e00 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.html.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.html.ts
@@ -14,7 +14,7 @@ <cr-composebox-contextual-entrypoint-button id="entrypointButton" exportparts="context-menu-entrypoint-icon, entrypoint-button" .inputState="${this.inputState}" - @context-menu-entrypoint-click="${this.showMenuAtEntrypoint_}" + @context-menu-entrypoint-click="${this.onContextMenuEntrypointClick_}" ?upload-button-disabled="${this.uploadButtonDisabled}" ?show-context-menu-description="${this.showContextMenuDescription}" glif-animation-state="${this.glifAnimationState}">
diff --git a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.ts b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.ts index 2d68920..6013ca15 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_and_menu.ts
@@ -112,7 +112,11 @@ this.fire('context-menu-closed'); } - protected showMenuAtEntrypoint_() { + protected onContextMenuEntrypointClick_() { + this.showMenuAtEntrypoint_(); + } + + private showMenuAtEntrypoint_() { this.$.entrypointButton.classList.add('menu-open'); const entrypoint = this.$.entrypointButton.shadowRoot.querySelector<HTMLElement>(
diff --git a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.html.ts b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.html.ts index 862f6995..dedc90a 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.html.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.html.ts
@@ -20,7 +20,7 @@ aria-label="${this.i18n('addContextTitle')}"> <cr-icon id="entrypointIcon" icon="cr:add" slot="prefix-icon"></cr-icon> <span id="description" - @animationend="${this.onDescriptionAnimationEnd_}"> + @animationend="${this.onDescriptionAnimationend_}"> ${this.i18n('addContext')} </span> </cr-button> @@ -38,7 +38,7 @@ <div class="aim-gradient-outer-blur aim-c"></div> <div class="aim-gradient-solid aim-c"></div> <div class="aim-background aim-c" - @animationend="${this.onAimBackgroundAnimationEnd_}"> + @animationend="${this.onAimBackgroundAnimationend_}"> </div> ` : ''} </div>`
diff --git a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.ts b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.ts index 2f5e65f..e484110 100644 --- a/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.ts +++ b/ui/webui/resources/cr_components/composebox/contextual_entrypoint_button.ts
@@ -99,11 +99,11 @@ }); } - protected onDescriptionAnimationEnd_(e: AnimationEvent) { + protected onDescriptionAnimationend_(e: AnimationEvent) { this.onAnimationEnd_(e, 'slide-in'); } - protected onAimBackgroundAnimationEnd_(e: AnimationEvent) { + protected onAimBackgroundAnimationend_(e: AnimationEvent) { if (this.showContextMenuDescription) { return; }
diff --git a/ui/webui/resources/cr_components/composebox/file_thumbnail.html.ts b/ui/webui/resources/cr_components/composebox/file_thumbnail.html.ts index d76648d..3d1f258 100644 --- a/ui/webui/resources/cr_components/composebox/file_thumbnail.html.ts +++ b/ui/webui/resources/cr_components/composebox/file_thumbnail.html.ts
@@ -33,7 +33,7 @@ iron-icon="cr:clear" title="${this.file.name}" aria-label="${this.deleteFileButtonTitle_}" - @click="${this.deleteFile_}"> + @click="${this.onRemoveButtonClick_}"> </cr-icon-button>`: ''} </div> <div class="chip-overlay"></div> @@ -62,7 +62,7 @@ iron-icon="cr:clear" title="${this.file.name}" aria-label="${this.deleteFileButtonTitle_}" - @click="${this.deleteFile_}"> + @click="${this.onRemoveButtonClick_}"> </cr-icon-button>`: ''} </div> <div class="chip-overlay"></div> @@ -95,7 +95,7 @@ iron-icon="cr:clear" title="${this.file.name}" aria-label="${this.deleteFileButtonTitle_}" - @click="${this.deleteFile_}"> + @click="${this.onRemoveButtonClick_}"> </cr-icon-button>`: ''} </div> <div class="chip-overlay"></div> @@ -117,7 +117,7 @@ iron-icon="cr:clear" title="${this.file.name}" aria-label="${this.deleteFileButtonTitle_}" - @click="${this.deleteFile_}"> + @click="${this.onRemoveButtonClick_}"> </cr-icon-button>`: ''} </div>` : html` <div id="pdfChip" class="chip"> @@ -140,7 +140,7 @@ iron-icon="cr:clear" title="${this.file.name}" aria-label="${this.deleteFileButtonTitle_}" - @click="${this.deleteFile_}"> + @click="${this.onRemoveButtonClick_}"> </cr-icon-button>`: ''} </div> <div class="chip-overlay"></div>
diff --git a/ui/webui/resources/cr_components/composebox/file_thumbnail.ts b/ui/webui/resources/cr_components/composebox/file_thumbnail.ts index 5dcc005..b26893d 100644 --- a/ui/webui/resources/cr_components/composebox/file_thumbnail.ts +++ b/ui/webui/resources/cr_components/composebox/file_thumbnail.ts
@@ -73,7 +73,7 @@ } } - protected deleteFile_() { + protected onRemoveButtonClick_() { // TODO(crbug.com/422559977): Send call to handler to delete file from // cache. this.fire('delete-file', {uuid: this.file.uuid, fromUserAction: true});
diff --git a/ui/webui/resources/cr_components/composebox/recent_tab_chip.html.ts b/ui/webui/resources/cr_components/composebox/recent_tab_chip.html.ts index 7c60314..f1add52d 100644 --- a/ui/webui/resources/cr_components/composebox/recent_tab_chip.html.ts +++ b/ui/webui/resources/cr_components/composebox/recent_tab_chip.html.ts
@@ -10,7 +10,7 @@ // clang-format off return this.recentTab ? html`<!--_html_template_start_--> <cr-button id="recentTabButton" - @click="${this.addTabContext_}" + @click="${this.onRecentTabButtonClick_}" title="${this.recentTabChipTitle_}" aria-label="${this.recentTab.showInCurrentTabChip ? this.i18n('askAboutThisPageAriaLabel',
diff --git a/ui/webui/resources/cr_components/composebox/recent_tab_chip.ts b/ui/webui/resources/cr_components/composebox/recent_tab_chip.ts index 9d4fca4..001d495c 100644 --- a/ui/webui/resources/cr_components/composebox/recent_tab_chip.ts +++ b/ui/webui/resources/cr_components/composebox/recent_tab_chip.ts
@@ -61,7 +61,7 @@ return `${htmlEscape(this.recentTab.title)} - ${htmlEscape(domain)}`; } - protected addTabContext_(e: Event) { + protected onRecentTabButtonClick_(e: Event) { e.stopPropagation(); assert(this.recentTab);
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.html.ts b/ui/webui/resources/cr_components/searchbox/searchbox.html.ts index d103683..a435566 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.html.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox.html.ts
@@ -50,11 +50,11 @@ aria-expanded="${this.dropdownIsVisible}" aria-controls="matches" aria-description="${this.searchboxAriaDescription}" placeholder="${this.computePlaceholderText_(this.placeholderText)}" - @copy="${this.onInputCutCopy_}" - @cut="${this.onInputCutCopy_}" @focus="${this.onInputFocus_}" + @copy="${this.onInputCopy_}" + @cut="${this.onInputCut_}" @focus="${this.onInputFocus_}" @focusout="${this.onInputFocusout_}" @input="${this.onInputInput_}" @keydown="${this.onInputKeydown_}" - @keyup="${this.onInputKeyup_}" @mousedown="${this.onInputMouseDown_}" + @keyup="${this.onInputKeyup_}" @mousedown="${this.onInputMousedown_}" @paste="${this.onInputPaste_}"></textarea> ` : html` <input id="input" class="truncate" type="search" autocomplete="off" @@ -63,11 +63,11 @@ aria-expanded="${this.dropdownIsVisible}" aria-controls="matches" aria-description="${this.searchboxAriaDescription}" placeholder="${this.computePlaceholderText_(this.placeholderText)}" - @copy="${this.onInputCutCopy_}" - @cut="${this.onInputCutCopy_}" @focus="${this.onInputFocus_}" + @copy="${this.onInputCopy_}" + @cut="${this.onInputCut_}" @focus="${this.onInputFocus_}" @focusout="${this.onInputFocusout_}" @input="${this.onInputInput_}" @keydown="${this.onInputKeydown_}" - @keyup="${this.onInputKeyup_}" @mousedown="${this.onInputMouseDown_}" + @keyup="${this.onInputKeyup_}" @mousedown="${this.onInputMousedown_}" @paste="${this.onInputPaste_}"> </input> `} @@ -91,7 +91,7 @@ ` : ''} ${this.composeButtonEnabled ? html` <cr-searchbox-compose-button id="composeButton" - @compose-click="${this.onComposeButtonClick_}"> + @compose-click="${this.onComposeClick_}"> </cr-searchbox-compose-button> ` : ''} </div>
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.ts b/ui/webui/resources/cr_components/searchbox/searchbox.ts index 91782633..1af00804 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox.ts
@@ -77,6 +77,14 @@ shiftKey: boolean; } +export interface OpenComposeboxEventDetail { + searchboxText: string; + contextFiles: ContextualUpload[]; + mode: ToolMode; + model: ModelMode; + inputState: InputState|null; +} + export interface SearchboxElement { $: { icon: SearchboxIconElement, @@ -626,7 +634,15 @@ // Event handlers //============================================================================ - protected onInputCutCopy_(e: ClipboardEvent) { + protected onInputCut_(e: ClipboardEvent) { + this.onInputCutCopy_(e); + } + + protected onInputCopy_(e: ClipboardEvent) { + this.onInputCutCopy_(e); + } + + private onInputCutCopy_(e: ClipboardEvent) { // Only handle cut/copy when input has content and it's all selected. if (!this.$.input.value || this.$.input.selectionStart !== 0 || this.$.input.selectionEnd !== this.$.input.value.length || @@ -750,7 +766,7 @@ } } - protected onInputMouseDown_(e: MouseEvent|null) { + protected onInputMousedown_(e: MouseEvent|null) { // Non-main (generally left) mouse clicks are ignored. if (e && e.button !== 0) { return; @@ -1065,7 +1081,7 @@ e.detail.files, ComposeboxContextAddedMethod.CONTEXT_MENU); } - protected addTabContext_(e: CustomEvent<{ + protected onAddTabContext_(e: CustomEvent<{ id: number, title: string, url: Url, @@ -1094,7 +1110,7 @@ this.tabSuggestions_ = [...tabs]; } - protected async getTabPreview_(e: CustomEvent<{ + protected async onGetTabPreview_(e: CustomEvent<{ tabId: number, onPreviewFetched: (previewDataUrl: string) => void, }>) { @@ -1113,10 +1129,10 @@ } this.focusInput(); - this.onInputMouseDown_(null); + this.onInputMousedown_(null); } - protected onContextMenuContainerMouseDown_(e: FocusEvent) { + protected onContextMenuContainerMousedown_(e: FocusEvent) { // Special treatment for the "Tall" layout variants where not clicking on an // inner element should be treated as clicking on a non-focusable area. if (this.searchboxLayoutMode !== 'Compact' && @@ -1137,7 +1153,7 @@ this.refreshTabSuggestions_(/*forceRefresh=*/ true); } - protected onComposeButtonClick_(e: CustomEvent<ClickEventDetail>) { + protected onComposeClick_(e: CustomEvent<ClickEventDetail>) { // TODO(crbug.com/463667769): Call submitQuery here since RealboxHandler is // now a `ContextualSearchboxHandler`. this.pageHandler_.activateMetricsFunnel('AiModeButton'); @@ -1196,11 +1212,11 @@ this.openComposebox_([], e.detail.toolMode); } - protected handleDeepSearchClick_() { + protected onDeepSearchClick_() { this.openComposebox_([], ToolMode.kDeepSearch); } - protected handleImageGenClick_() { + protected onCreateImageClick_() { this.openComposebox_([], ToolMode.kImageGen); } @@ -1218,7 +1234,7 @@ assert(context); context.closeMenu(); } - this.fire('open-composebox', { + this.fire<OpenComposeboxEventDetail>('open-composebox', { searchboxText: this.$.input.value, contextFiles: uploads, mode: mode,
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox_contextual_entrypoint.html.ts b/ui/webui/resources/cr_components/searchbox/searchbox_contextual_entrypoint.html.ts index 8ad3e64..35ecc253 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox_contextual_entrypoint.html.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox_contextual_entrypoint.html.ts
@@ -10,19 +10,19 @@ export function getHtml(this: SearchboxElement) { // clang-format off return html` -<cr-composebox-file-inputs @on-file-change="${this.onFileChange_}"> +<cr-composebox-file-inputs @file-change="${this.onFileChange_}"> <div class="context-menu-container" id="contextMenuContainer" - @mousedown="${this.onContextMenuContainerMouseDown_}" + @mousedown="${this.onContextMenuContainerMousedown_}" @click="${this.onContextMenuContainerClick_}"> <cr-composebox-contextual-entrypoint-and-menu id="context" exportparts="context-menu-entrypoint-icon" class="upload-button" - @add-tab-context="${this.addTabContext_}" + @add-tab-context="${this.onAddTabContext_}" @tool-click="${this.onToolClick_}" - @deep-search-click="${this.handleDeepSearchClick_}" - @create-image-click="${this.handleImageGenClick_}" + @deep-search-click="${this.onDeepSearchClick_}" + @create-image-click="${this.onCreateImageClick_}" @model-click="${this.onModelClick_}" - @get-tab-preview="${this.getTabPreview_}" + @get-tab-preview="${this.onGetTabPreview_}" @context-menu-entrypoint-click="${this.onContextMenuEntrypointClick_}" @context-menu-closed="${this.onContextMenuClosed_}" @context-menu-opened="${this.onContextMenuOpened_}"
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox_match.html.ts b/ui/webui/resources/cr_components/searchbox/searchbox_match.html.ts index 193be372..5103cc0b 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox_match.html.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox_match.html.ts
@@ -36,7 +36,7 @@ hint="${this.match.keywordChipHint}" icon-path="//resources/images/icon_search.svg" aria-label="${this.match.keywordChipA11y}" - @execute-action="${this.onActivateKeyword_}" + @execute-action="${this.onKeywordExecuteAction_}" tabindex="1"> </cr-searchbox-action> </div> @@ -64,7 +64,7 @@ title="${this.removeButtonTitle_}" ?hidden="${!this.match.supportsDeletion}" @click="${this.onRemoveButtonClick_}" - @mousedown="${this.onRemoveButtonMouseDown_}"> + @mousedown="${this.onRemoveButtonMousedown_}"> </cr-icon-button> </div> <!--_html_template_end_-->`;
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox_match.ts b/ui/webui/resources/cr_components/searchbox/searchbox_match.ts index 06881f0..d503b0f8 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox_match.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox_match.ts
@@ -256,7 +256,7 @@ // Event handlers //============================================================================ - protected onActivateKeyword_(e: ActionEvent) { + protected onKeywordExecuteAction_(e: ActionEvent) { // Keyboard activation isn't possible because when the keyword chip is // focused, focus is redirected to the omnibox view. const event = e.detail.event as PointerEvent; @@ -324,7 +324,7 @@ this.matchIndex, this.match.destinationUrl); } - protected onRemoveButtonMouseDown_(e: Event) { + protected onRemoveButtonMousedown_(e: Event) { e.preventDefault(); // Prevents default browser action (focus). }
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox_recent_tab_chip.html.ts b/ui/webui/resources/cr_components/searchbox/searchbox_recent_tab_chip.html.ts index 1326fc6..3048da5 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox_recent_tab_chip.html.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox_recent_tab_chip.html.ts
@@ -13,7 +13,7 @@ <div id="recentTabChipContainer"> <composebox-recent-tab-chip class="upload-button contextual-chip" .recentTab="${this.recentTabForChip_}" - @add-tab-context="${this.addTabContext_}"> + @add-tab-context="${this.onAddTabContext_}"> </composebox-recent-tab-chip> </div> ` : nothing}
diff --git a/url/BUILD.gn b/url/BUILD.gn index b71854f..061755d9 100644 --- a/url/BUILD.gn +++ b/url/BUILD.gn
@@ -70,12 +70,13 @@ public_deps = [ "//base", "//build:robolectric_buildflags", + "//net:cronet_buildflags", ] configs += [ "//build/config/compiler:wexit_time_destructors" ] if (is_android || is_robolectric) { - deps += [ ":url_jni_headers" ] + public_deps += [ ":url_jni_headers" ] if (!is_cronet_build) { sources += [ "android/gurl_android.cc",
diff --git a/url/DEPS b/url/DEPS index 08535be..0003a33 100644 --- a/url/DEPS +++ b/url/DEPS
@@ -3,6 +3,7 @@ "-base/i18n", "-third_party/icu", "+third_party/jni_zero", + "+net/base/cronet_buildflags.h", ] specific_include_rules = {
diff --git a/url/android/gurl_android.cc b/url/android/gurl_android.cc index 044a94d5..1ef72cf 100644 --- a/url/android/gurl_android.cc +++ b/url/android/gurl_android.cc
@@ -62,9 +62,9 @@ } // static -ScopedJavaLocalRef<jobject> GURLAndroid::FromNativeGURL(JNIEnv* env, - const GURL& gurl) { - ScopedJavaLocalRef<jobject> j_gurl = Java_GURL_Constructor(env); +ScopedJavaLocalRef<JGURL> GURLAndroid::FromNativeGURL(JNIEnv* env, + const GURL& gurl) { + ScopedJavaLocalRef<JGURL> j_gurl = Java_GURL_Constructor(env); InitFromGURL(env, gurl, j_gurl); return j_gurl; }
diff --git a/url/android/gurl_android.h b/url/android/gurl_android.h index 38a4edf..3eb523a 100644 --- a/url/android/gurl_android.h +++ b/url/android/gurl_android.h
@@ -9,6 +9,7 @@ #include "base/containers/span.h" #include "third_party/jni_zero/jni_zero.h" #include "url/gurl.h" +#include "url/url_jni_headers/GURL_shared_jni.h" namespace url { @@ -16,8 +17,8 @@ public: static GURL ToNativeGURL(JNIEnv* env, const jni_zero::JavaRef<jobject>& j_gurl); - static jni_zero::ScopedJavaLocalRef<jobject> FromNativeGURL(JNIEnv* env, - const GURL& gurl); + static jni_zero::ScopedJavaLocalRef<JGURL> FromNativeGURL(JNIEnv* env, + const GURL& gurl); static jni_zero::ScopedJavaLocalRef<jobject> EmptyGURL(JNIEnv* env); };
diff --git a/url/android/origin_android.cc b/url/android/origin_android.cc index b63ba965..dd603ea 100644 --- a/url/android/origin_android.cc +++ b/url/android/origin_android.cc
@@ -25,7 +25,7 @@ scheme, host, port, Origin::Nonce(nonce_token)); } -base::android::ScopedJavaLocalRef<jobject> Origin::ToJavaObject( +base::android::ScopedJavaLocalRef<JOrigin> Origin::ToJavaObject( JNIEnv* env) const { const base::UnguessableToken* token = GetNonceForSerialization(); return Java_Origin_Constructor(env, tuple_.scheme(), tuple_.host(),
diff --git a/url/origin.h b/url/origin.h index d266e99..fa37b81 100644 --- a/url/origin.h +++ b/url/origin.h
@@ -22,10 +22,13 @@ #include "build/build_config.h" #include "build/buildflag.h" #include "build/robolectric_buildflags.h" +#include "net/base/cronet_buildflags.h" #include "url/scheme_host_port.h" -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC) +#if (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC)) && \ + !BUILDFLAG(CRONET_BUILD) #include "base/android/jni_android.h" +#include "url/url_jni_headers/Origin_shared_jni.h" #endif class GURL; @@ -331,8 +334,9 @@ // and precursor information. std::string GetDebugString(bool include_nonce = true) const; -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC) - jni_zero::ScopedJavaLocalRef<jobject> ToJavaObject(JNIEnv* env) const; +#if (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC)) && \ + !BUILDFLAG(CRONET_BUILD) + jni_zero::ScopedJavaLocalRef<JOrigin> ToJavaObject(JNIEnv* env) const; static Origin FromJavaObject(JNIEnv* env, const jni_zero::JavaRef<jobject>& java_origin); static int64_t CreateNative(JNIEnv* env, @@ -342,7 +346,7 @@ bool is_opaque, uint64_t tokenHighBits, uint64_t tokenLowBits); -#endif // BUILDFLAG(IS_ANDROID) +#endif void WriteIntoTrace(perfetto::TracedValue context) const; @@ -351,7 +355,8 @@ size_t EstimateMemoryUsage() const; private: -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC) +#if (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_ROBOLECTRIC)) && \ + !BUILDFLAG(CRONET_BUILD) friend Origin CreateOpaqueOriginForAndroid( const std::string& scheme, const std::string& host, @@ -507,7 +512,7 @@ } // namespace url -#if BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(CRONET_BUILD) namespace jni_zero { // @JniType conversion function.
diff --git a/v8 b/v8 index 07ff726..7317cc7 160000 --- a/v8 +++ b/v8
@@ -1 +1 @@ -Subproject commit 07ff726835cdda38049d1af45c0d69135e5d0503 +Subproject commit 7317cc77afe339d0618369e2c299a3ada5e6c23f