From 72ee26e314f471012ee4ee60b5cf1831c0ed6a45 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 9 Apr 2025 15:57:49 -0700 Subject: [PATCH 01/53] Initialize Flutter Beta (`flutter-3.32-candidate.0`) (#166783) Closes https://github.com/flutter/flutter/issues/166811. ```sh $ dev/conductor/bin/conductor start \ --candidate-branch=flutter-3.32-candidate.0 \ --release-channel=beta \ --github-username=matanlurey \ --dart-revision=0d6811928830b87e36a0f49eb7fe554c308d3699 ``` --- .ci.yaml | 1 + DEPS | 16 +- bin/internal/flutter_packages.version | 2 +- bin/internal/release-candidate-branch.version | 1 + .../flutter_gallery/pubspec.yaml | 4 +- dev/integration_tests/link_hook/pubspec.yaml | 4 +- docs/roadmap/Roadmap.md | 2 +- .../flutter/ci/licenses_golden/licenses_dart | 4 +- .../flutter/ci/licenses_golden/licenses_skia | 2 +- .../display_list/aiks_dl_basic_unittests.cc | 18 ++ .../display_list/aiks_dl_blur_unittests.cc | 51 ---- .../flutter/impeller/display_list/paint.cc | 3 +- engine/src/flutter/impeller/geometry/matrix.h | 13 ++ .../impeller/geometry/matrix_unittests.cc | 9 + .../renderer/backend/vulkan/allocator_vk.cc | 19 +- .../backend/vulkan/allocator_vk_unittests.cc | 20 ++ .../backend/vulkan/texture_source_vk.h | 1 - .../lib/src/engine/semantics/scrollable.dart | 186 +++++++-------- .../test/engine/semantics/semantics_test.dart | 132 ++++++----- engine/src/flutter/pubspec.yaml | 4 - engine/src/flutter/web_sdk/pubspec.yaml | 4 - .../sliver/sliver_ensure_semantics.0.dart | 207 +++++++++++++++++ .../sliver_ensure_semantics.0_test.dart | 14 ++ .../flutter/lib/src/material/carousel.dart | 9 + .../flutter/lib/src/material/list_tile.dart | 18 +- .../lib/src/material/list_tile_theme.dart | 14 +- .../lib/src/rendering/proxy_sliver.dart | 8 + .../flutter/lib/src/rendering/sliver.dart | 23 ++ .../rendering/sliver_multi_box_adaptor.dart | 12 + .../lib/src/rendering/sliver_tree.dart | 3 +- .../flutter/lib/src/rendering/viewport.dart | 20 +- packages/flutter/lib/src/widgets/sliver.dart | 45 ++++ .../test/cupertino/date_picker_test.dart | 6 +- .../flutter/test/material/list_tile_test.dart | 132 +++++++++++ .../test/material/list_tile_theme_test.dart | 219 ++++++++++++++++++ .../test/widgets/color_filter_test.dart | 4 +- .../test/widgets/selectable_text_test.dart | 5 +- .../test/widgets/shader_mask_test.dart | 4 +- .../test/widgets/sliver_tree_test.dart | 44 ++++ .../flutter/test/widgets/slivers_test.dart | 95 +++++--- packages/flutter_tools/lib/executable.dart | 2 - .../lib/src/commands/widget_preview.dart | 56 ----- .../lib/src/compute_dev_dependencies.dart | 8 +- .../lib/src/custom_devices/custom_device.dart | 24 +- packages/flutter_tools/lib/src/dart/pub.dart | 28 ++- .../src/runner/flutter_command_runner.dart | 7 - .../lib/src/widget_preview/dtd_services.dart | 101 -------- packages/flutter_tools/pubspec.yaml | 4 +- .../static/custom-devices.schema.json | 4 +- .../templates/template_manifest.json | 1 - .../lib/src/dtd_services.dart.tmpl | 33 --- .../src/widget_preview_rendering.dart.tmpl | 3 - .../widget_preview_scaffold/pubspec.yaml.tmpl | 1 - .../widget_preview/widget_preview_test.dart | 3 - .../permeable/widget_preview_test.dart | 3 - .../compute_dev_dependencies_test.dart | 28 +++ .../custom_devices/custom_device_test.dart | 95 ++++++++ .../general.shard/dart/pub_deps_test.dart | 12 +- .../widget_preview_test.dart | 48 +--- .../lib/src/dtd_services.dart | 33 --- .../lib/src/widget_preview_rendering.dart | 3 - .../widget_preview_scaffold/pubspec.yaml | 14 +- 62 files changed, 1258 insertions(+), 631 deletions(-) create mode 100644 bin/internal/release-candidate-branch.version create mode 100644 examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart create mode 100644 examples/api/test/widgets/sliver/sliver_ensure_semantics.0_test.dart delete mode 100644 packages/flutter_tools/lib/src/widget_preview/dtd_services.dart delete mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl delete mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd_services.dart diff --git a/.ci.yaml b/.ci.yaml index 4033c1fcfa749..2571271d5ef15 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -5132,6 +5132,7 @@ targets: - name: Mac_ios microbenchmarks_ios recipe: devicelab/devicelab_drone presubmit: false + bringup: true timeout: 60 properties: tags: > diff --git a/DEPS b/DEPS index d4eb7b945fd2f..c57b9b2fe5389 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '7b929584566c2c20f59d692301a40e55528a83a7', + 'skia_revision': 'ac01f9306a0c08acf128d37bbd7b3e199525cc40', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -56,16 +56,16 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '87965ab4864e444c521023820eb06e569d007059', + 'dart_revision': '0d6811928830b87e36a0f49eb7fe554c308d3699', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': 'b4bdcc33115b31758c56b83bb9de4642c411a042', - 'dart_boringssl_rev': 'e68438b863afaa3e81e1771b91819817780f3b60', + 'dart_boringssl_rev': '8d8df26fc54f6e5fb0bf404a76ce973c0413eafc', 'dart_core_rev': '7a80178ca72b01b0efb99a9a9a654d83ca21d6b7', - 'dart_devtools_rev': 'f10e8df8c517fb0412b9a66c626581867c9c267d', + 'dart_devtools_rev': '1fb2f4ce5099042b7f2dfa93dec675a21861d21f', 'dart_ecosystem_rev': '391a80ccb774cfebe4865bcd7e933d1ab016eea5', - 'dart_http_rev': '32d5ffcc8d0d5ce9a3a76a293230fa70ec2cc88f', + 'dart_http_rev': '6fabf06b90d962cf9a6c009bbe919902ff1a1471', 'dart_i18n_rev': 'de1943629469719bf34269bf90fcdbe9334a73f3', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', @@ -73,7 +73,7 @@ vars = { 'dart_protobuf_rev': '1aaa332af75c61ff32739821f7ec52186ff18d4c', 'dart_pub_rev': 'b2c03b448a47fdd52800609b9222cd737be3a934', 'dart_sync_http_rev': 'dc54465f07d9652875deeade643256dafa2fbc6c', - 'dart_tools_rev': '62bc13bc086a66ce9a6a3e64865c82d17a1379b3', + 'dart_tools_rev': '8d49319b95912a5dff3d7ba4cd861458c55c5608', 'dart_vector_math_rev': 'f08d7d2652e9ecf7d8f8605d9983335174511c95', 'dart_web_rev': '5a39fdc396ae40344308975140343c23b6863261', 'dart_webdev_rev': '697f2f7f56517b0678c6256e0834778905acfc0d', @@ -308,7 +308,7 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@b4bdcc33115b31758c56b83bb9de4642c411a042', 'engine/src/flutter/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:f10e8df8c517fb0412b9a66c626581867c9c267d'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:1fb2f4ce5099042b7f2dfa93dec675a21861d21f'}]}, 'engine/src/flutter/third_party/dart/third_party/pkg/core': Var('dart_git') + '/core.git' + '@' + Var('dart_core_rev'), @@ -332,7 +332,7 @@ deps = { Var('dart_git') + '/leak_tracker.git@f5620600a5ce1c44f65ddaa02001e200b096e14c', 'engine/src/flutter/third_party/dart/third_party/pkg/native': - Var('dart_git') + '/native.git@75f3408cd72c1e217f162fb5e43f4cdf1d34e71d', + Var('dart_git') + '/native.git@4928765d7681ad8a4211c5977afd2f93f50b3f65', 'engine/src/flutter/third_party/dart/third_party/pkg/protobuf': Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'), diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 78e0e167d4e40..052f78fd6d36a 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -267ac7b66308ad34b6ce14c1f5399ab0691f9ede +2405f6a2b7e1664e2779030c6b651676ecbe7651 diff --git a/bin/internal/release-candidate-branch.version b/bin/internal/release-candidate-branch.version new file mode 100644 index 0000000000000..d8027b669ff7a --- /dev/null +++ b/bin/internal/release-candidate-branch.version @@ -0,0 +1 @@ +flutter-3.32-candidate.0 diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml index 488f700520a13..c4d001960895b 100644 --- a/dev/integration_tests/flutter_gallery/pubspec.yaml +++ b/dev/integration_tests/flutter_gallery/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - html: 0.15.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.15.5+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -272,4 +272,4 @@ flutter: - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf -# PUBSPEC CHECKSUM: deed +# PUBSPEC CHECKSUM: 054a diff --git a/dev/integration_tests/link_hook/pubspec.yaml b/dev/integration_tests/link_hook/pubspec.yaml index dfac7e28b0fca..290b0c80edd04 100644 --- a/dev/integration_tests/link_hook/pubspec.yaml +++ b/dev/integration_tests/link_hook/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: dev_dependencies: ffi: 2.1.4 - ffigen: 18.0.0 + ffigen: 18.1.0 flutter_lints: 5.0.0 test: 1.25.15 @@ -68,4 +68,4 @@ dev_dependencies: webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml_edit: 2.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: a861 +# PUBSPEC CHECKSUM: bf62 diff --git a/docs/roadmap/Roadmap.md b/docs/roadmap/Roadmap.md index 70a7604fd63a3..7d3ce83d853ef 100644 --- a/docs/roadmap/Roadmap.md +++ b/docs/roadmap/Roadmap.md @@ -2,7 +2,7 @@ In the interest of transparency, we want to share high-level details of our road Our plans will evolve over time based on customer feedback and new market opportunities. We will use our surveys and feedback on GitHub issues to prioritize work. The list here shouldn't be viewed either as exhaustive nor a promise that we will complete all this work. If you have feedback about what you think we should work on, we encourage you to get in touch by [filing an issue](https://github.com/flutter/flutter/issues/new/choose), or using the "thumbs-up" emoji reaction on an issue's first comment. Because Flutter is an open source project, we invite contributions both towards the themes presented below and in other areas. -_If you are a contributor or team of contributors with long-term plans for [contributing to Flutter](../../CONTRIBUTING.md), and would like your planned efforts reflected in the roadmap, please reach out to Hixie (ian@hixie.ch)._ +_If you are a contributor or team of contributors with long-term plans for [contributing to Flutter](../../CONTRIBUTING.md), and would like your planned efforts reflected in the roadmap, please reach via email to `roadmap-input@flutter.dev`._ # 2025 diff --git a/engine/src/flutter/ci/licenses_golden/licenses_dart b/engine/src/flutter/ci/licenses_golden/licenses_dart index 6680f277058d8..df73f9d8f9d4e 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_dart +++ b/engine/src/flutter/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: 7582ce3fcdf2e515db0132d7c32bf1b3 +Signature: c96f6439e1d3cadfbb6adc790efdcc37 ==================================================================================================== LIBRARY: dart @@ -4862,7 +4862,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/4293d50dd30d5469fac05af7470148977327bbe8 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/0d6811928830b87e36a0f49eb7fe554c308d3699 /third_party/fallback_root_certificates/ ==================================================================================================== diff --git a/engine/src/flutter/ci/licenses_golden/licenses_skia b/engine/src/flutter/ci/licenses_golden/licenses_skia index 935f8d33d225e..63ffe75d7de4a 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_skia +++ b/engine/src/flutter/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: cc16851918508797c4c52f81aeae260a +Signature: 6385fd7cd2f7be2e7ceee9d4b05eaf04 ==================================================================================================== LIBRARY: etc1 diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc index 2f9090e2bed19..1767eaa82604e 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc @@ -938,6 +938,7 @@ TEST_P(AiksTest, ImageColorSourceEffectTransform) { // Scale { + builder.Save(); builder.Translate(100, 0); builder.Scale(100, 100); DlPaint paint; @@ -948,6 +949,23 @@ TEST_P(AiksTest, ImageColorSourceEffectTransform) { DlImageSampling::kNearestNeighbor, &matrix)); builder.DrawRect(DlRect::MakeLTRB(0, 0, 1, 1), paint); + builder.Restore(); + } + + // Perspective + { + builder.Save(); + builder.Translate(150, 150); + DlPaint paint; + + DlMatrix matrix = + DlMatrix::MakePerspective(Radians{0.5}, ISize{200, 200}, 0.05, 1); + paint.setColorSource(DlColorSource::MakeImage( + texture, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &matrix)); + + builder.DrawRect(DlRect::MakeLTRB(0, 0, 200, 200), paint); + builder.Restore(); } ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_blur_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_blur_unittests.cc index c7d67aa4e0f35..04243ff599ecb 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_blur_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_blur_unittests.cc @@ -1253,57 +1253,6 @@ TEST_P(AiksTest, BlurredRectangleWithShader) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } -TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) { - if (GetParam() != PlaygroundBackend::kMetal) { - GTEST_SKIP() - << "This backend doesn't yet support setting device capabilities."; - } - if (!WillRenderSomething()) { - // Sometimes these tests are run without playgrounds enabled which is - // pointless for this test since we are asserting that - // `SupportsDecalSamplerAddressMode` is called. - GTEST_SKIP() << "This test requires playgrounds."; - } - - std::shared_ptr old_capabilities = - GetContext()->GetCapabilities(); - auto mock_capabilities = std::make_shared(); - EXPECT_CALL(*mock_capabilities, SupportsDecalSamplerAddressMode()) - .Times(::testing::AtLeast(1)) - .WillRepeatedly(::testing::Return(false)); - FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat); - FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat); - FLT_FORWARD(mock_capabilities, old_capabilities, - GetDefaultDepthStencilFormat); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA); - FLT_FORWARD(mock_capabilities, old_capabilities, - SupportsImplicitResolvingMSAA); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsFramebufferFetch); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute); - FLT_FORWARD(mock_capabilities, old_capabilities, - SupportsTextureToTextureBlits); - FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultGlyphAtlasFormat); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsTriangleFan); - FLT_FORWARD(mock_capabilities, old_capabilities, SupportsPrimitiveRestart); - ASSERT_TRUE(SetCapabilities(mock_capabilities).ok()); - - auto texture = DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")); - - DisplayListBuilder builder; - builder.Scale(GetContentScale().x * 0.5, GetContentScale().y * 0.5); - - DlPaint paint; - paint.setColor(DlColor::kBlack()); - builder.DrawPaint(paint); - - auto blur_filter = DlImageFilter::MakeBlur(20, 20, DlTileMode::kDecal); - paint.setImageFilter(blur_filter); - builder.DrawImage(texture, DlPoint(200, 200), {}, &paint); - ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); -} - // This addresses a bug where tiny blurs could result in mip maps that beyond // the limits for the textures used for blurring. // See also: b/323402168 diff --git a/engine/src/flutter/impeller/display_list/paint.cc b/engine/src/flutter/impeller/display_list/paint.cc index ef99300258392..93d8d694fa024 100644 --- a/engine/src/flutter/impeller/display_list/paint.cc +++ b/engine/src/flutter/impeller/display_list/paint.cc @@ -197,7 +197,8 @@ std::shared_ptr Paint::CreateContents() const { image_color_source->vertical_tile_mode()); auto sampler_descriptor = skia_conversions::ToSamplerDescriptor(image_color_source->sampling()); - auto effect_transform = image_color_source->matrix(); + // See https://github.com/flutter/flutter/issues/165205 + flutter::DlMatrix effect_transform = image_color_source->matrix().To3x3(); auto contents = std::make_shared(); contents->SetOpacityFactor(color.alpha); diff --git a/engine/src/flutter/impeller/geometry/matrix.h b/engine/src/flutter/impeller/geometry/matrix.h index fb4dc95e86d8b..eb032b292ba0e 100644 --- a/engine/src/flutter/impeller/geometry/matrix.h +++ b/engine/src/flutter/impeller/geometry/matrix.h @@ -247,6 +247,19 @@ struct Matrix { // clang-format on } + // Converts the second row/col to identity to make this an equivalent + // to a Skia 3x3 Matrix. + constexpr Matrix To3x3() const { + // clang-format off + return Matrix( + m[0], m[1], 0, m[3], + m[4], m[5], 0, m[7], + 0, 0, 1, 0, + m[12], m[13], 0, m[15] + ); + // clang-format on + } + constexpr Matrix Translate(const Vector3& t) const { // clang-format off return Matrix(m[0], m[1], m[2], m[3], diff --git a/engine/src/flutter/impeller/geometry/matrix_unittests.cc b/engine/src/flutter/impeller/geometry/matrix_unittests.cc index 6c2ef35228a2a..719affb438b8c 100644 --- a/engine/src/flutter/impeller/geometry/matrix_unittests.cc +++ b/engine/src/flutter/impeller/geometry/matrix_unittests.cc @@ -313,5 +313,14 @@ TEST(MatrixTest, MakeScaleTranslate) { Matrix::MakeTranslation({0, 0, 0}) * Matrix::MakeScale({0, 0, 0}))); } +TEST(MatrixTest, To3x3) { + Matrix x(1.0, 0.0, 4.0, 0.0, // + 0.0, 1.0, 4.0, 0.0, // + 6.0, 5.0, 111.0, 7.0, // + 0.0, 0.0, 9.0, 1.0); + + EXPECT_TRUE(MatrixNear(x.To3x3(), Matrix())); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc index f66bbab100b26..9bd546340a841 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc @@ -5,6 +5,7 @@ #include "impeller/renderer/backend/vulkan/allocator_vk.h" #include +#include #include "flutter/fml/memory/ref_ptr.h" #include "flutter/fml/trace_event.h" @@ -12,6 +13,7 @@ #include "impeller/core/formats.h" #include "impeller/renderer/backend/vulkan/capabilities_vk.h" #include "impeller/renderer/backend/vulkan/device_buffer_vk.h" +#include "impeller/renderer/backend/vulkan/device_holder_vk.h" #include "impeller/renderer/backend/vulkan/formats_vk.h" #include "impeller/renderer/backend/vulkan/texture_vk.h" #include "vulkan/vulkan_enums.hpp" @@ -405,9 +407,10 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { return; } - resource_.Swap(ImageResource(ImageVMA{allocator, allocation, image}, - std::move(image_view), - std::move(rt_image_view))); + resource_.Swap(ImageResource( + ImageVMA{allocator, allocation, image}, std::move(image_view), + std::move(rt_image_view), context.GetResourceAllocator(), + context.GetDeviceHolder())); is_valid_ = true; } @@ -429,6 +432,8 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { private: struct ImageResource { + std::shared_ptr device_holder; + std::shared_ptr allocator; UniqueImageVMA image; vk::UniqueImageView image_view; vk::UniqueImageView rt_image_view; @@ -437,8 +442,12 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { ImageResource(ImageVMA p_image, vk::UniqueImageView p_image_view, - vk::UniqueImageView p_rt_image_view) - : image(p_image), + vk::UniqueImageView p_rt_image_view, + std::shared_ptr allocator, + std::shared_ptr device_holder) + : device_holder(std::move(device_holder)), + allocator(std::move(allocator)), + image(p_image), image_view(std::move(p_image_view)), rt_image_view(std::move(p_rt_image_view)) {} diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc index 94cb0dec5ba13..0cf8930207215 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc @@ -5,6 +5,7 @@ #include "flutter/testing/testing.h" // IWYU pragma: keep #include "gtest/gtest.h" #include "impeller/base/allocation_size.h" +#include "impeller/core/allocator.h" #include "impeller/core/device_buffer.h" #include "impeller/core/device_buffer_descriptor.h" #include "impeller/core/formats.h" @@ -73,6 +74,25 @@ TEST(AllocatorVKTest, MemoryTypeSelectionTwoHeap) { EXPECT_EQ(AllocatorVK::FindMemoryTypeIndex(4, properties), -1); } +TEST(AllocatorVKTest, ImageResourceKeepsVulkanDeviceAlive) { + std::shared_ptr texture; + std::weak_ptr weak_allocator; + { + auto const context = MockVulkanContextBuilder().Build(); + weak_allocator = context->GetResourceAllocator(); + auto allocator = context->GetResourceAllocator(); + + texture = allocator->CreateTexture(TextureDescriptor{ + .storage_mode = StorageMode::kDevicePrivate, + .format = PixelFormat::kR8G8B8A8UNormInt, + .size = {1, 1}, + }); + context->Shutdown(); + } + + ASSERT_TRUE(weak_allocator.lock()); +} + #ifdef IMPELLER_DEBUG TEST(AllocatorVKTest, RecreateSwapchainWhenSizeChanges) { diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.h index d1498d0ba2505..77c78be1605cb 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.h @@ -12,7 +12,6 @@ #include "impeller/renderer/backend/vulkan/shared_object_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/backend/vulkan/yuv_conversion_vk.h" -#include "vulkan/vulkan_handles.hpp" namespace impeller { diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart index c5fb4cf2025d8..90814fc004234 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:ui/src/engine.dart'; @@ -9,20 +10,10 @@ import 'package:ui/ui.dart' as ui; /// Implements vertical and horizontal scrolling functionality for semantics /// objects. /// -/// Scrolling is implemented using a "joystick" method. The absolute value of -/// "scrollTop" in HTML is not important. We only need to know in whether the -/// value changed in the positive or negative direction. If it changes in the -/// positive direction we send a [ui.SemanticsAction.scrollUp]. Otherwise, we -/// send [ui.SemanticsAction.scrollDown]. The actual scrolling is then handled -/// by the framework and we receive a [ui.SemanticsUpdate] containing the new -/// [scrollPosition] and child positions. -/// -/// "scrollTop" or "scrollLeft" is always reset to an arbitrarily chosen non- -/// zero "neutral" scroll position value. This is done so we have a -/// predictable range of DOM scroll position values. When the amount of -/// contents is less than the size of the viewport the browser snaps -/// "scrollTop" back to zero. If there is more content than available in the -/// viewport "scrollTop" may take positive values. +/// Scrolling is controlled by sending the current DOM scroll position in a +/// [ui.SemanticsAction.scrollToOffset] to the framework where it applies the +/// value to its scrollable and the engine receives a [ui.SemanticsUpdate] +/// containing the new [SemanticsObject.scrollPosition] and child positions. class SemanticScrollable extends SemanticRole { SemanticScrollable(SemanticsObject semanticsObject) : super.withBasics( @@ -39,81 +30,61 @@ class SemanticScrollable extends SemanticRole { /// Disables browser-driven scrolling in the presence of pointer events. GestureModeCallback? _gestureModeListener; - /// DOM element used as a workaround for: https://github.com/flutter/flutter/issues/104036 - /// - /// When the assistive technology gets to the last element of the scrollable - /// list, the browser thinks the scrollable area doesn't have any more content, - /// so it overrides the value of "scrollTop"/"scrollLeft" with zero. As a result, - /// the user can't scroll back up/left. - /// - /// As a workaround, we add this DOM element and set its size to - /// [canonicalNeutralScrollPosition] so the browser believes - /// that the scrollable area still has some more content, and doesn't override - /// scrollTop/scrollLetf with zero. + /// DOM element used to indicate to the browser the total quantity of available + /// content under this scrollable area. This element is sized based on the + /// total scroll extent calculated by scrollExtentMax - scrollExtentMin + rect.height + /// of the [SemanticsObject] managed by this scrollable. final DomElement _scrollOverflowElement = createDomElement('flt-semantics-scroll-overflow'); /// Listens to HTML "scroll" gestures detected by the browser. /// - /// This gesture is converted to [ui.SemanticsAction.scrollUp] or - /// [ui.SemanticsAction.scrollDown], depending on the direction. + /// When the browser detects a "scroll" gesture we send the updated DOM scroll position + /// to the framework in a [ui.SemanticsAction.scrollToOffset]. @visibleForTesting DomEventListener? scrollListener; - /// The value of the "scrollTop" or "scrollLeft" property of this object's - /// [element] that has zero offset relative to the [scrollPosition]. - int _effectiveNeutralScrollPosition = 0; - /// Whether this scrollable can scroll vertically or horizontally. bool get _canScroll => semanticsObject.isVerticalScrollContainer || semanticsObject.isHorizontalScrollContainer; + /// The previous value of the "scrollTop" or "scrollLeft" property of this object's + /// [element], used to determine if the content was scrolled. + int _previousDomScrollPosition = 0; + /// Responds to browser-detected "scroll" gestures. void _recomputeScrollPosition() { - if (_domScrollPosition != _effectiveNeutralScrollPosition) { + if (_domScrollPosition != _previousDomScrollPosition) { if (!EngineSemantics.instance.shouldAcceptBrowserGesture('scroll')) { return; } - final bool doScrollForward = _domScrollPosition > _effectiveNeutralScrollPosition; - _neutralizeDomScrollPosition(); + + _previousDomScrollPosition = _domScrollPosition; + _updateScrollableState(); semanticsObject.recomputePositionAndSize(); semanticsObject.updateChildrenPositionAndSize(); final int semanticsId = semanticsObject.id; - if (doScrollForward) { - if (semanticsObject.isVerticalScrollContainer) { - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - viewId, - semanticsId, - ui.SemanticsAction.scrollUp, - null, - ); - } else { - assert(semanticsObject.isHorizontalScrollContainer); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - viewId, - semanticsId, - ui.SemanticsAction.scrollLeft, - null, - ); - } + final Float64List offsets = Float64List(2); + + // Either SemanticsObject.isVerticalScrollContainer or + // SemanticsObject.isHorizontalScrollContainer should be + // true otherwise scrollToOffset cannot be called. + if (semanticsObject.isVerticalScrollContainer) { + offsets[0] = 0.0; + offsets[1] = element.scrollTop; } else { - if (semanticsObject.isVerticalScrollContainer) { - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - viewId, - semanticsId, - ui.SemanticsAction.scrollDown, - null, - ); - } else { - assert(semanticsObject.isHorizontalScrollContainer); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( - viewId, - semanticsId, - ui.SemanticsAction.scrollRight, - null, - ); - } + assert(semanticsObject.isHorizontalScrollContainer); + offsets[0] = element.scrollLeft; + offsets[1] = 0.0; } + + final ByteData? message = const StandardMessageCodec().encodeMessage(offsets); + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + viewId, + semanticsId, + ui.SemanticsAction.scrollToOffset, + message, + ); } } @@ -122,6 +93,22 @@ class SemanticScrollable extends SemanticRole { // Scrolling is controlled by setting overflow-y/overflow-x to 'scroll`. The // default overflow = "visible" needs to be unset. semanticsObject.element.style.overflow = ''; + // On macOS the scrollbar behavior which can be set in the settings application + // may sometimes insert scrollbars into an application when a peripheral like a + // mouse or keyboard is plugged in. This causes the clientHeight or clientWidth + // of the scrollable DOM element to be offset by the width of the scrollbar. + // This causes issues in the vertical scrolling context because the max scroll + // extent is calculated by the element's scrollHeight - clientHeight, so when + // the clientHeight is offset by scrollbar width the browser may there is + // a greater scroll extent then what is actually available. + // + // The scrollbar is already made transparent in SemanticsRole._initElement so here + // set scrollbar-width to "none" to prevent it from affecting the max scroll extent. + // + // Support for scrollbar-width was only added to Safari v18.2+, so versions before + // that may still experience overscroll issues when macOS inserts scrollbars + // into the application. + semanticsObject.element.style.scrollbarWidth = 'none'; _scrollOverflowElement.style ..position = 'absolute' @@ -136,7 +123,15 @@ class SemanticScrollable extends SemanticRole { super.update(); semanticsObject.owner.addOneTimePostUpdateCallback(() { - _neutralizeDomScrollPosition(); + if (_canScroll) { + final double? scrollPosition = semanticsObject.scrollPosition; + assert(scrollPosition != null); + if (scrollPosition != _domScrollPosition) { + element.scrollTop = scrollPosition!; + _previousDomScrollPosition = _domScrollPosition; + } + } + _updateScrollableState(); semanticsObject.recomputePositionAndSize(); semanticsObject.updateChildrenPositionAndSize(); }); @@ -183,56 +178,38 @@ class SemanticScrollable extends SemanticRole { } } - /// Resets the scroll position (top or left) to the neutral value. - /// - /// The scroll position of the scrollable HTML node that's considered to - /// have zero offset relative to Flutter's notion of scroll position is - /// referred to as "neutral scroll position". - /// - /// We always set the scroll position to a non-zero value in order to - /// be able to scroll in the negative direction. When scrollTop/scrollLeft is - /// zero the browser will refuse to scroll back even when there is more - /// content available. - void _neutralizeDomScrollPosition() { + void _updateScrollableState() { // This value is arbitrary. - const int canonicalNeutralScrollPosition = 10; final ui.Rect? rect = semanticsObject.rect; if (rect == null) { printWarning('Warning! the rect attribute of semanticsObject is null'); return; } + final double? scrollExtentMax = semanticsObject.scrollExtentMax; + final double? scrollExtentMin = semanticsObject.scrollExtentMin; + assert(scrollExtentMax != null); + assert(scrollExtentMin != null); + final double scrollExtentTotal = + scrollExtentMax! - + scrollExtentMin! + + (semanticsObject.isVerticalScrollContainer ? rect.height : rect.width); + // Place the _scrollOverflowElement at the beginning of the content + // and size it based on the total scroll extent so the browser + // knows how much scrollable content there is. if (semanticsObject.isVerticalScrollContainer) { - // Place the _scrollOverflowElement at the end of the content and - // make sure that when we neutralize the scrolling position, - // it doesn't scroll into the visible area. - final int verticalOffset = rect.height.ceil() + canonicalNeutralScrollPosition; _scrollOverflowElement.style - ..transform = 'translate(0px,${verticalOffset}px)' - ..width = '${rect.width.round()}px' - ..height = '${canonicalNeutralScrollPosition}px'; - - element.scrollTop = canonicalNeutralScrollPosition.toDouble(); - // Read back because the effective value depends on the amount of content. - _effectiveNeutralScrollPosition = element.scrollTop.toInt(); + ..width = '0px' + ..height = '${scrollExtentTotal.toStringAsFixed(1)}px'; semanticsObject - ..verticalScrollAdjustment = _effectiveNeutralScrollPosition.toDouble() + ..verticalScrollAdjustment = element.scrollTop ..horizontalScrollAdjustment = 0.0; } else if (semanticsObject.isHorizontalScrollContainer) { - // Place the _scrollOverflowElement at the end of the content and - // make sure that when we neutralize the scrolling position, - // it doesn't scroll into the visible area. - final int horizontalOffset = rect.width.ceil() + canonicalNeutralScrollPosition; _scrollOverflowElement.style - ..transform = 'translate(${horizontalOffset}px,0px)' - ..width = '${canonicalNeutralScrollPosition}px' - ..height = '${rect.height.round()}px'; - - element.scrollLeft = canonicalNeutralScrollPosition.toDouble(); - // Read back because the effective value depends on the amount of content. - _effectiveNeutralScrollPosition = element.scrollLeft.toInt(); + ..width = '${scrollExtentTotal.toStringAsFixed(1)}px' + ..height = '0px'; semanticsObject ..verticalScrollAdjustment = 0.0 - ..horizontalScrollAdjustment = _effectiveNeutralScrollPosition.toDouble(); + ..horizontalScrollAdjustment = element.scrollLeft; } else { _scrollOverflowElement.style ..transform = 'translate(0px,0px)' @@ -240,7 +217,6 @@ class SemanticScrollable extends SemanticRole { ..height = '0px'; element.scrollLeft = 0.0; element.scrollTop = 0.0; - _effectiveNeutralScrollPosition = 0; semanticsObject ..verticalScrollAdjustment = 0.0 ..horizontalScrollAdjustment = 0.0; diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart index 10420cc21e2db..f1b153b9f2ace 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -1612,7 +1612,7 @@ void _testVerticalScrolling() { '''); final DomElement scrollable = findScrollable(owner()); - expect(scrollable.scrollTop, isPositive); + expect(scrollable.scrollTop, 0); semantics().semanticsEnabled = false; }); @@ -1649,8 +1649,8 @@ void _testVerticalScrolling() { expect(scrollable, isNotNull); // When there's less content than the available size the neutral scrollTop - // is still a positive number. - expect(scrollable.scrollTop, isPositive); + // is 0. + expect(scrollable.scrollTop, 0); semantics().semanticsEnabled = false; }); @@ -1703,18 +1703,7 @@ void _testVerticalScrolling() { final DomElement scrollable = owner().debugSemanticsTree![0]!.element; expect(scrollable, isNotNull); - - // When there's more content than the available size the neutral scrollTop - // is greater than 0 with a maximum of 10 or 9. - int browserMaxScrollDiff = 0; - // The max scroll value varies between `9` and `10` for Safari desktop - // browsers. - if (ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit && - ui_web.browser.operatingSystem == ui_web.OperatingSystem.macOs) { - browserMaxScrollDiff = 1; - } - - expect(scrollable.scrollTop >= (10 - browserMaxScrollDiff), isTrue); + expect(scrollable.scrollTop, 0); Future capturedEventFuture = captureSemanticsEvent(); scrollable.scrollTop = 20; @@ -1722,21 +1711,44 @@ void _testVerticalScrolling() { ui.SemanticsActionEvent capturedEvent = await capturedEventFuture; expect(capturedEvent.nodeId, 0); - expect(capturedEvent.type, ui.SemanticsAction.scrollUp); - expect(capturedEvent.arguments, isNull); - // Engine semantics returns scroll top back to neutral. - expect(scrollable.scrollTop >= (10 - browserMaxScrollDiff), isTrue); + expect(capturedEvent.type, ui.SemanticsAction.scrollToOffset); + expect(capturedEvent.arguments, isNotNull); + final Float64List expectedOffset = Float64List(2); + expectedOffset[0] = 0.0; + expectedOffset[1] = 20.0; + Float64List message = + const StandardMessageCodec().decodeMessage(capturedEvent.arguments! as ByteData) + as Float64List; + expect(message, expectedOffset); + + // Update scrollPosition to scrollTop value. + final ui.SemanticsUpdateBuilder builder2 = ui.SemanticsUpdateBuilder(); + updateNode( + builder2, + scrollPosition: 20.0, + flags: 0 | ui.SemanticsFlag.hasImplicitScrolling.index, + actions: 0 | ui.SemanticsAction.scrollUp.index | ui.SemanticsAction.scrollDown.index, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(0, 0, 50, 100), + childrenInHitTestOrder: Int32List.fromList([1, 2, 3]), + childrenInTraversalOrder: Int32List.fromList([1, 2, 3]), + ); + owner().updateSemantics(builder2.build()); capturedEventFuture = captureSemanticsEvent(); scrollable.scrollTop = 5; capturedEvent = await capturedEventFuture; - expect(scrollable.scrollTop >= (5 - browserMaxScrollDiff), isTrue); + expect(scrollable.scrollTop, 5); expect(capturedEvent.nodeId, 0); - expect(capturedEvent.type, ui.SemanticsAction.scrollDown); - expect(capturedEvent.arguments, isNull); - // Engine semantics returns scroll top back to neutral. - expect(scrollable.scrollTop >= (10 - browserMaxScrollDiff), isTrue); + expect(capturedEvent.type, ui.SemanticsAction.scrollToOffset); + expect(capturedEvent.arguments, isNotNull); + expectedOffset[0] = 0.0; + expectedOffset[1] = 5.0; + message = + const StandardMessageCodec().decodeMessage(capturedEvent.arguments! as ByteData) + as Float64List; + expect(message, expectedOffset); }); test('scrollable switches to pointer event mode on a wheel event', () async { @@ -1783,27 +1795,22 @@ void _testVerticalScrolling() { final DomElement scrollable = owner().debugSemanticsTree![0]!.element; expect(scrollable, isNotNull); - void expectNeutralPosition() { - // Browsers disagree on the exact value, but it's always close to 10. - expect((scrollable.scrollTop - 10).abs(), lessThan(2)); - } - - // Initially, starting with a neutral scroll position, everything should be + // Initially, starting at "scrollTop" 0, everything should be // in browser gesture mode, react to DOM scroll events, and generate // semantic actions. - expectNeutralPosition(); + expect(scrollable.scrollTop, 0); expect(semantics().gestureMode, GestureMode.browserGestures); scrollable.scrollTop = 20; expect(scrollable.scrollTop, 20); await Future.delayed(const Duration(milliseconds: 100)); expect(actionLog, hasLength(1)); final capturedEvent = actionLog.removeLast(); - expect(capturedEvent.type, ui.SemanticsAction.scrollUp); + expect(capturedEvent.type, ui.SemanticsAction.scrollToOffset); - // Now, starting with a neutral mode, observing a DOM "wheel" event should + // Now, starting at the "scrollTop" 20 we set, observing a DOM "wheel" event should // swap into pointer event mode, and the scrollable becomes a plain clip, // i.e. `overflow: hidden`. - expectNeutralPosition(); + expect(scrollable.scrollTop, 20); expect(semantics().gestureMode, GestureMode.browserGestures); expect(scrollable.style.overflowY, 'scroll'); @@ -1870,8 +1877,8 @@ void _testHorizontalScrolling() { expect(scrollable, isNotNull); // When there's less content than the available size the neutral - // scrollLeft is still a positive number. - expect(scrollable.scrollLeft, isPositive); + // scrollLeft is still 0. + expect(scrollable.scrollLeft, 0); semantics().semanticsEnabled = false; }); @@ -1924,17 +1931,7 @@ void _testHorizontalScrolling() { final DomElement scrollable = findScrollable(owner()); expect(scrollable, isNotNull); - - // When there's more content than the available size the neutral scrollTop - // is greater than 0 with a maximum of 10. - int browserMaxScrollDiff = 0; - // The max scroll value varies between `9` and `10` for Safari desktop - // browsers. - if (ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit && - ui_web.browser.operatingSystem == ui_web.OperatingSystem.macOs) { - browserMaxScrollDiff = 1; - } - expect(scrollable.scrollLeft >= (10 - browserMaxScrollDiff), isTrue); + expect(scrollable.scrollLeft, 0); Future capturedEventFuture = captureSemanticsEvent(); scrollable.scrollLeft = 20; @@ -1942,21 +1939,44 @@ void _testHorizontalScrolling() { ui.SemanticsActionEvent capturedEvent = await capturedEventFuture; expect(capturedEvent.nodeId, 0); - expect(capturedEvent.type, ui.SemanticsAction.scrollLeft); - expect(capturedEvent.arguments, isNull); - // Engine semantics returns scroll position back to neutral. - expect(scrollable.scrollLeft >= (10 - browserMaxScrollDiff), isTrue); + expect(capturedEvent.type, ui.SemanticsAction.scrollToOffset); + expect(capturedEvent.arguments, isNotNull); + final Float64List expectedOffset = Float64List(2); + expectedOffset[0] = 20.0; + expectedOffset[1] = 0.0; + Float64List message = + const StandardMessageCodec().decodeMessage(capturedEvent.arguments! as ByteData) + as Float64List; + expect(message, expectedOffset); + + // Update scrollPosition to scrollLeft value. + final ui.SemanticsUpdateBuilder builder2 = ui.SemanticsUpdateBuilder(); + updateNode( + builder2, + scrollPosition: 20.0, + flags: 0 | ui.SemanticsFlag.hasImplicitScrolling.index, + actions: 0 | ui.SemanticsAction.scrollLeft.index | ui.SemanticsAction.scrollRight.index, + transform: Matrix4.identity().toFloat64(), + rect: const ui.Rect.fromLTRB(0, 0, 50, 100), + childrenInHitTestOrder: Int32List.fromList([1, 2, 3]), + childrenInTraversalOrder: Int32List.fromList([1, 2, 3]), + ); + owner().updateSemantics(builder2.build()); capturedEventFuture = captureSemanticsEvent(); scrollable.scrollLeft = 5; capturedEvent = await capturedEventFuture; - expect(scrollable.scrollLeft >= (5 - browserMaxScrollDiff), isTrue); + expect(scrollable.scrollLeft, 5); expect(capturedEvent.nodeId, 0); - expect(capturedEvent.type, ui.SemanticsAction.scrollRight); - expect(capturedEvent.arguments, isNull); - // Engine semantics returns scroll top back to neutral. - expect(scrollable.scrollLeft >= (10 - browserMaxScrollDiff), isTrue); + expect(capturedEvent.type, ui.SemanticsAction.scrollToOffset); + expect(capturedEvent.arguments, isNotNull); + expectedOffset[0] = 5.0; + expectedOffset[1] = 0.0; + message = + const StandardMessageCodec().decodeMessage(capturedEvent.arguments! as ByteData) + as Float64List; + expect(message, expectedOffset); }); } diff --git a/engine/src/flutter/pubspec.yaml b/engine/src/flutter/pubspec.yaml index 5b105e8e08079..b6619a1363618 100644 --- a/engine/src/flutter/pubspec.yaml +++ b/engine/src/flutter/pubspec.yaml @@ -166,8 +166,6 @@ dependency_overrides: path: ./third_party/pkg/googleapis/discoveryapis_commons _fe_analyzer_shared: path: ./third_party/dart/pkg/_fe_analyzer_shared - _macros: - path: ./third_party/dart/pkg/_macros analyzer: path: ./third_party/dart/pkg/analyzer archive: @@ -222,8 +220,6 @@ dependency_overrides: path: ./third_party/dart/pkg/kernel logging: path: ./third_party/dart/third_party/pkg/core/pkgs/logging - macros: - path: ./third_party/dart/pkg/macros matcher: path: ./third_party/dart/third_party/pkg/test/pkgs/matcher meta: diff --git a/engine/src/flutter/web_sdk/pubspec.yaml b/engine/src/flutter/web_sdk/pubspec.yaml index fb1def8b2a90a..d46d1bd38ea47 100644 --- a/engine/src/flutter/web_sdk/pubspec.yaml +++ b/engine/src/flutter/web_sdk/pubspec.yaml @@ -18,8 +18,6 @@ dev_dependencies: dependency_overrides: # Must include all transitive dependencies from the "any" packages above. _fe_analyzer_shared: path: ../third_party/dart/pkg/_fe_analyzer_shared - _macros: - path: ../third_party/dart/pkg/_macros analyzer: path: ../third_party/dart/pkg/analyzer args: @@ -38,8 +36,6 @@ dependency_overrides: # Must include all transitive dependencies from the "any" path: ../third_party/dart/third_party/pkg/tools/pkgs/file glob: path: ../third_party/dart/third_party/pkg/tools/pkgs/glob - macros: - path: ../third_party/dart/pkg/macros meta: path: ../third_party/dart/pkg/meta package_config: diff --git a/examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart b/examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart new file mode 100644 index 0000000000000..defdb691c2347 --- /dev/null +++ b/examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart @@ -0,0 +1,207 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +/// Flutter code sample for [SliverEnsureSemantics]. + +void main() => runApp(const SliverEnsureSemanticsExampleApp()); + +class SliverEnsureSemanticsExampleApp extends StatelessWidget { + const SliverEnsureSemanticsExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp(home: SliverEnsureSemanticsExample()); + } +} + +class SliverEnsureSemanticsExample extends StatefulWidget { + const SliverEnsureSemanticsExample({super.key}); + + @override + State createState() => _SliverEnsureSemanticsExampleState(); +} + +class _SliverEnsureSemanticsExampleState extends State { + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + backgroundColor: theme.colorScheme.inversePrimary, + title: const Text('SliverEnsureSemantics Demo'), + ), + body: Center( + child: CustomScrollView( + semanticChildCount: 106, + slivers: [ + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 0, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Semantics( + header: true, + headingLevel: 3, + child: Text('Steps to reproduce', style: theme.textTheme.headlineSmall), + ), + const Text('Issue description'), + Semantics( + header: true, + headingLevel: 3, + child: Text('Expected Results', style: theme.textTheme.headlineSmall), + ), + Semantics( + header: true, + headingLevel: 3, + child: Text('Actual Results', style: theme.textTheme.headlineSmall), + ), + Semantics( + header: true, + headingLevel: 3, + child: Text('Code Sample', style: theme.textTheme.headlineSmall), + ), + Semantics( + header: true, + headingLevel: 3, + child: Text('Screenshots', style: theme.textTheme.headlineSmall), + ), + Semantics( + header: true, + headingLevel: 3, + child: Text('Logs', style: theme.textTheme.headlineSmall), + ), + ], + ), + ), + ), + ), + ), + ), + SliverFixedExtentList( + itemExtent: 44.0, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return Card( + child: Padding(padding: const EdgeInsets.all(8.0), child: Text('Item $index')), + ); + }, + childCount: 50, + semanticIndexOffset: 1, + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 51, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Semantics(header: true, child: const Text('Footer 1')), + ), + ), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 52, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Semantics(header: true, child: const Text('Footer 2')), + ), + ), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 53, + child: Semantics(link: true, child: const Text('Link #1')), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 54, + child: OverflowBar( + children: [ + TextButton(onPressed: () {}, child: const Text('Button 1')), + TextButton(onPressed: () {}, child: const Text('Button 2')), + ], + ), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 55, + child: Semantics(link: true, child: const Text('Link #2')), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverSemanticsList( + sliver: SliverFixedExtentList( + itemExtent: 44.0, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return Semantics( + role: SemanticsRole.listItem, + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Second List Item $index'), + ), + ), + ); + }, + childCount: 50, + semanticIndexOffset: 56, + ), + ), + ), + ), + SliverEnsureSemantics( + sliver: SliverToBoxAdapter( + child: IndexedSemantics( + index: 107, + child: Semantics(link: true, child: const Text('Link #3')), + ), + ), + ), + ], + ), + ), + ); + } +} + +// A sliver that assigns the role of SemanticsRole.list to its sliver child. +class SliverSemanticsList extends SingleChildRenderObjectWidget { + const SliverSemanticsList({super.key, required Widget sliver}) : super(child: sliver); + + @override + RenderSliverSemanticsList createRenderObject(BuildContext context) => RenderSliverSemanticsList(); +} + +class RenderSliverSemanticsList extends RenderProxySliver { + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.role = SemanticsRole.list; + } +} diff --git a/examples/api/test/widgets/sliver/sliver_ensure_semantics.0_test.dart b/examples/api/test/widgets/sliver/sliver_ensure_semantics.0_test.dart new file mode 100644 index 0000000000000..651d98d6f8dd8 --- /dev/null +++ b/examples/api/test/widgets/sliver/sliver_ensure_semantics.0_test.dart @@ -0,0 +1,14 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_api_samples/widgets/sliver/sliver_ensure_semantics.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('SliverEnsureSemantics example', (WidgetTester tester) async { + await tester.pumpWidget(const example.SliverEnsureSemanticsExampleApp()); + + expect(find.text('SliverEnsureSemantics Demo'), findsOneWidget); + }); +} diff --git a/packages/flutter/lib/src/material/carousel.dart b/packages/flutter/lib/src/material/carousel.dart index 4e7fcbd58cfff..1a36f50809243 100644 --- a/packages/flutter/lib/src/material/carousel.dart +++ b/packages/flutter/lib/src/material/carousel.dart @@ -107,6 +107,15 @@ import 'theme.dart'; /// Here is an example to show different carousel layouts that [CarouselView] /// and [CarouselView.weighted] can build. /// +/// On desktop and web running on desktop platforms, dragging to scroll with a mouse +/// is disabled by default to align with natural behavior. +/// +/// To further align expected behavior like this, mouse input can scroll horizontally +/// by pressing the shift key while scrolling with the mouse wheel. +/// +/// This key-driven behavior is dictated by the [ScrollBehavior.pointerAxisModifiers], +/// while [ScrollBehavior.dragDevices] manages what devices can drag a scrollable. +/// /// ** See code in examples/api/lib/material/carousel/carousel.0.dart ** /// {@end-tool} /// diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index e2c9ee21c1f89..7cdeef8db9fc1 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -393,7 +393,7 @@ class ListTile extends StatelessWidget { this.title, this.subtitle, this.trailing, - this.isThreeLine = false, + this.isThreeLine, this.dense, this.visualDensity, this.shape, @@ -425,7 +425,7 @@ class ListTile extends StatelessWidget { this.minTileHeight, this.titleAlignment, this.internalAddSemanticForOnTap = true, - }) : assert(!isThreeLine || subtitle != null); + }) : assert(isThreeLine != true || subtitle != null); /// A widget to display before the title. /// @@ -482,7 +482,12 @@ class ListTile extends StatelessWidget { /// /// When using a [Text] widget for [title] and [subtitle], you can enforce /// line limits using [Text.maxLines]. - final bool isThreeLine; + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final bool? isThreeLine; /// {@template flutter.material.ListTile.dense} /// Whether this list tile is part of a vertically dense list. @@ -987,7 +992,11 @@ class ListTile extends StatelessWidget { trailing: trailingIcon, isDense: _isDenseLayout(theme, tileTheme), visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity, - isThreeLine: isThreeLine, + isThreeLine: + isThreeLine ?? + tileTheme.isThreeLine ?? + theme.listTileTheme.isThreeLine ?? + false, textDirection: textDirection, titleBaselineType: titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!, @@ -1021,7 +1030,6 @@ class ListTile extends StatelessWidget { ifTrue: 'THREE_LINE', ifFalse: 'TWO_LINE', showName: true, - defaultValue: false, ), ); properties.add( diff --git a/packages/flutter/lib/src/material/list_tile_theme.dart b/packages/flutter/lib/src/material/list_tile_theme.dart index 3dd6ce9d6c1e7..03b1d6570ec27 100644 --- a/packages/flutter/lib/src/material/list_tile_theme.dart +++ b/packages/flutter/lib/src/material/list_tile_theme.dart @@ -73,6 +73,7 @@ class ListTileThemeData with Diagnosticable { this.minTileHeight, this.titleAlignment, this.controlAffinity, + this.isThreeLine, }); /// Overrides the default value of [ListTile.dense]. @@ -139,6 +140,9 @@ class ListTileThemeData with Diagnosticable { /// or [ExpansionTile.controlAffinity] or [SwitchListTile.controlAffinity] or [RadioListTile.controlAffinity]. final ListTileControlAffinity? controlAffinity; + /// If specified, overrides the default value of [ListTile.isThreeLine]. + final bool? isThreeLine; + /// Creates a copy of this object with the given fields replaced with the /// new values. ListTileThemeData copyWith({ @@ -187,6 +191,7 @@ class ListTileThemeData with Diagnosticable { visualDensity: visualDensity ?? this.visualDensity, titleAlignment: titleAlignment ?? this.titleAlignment, controlAffinity: controlAffinity ?? this.controlAffinity, + isThreeLine: isThreeLine ?? this.isThreeLine, ); } @@ -221,6 +226,7 @@ class ListTileThemeData with Diagnosticable { visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity, titleAlignment: t < 0.5 ? a?.titleAlignment : b?.titleAlignment, controlAffinity: t < 0.5 ? a?.controlAffinity : b?.controlAffinity, + isThreeLine: t < 0.5 ? a?.isThreeLine : b?.isThreeLine, ); } @@ -247,6 +253,7 @@ class ListTileThemeData with Diagnosticable { visualDensity, titleAlignment, controlAffinity, + isThreeLine, ]); @override @@ -278,7 +285,8 @@ class ListTileThemeData with Diagnosticable { other.mouseCursor == mouseCursor && other.visualDensity == visualDensity && other.titleAlignment == titleAlignment && - other.controlAffinity == controlAffinity; + other.controlAffinity == controlAffinity && + other.isThreeLine == isThreeLine; } @override @@ -337,6 +345,7 @@ class ListTileThemeData with Diagnosticable { defaultValue: null, ), ); + properties.add(DiagnosticsProperty('isThreeLine', isThreeLine, defaultValue: null)); } } @@ -573,6 +582,7 @@ class ListTileTheme extends InheritedTheme { MaterialStateProperty? mouseCursor, VisualDensity? visualDensity, ListTileControlAffinity? controlAffinity, + bool? isThreeLine, required Widget child, }) { return Builder( @@ -603,6 +613,7 @@ class ListTileTheme extends InheritedTheme { mouseCursor: mouseCursor ?? parent.mouseCursor, visualDensity: visualDensity ?? parent.visualDensity, controlAffinity: controlAffinity ?? parent.controlAffinity, + isThreeLine: isThreeLine ?? parent.isThreeLine, ), child: child, ); @@ -627,6 +638,7 @@ class ListTileTheme extends InheritedTheme { horizontalTitleGap: horizontalTitleGap, minVerticalPadding: minVerticalPadding, minLeadingWidth: minLeadingWidth, + isThreeLine: _data?.isThreeLine, ), child: child, ); diff --git a/packages/flutter/lib/src/rendering/proxy_sliver.dart b/packages/flutter/lib/src/rendering/proxy_sliver.dart index 84be9b46ca119..a1d8f6353baa7 100644 --- a/packages/flutter/lib/src/rendering/proxy_sliver.dart +++ b/packages/flutter/lib/src/rendering/proxy_sliver.dart @@ -42,6 +42,14 @@ abstract class RenderProxySliver extends RenderSliver this.child = child; } + @override + Rect get semanticBounds { + if (child != null) { + return child!.semanticBounds; + } + return super.semanticBounds; + } + @override void setupParentData(RenderObject child) { if (child.parentData is! SliverPhysicalParentData) { diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart index 0a944edba31ef..c0f5d99ee9d29 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -5,6 +5,7 @@ /// @docImport 'package:flutter/material.dart'; /// /// @docImport 'proxy_box.dart'; +/// @docImport 'proxy_sliver.dart'; /// @docImport 'sliver_fill.dart'; /// @docImport 'sliver_grid.dart'; /// @docImport 'sliver_list.dart'; @@ -1306,6 +1307,28 @@ List _debugCompareFloats( /// than zero, then it should override [childCrossAxisPosition]. For example /// [RenderSliverGrid] overrides this method. abstract class RenderSliver extends RenderObject { + /// Whether this sliver should be included in the semantics tree. + /// + /// This value is used by [RenderViewportBase] to ensure a sliver is + /// included in the semantics tree regardless of its geometry. + /// + /// A [RenderSliver] should override this value to `true` to ensure + /// its child is included in the semantics tree. For example if your + /// sliver is under a [RenderViewport] you may want to wrap it with + /// a [SliverEnsureSemantics] to ensure that: + /// + /// 1. It is still visited by [RenderViewportBase.visitChildrenForSemantics] + /// regardless of its geometry. This includes cases where your sliver is outside + /// the current viewport and cache extent. + /// 2. Its semantic information is not clipped out by the [RenderViewport] in + /// [RenderViewportBase.describeSemanticsClip] or [RenderViewportBase.describeApproximatePaintClip]. + /// + /// If a given [RenderSliver] does not provide a valid [semanticBounds] it will still + /// be dropped from the semantics tree. + /// + /// Defaults to `false`. + bool get ensureSemantics => false; + // layout input @override SliverConstraints get constraints => super.constraints as SliverConstraints; diff --git a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart index 90885e4c110d0..4f6071eaef202 100644 --- a/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart +++ b/packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart @@ -425,6 +425,18 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver // Do not visit children in [_keepAliveBucket]. } + @override + Rect get semanticBounds { + // If we laid out the first child but this sliver is not visible, we report the + // semantic bounds of this sliver as the bounds of the first child. This is necessary + // for accessibility technologies to reach this sliver even when it is outside + // the current viewport and cache extent. + if (geometry != null && !geometry!.visible && firstChild != null && firstChild!.hasSize) { + return firstChild!.paintBounds; + } + return super.semanticBounds; + } + /// Called during layout to create and add the child with the given index and /// scroll offset. /// diff --git a/packages/flutter/lib/src/rendering/sliver_tree.dart b/packages/flutter/lib/src/rendering/sliver_tree.dart index f88df192d67ae..6cc82bb425746 100644 --- a/packages/flutter/lib/src/rendering/sliver_tree.dart +++ b/packages/flutter/lib/src/rendering/sliver_tree.dart @@ -332,7 +332,8 @@ class RenderTreeSliver extends RenderSliverVariedExtentList { while (child != null && indexOf(child) <= index) { final double mainAxisDelta = childMainAxisPosition(child); final TreeSliverNodeParentData parentData = child.parentData! as TreeSliverNodeParentData; - final Offset childOffset = Offset(parentData.depth * indentation, parentData.layoutOffset!); + final Offset childOffset = + Offset(parentData.depth * indentation, parentData.layoutOffset!) + offset; // If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child)) // does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden. diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 09af6c11f1cec..2661717e451b9 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -314,7 +314,10 @@ abstract class RenderViewportBase sliver.geometry!.visible || sliver.geometry!.cacheExtent > 0.0, + (RenderSliver sliver) => + sliver.geometry!.visible || + sliver.geometry!.cacheExtent > 0.0 || + sliver.ensureSemantics, ) .forEach(visitor); } @@ -671,6 +674,12 @@ abstract class RenderViewportBase 0.0)) { + // Return null here so we don't end up clipping out a semantics node rect + // for a sliver child when we explicitly want it to be included in the semantics tree. + return null; + } + switch (clipBehavior) { case Clip.none: return null; @@ -716,7 +725,14 @@ abstract class RenderViewportBase 0.0)) { + // Return null here so we don't end up clipping out a semantics node rect + // for a sliver child when we explicitly want it to be included in the semantics tree. + return null; + } if (_calculatedCacheExtent == null) { return semanticBounds; } diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index 4f96cb9f169b0..f6d14ebf789ae 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -1771,3 +1771,48 @@ class _SliverMainAxisGroupElement extends MultiChildRenderObjectElement { .forEach(visitor); } } + +/// A sliver that ensures its sliver child is included in the semantics tree. +/// +/// This sliver ensures that its child sliver is still visited by the [RenderViewport] +/// when constructing the semantics tree, and is not clipped out of the semantics tree by +/// the [RenderViewport] when it is outside the current viewport and outside the cache extent. +/// +/// The child sliver may still be excluded from the semantics tree if its [RenderSliver] does +/// not provide a valid [RenderSliver.semanticBounds]. This sliver does not guarantee its +/// child sliver is laid out. +/// +/// Be mindful when positioning [SliverEnsureSemantics] in a [CustomScrollView] after slivers that build +/// their children lazily, like [SliverList]. Lazy slivers might underestimate the total scrollable size (scroll +/// extent) before the [SliverEnsureSemantics] widget. This inaccuracy can cause problems for assistive +/// technologies (e.g., screen readers), which rely on a correct scroll extent to navigate properly; they +/// might fail to scroll accurately to the content wrapped by [SliverEnsureSemantics]. +/// +/// To avoid this potential issue and ensure the scroll extent is calculated accurately up to this sliver, +/// it's recommended to use slivers that can determine their extent precisely beforehand. Instead of +/// [SliverList], consider using [SliverFixedExtentList], [SliverVariedExtentList], or +/// [SliverPrototypeExtentList]. If using [SliverGrid], ensure it employs a delegate such as +/// [SliverGridDelegateWithFixedCrossAxisCount] or [SliverGridDelegateWithMaxCrossAxisExtent]. +/// Using these alternatives guarantees that the scrollable area's size is known accurately, allowing +/// assistive technologies to function correctly with [SliverEnsureSemantics]. +/// +/// {@tool dartpad} +/// This example shows how to use [SliverEnsureSemantics] to keep certain headers and lists +/// available to assistive technologies while they are outside the current viewport and cache extent. +/// +/// ** See code in examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart ** +/// {@end-tool} +// TODO(Renzo-Olivares): Investigate potential solutions for revealing off screen items, https://github.com/flutter/flutter/issues/166703. +class SliverEnsureSemantics extends SingleChildRenderObjectWidget { + /// Creates a sliver that ensures its sliver child is included in the semantics tree. + const SliverEnsureSemantics({super.key, required Widget sliver}) : super(child: sliver); + + @override + RenderObject createRenderObject(BuildContext context) => _RenderSliverEnsureSemantics(); +} + +/// Ensures its sliver child is included in the semantics tree. +class _RenderSliverEnsureSemantics extends RenderProxySliver { + @override + bool get ensureSemantics => true; +} diff --git a/packages/flutter/test/cupertino/date_picker_test.dart b/packages/flutter/test/cupertino/date_picker_test.dart index 883a664b2d5fc..1c5f0dc67ac74 100644 --- a/packages/flutter/test/cupertino/date_picker_test.dart +++ b/packages/flutter/test/cupertino/date_picker_test.dart @@ -17,8 +17,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../impeller_test_helpers.dart'; - // TODO(yjbanov): on the web text rendered with perspective produces flaky goldens: https://github.com/flutter/flutter/issues/110785 final bool skipPerspectiveTextGoldens = isBrowser && isSkwasm; @@ -1611,7 +1609,7 @@ void main() { matchesGoldenFile('date_picker_test.datetime.drag.png'), ); } - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616 + }); testWidgets('DatePicker displays the date in correct order', (WidgetTester tester) async { await tester.pumpWidget( @@ -1761,7 +1759,7 @@ void main() { matchesGoldenFile('timer_picker_test.datetime.drag.png'), ); } - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616 + }); testWidgets('TimerPicker only changes hour label after scrolling stops', ( WidgetTester tester, diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index ec52249f1e97b..b3f607a109c51 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -4361,6 +4361,138 @@ void main() { expect(trailingOffset.dy - tileOffset.dy, minVerticalPadding); }); }); + + // Regression test for https://github.com/flutter/flutter/issues/165453 + testWidgets('ListTile isThreeLine', (WidgetTester tester) async { + const double height = 300; + const double avatarTop = 130.0; + const double placeholderTop = 138.0; + + Widget buildFrame({bool? themeDataIsThreeLine, bool? themeIsThreeLine, bool? isThreeLine}) { + return MaterialApp( + key: UniqueKey(), + theme: + themeDataIsThreeLine != null + ? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: themeDataIsThreeLine)) + : null, + home: Material( + child: ListTileTheme( + data: + themeIsThreeLine != null ? ListTileThemeData(isThreeLine: themeIsThreeLine) : null, + child: ListView( + children: [ + ListTile( + isThreeLine: isThreeLine, + leading: const CircleAvatar(), + trailing: const SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: const Text('A'), + subtitle: const Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + ), + ListTile( + isThreeLine: isThreeLine, + leading: const CircleAvatar(), + trailing: const SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: const Text('A'), + subtitle: const Text('A'), + ), + ], + ), + ), + ), + ); + } + + void expectTwoLine() { + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 72.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0), + ); + } + + void expectThreeLine() { + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 88.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0), + ); + } + + await tester.pumpWidget(buildFrame()); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: true, isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: true, themeIsThreeLine: true, isThreeLine: false), + ); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(themeIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget(buildFrame(themeDataIsThreeLine: false, isThreeLine: true)); + expectThreeLine(); + + await tester.pumpWidget( + buildFrame(themeDataIsThreeLine: false, themeIsThreeLine: false, isThreeLine: true), + ); + expectThreeLine(); + }); } RenderParagraph _getTextRenderObject(WidgetTester tester, String text) { diff --git a/packages/flutter/test/material/list_tile_theme_test.dart b/packages/flutter/test/material/list_tile_theme_test.dart index 3c12053b7a629..c9c71c798377f 100644 --- a/packages/flutter/test/material/list_tile_theme_test.dart +++ b/packages/flutter/test/material/list_tile_theme_test.dart @@ -77,6 +77,7 @@ void main() { expect(themeData.mouseCursor, null); expect(themeData.visualDensity, null); expect(themeData.titleAlignment, null); + expect(themeData.isThreeLine, null); }); testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async { @@ -115,6 +116,7 @@ void main() { mouseCursor: MaterialStateMouseCursor.clickable, visualDensity: VisualDensity.comfortable, titleAlignment: ListTileTitleAlignment.top, + isThreeLine: true, ).debugFillProperties(builder); final List description = @@ -146,6 +148,7 @@ void main() { 'mouseCursor: WidgetStateMouseCursor(clickable)', 'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)', 'titleAlignment: ListTileTitleAlignment.top', + 'isThreeLine: true', ]), ); }); @@ -937,6 +940,7 @@ void main() { minTileHeight: 30, enableFeedback: true, titleAlignment: ListTileTitleAlignment.bottom, + isThreeLine: true, ); final ListTileThemeData copy = original.copyWith( @@ -958,6 +962,7 @@ void main() { minTileHeight: 80, enableFeedback: false, titleAlignment: ListTileTitleAlignment.top, + isThreeLine: false, ); expect(copy.dense, false); @@ -978,6 +983,7 @@ void main() { expect(copy.minTileHeight, 80); expect(copy.enableFeedback, false); expect(copy.titleAlignment, ListTileTitleAlignment.top); + expect(copy.isThreeLine, false); }); testWidgets('ListTileTheme.titleAlignment is overridden by ListTile.titleAlignment', ( @@ -1040,6 +1046,7 @@ void main() { titleAlignment: ListTileTitleAlignment.bottom, mouseCursor: MaterialStateMouseCursor.textable, visualDensity: VisualDensity.comfortable, + isThreeLine: true, ), ), home: Material( @@ -1067,6 +1074,7 @@ void main() { titleAlignment: ListTileTitleAlignment.top, mouseCursor: MaterialStateMouseCursor.clickable, visualDensity: VisualDensity.compact, + isThreeLine: false, child: const ListTile(), ); }, @@ -1098,6 +1106,217 @@ void main() { expect(theme.titleAlignment, ListTileTitleAlignment.top); expect(theme.mouseCursor, MaterialStateMouseCursor.clickable); expect(theme.visualDensity, VisualDensity.compact); + expect(theme.isThreeLine, false); + }); + + // Regression test for https://github.com/flutter/flutter/issues/165453 + testWidgets('ListTileThemeData isThreeLine', (WidgetTester tester) async { + const double height = 300; + const double avatarTop = 130.0; + const double placeholderTop = 138.0; + + Widget buildFrame({bool? isThreeLine}) { + return MaterialApp( + key: UniqueKey(), + theme: + isThreeLine != null + ? ThemeData(listTileTheme: ListTileThemeData(isThreeLine: isThreeLine)) + : null, + home: Material( + child: ListView( + children: const [ + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + ), + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A'), + ), + ], + ), + ), + ); + } + + void expectTwoLine() { + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 72.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0), + ); + } + + void expectThreeLine() { + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 88.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0), + ); + } + + await tester.pumpWidget(buildFrame()); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(isThreeLine: false)); + expectTwoLine(); + + await tester.pumpWidget(buildFrame(isThreeLine: true)); + expectThreeLine(); + }); + + // Regression test for https://github.com/flutter/flutter/issues/165453 + testWidgets('ListTileTheme isThreeLine', (WidgetTester tester) async { + const double height = 300; + const double avatarTop = 130.0; + const double placeholderTop = 138.0; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(listTileTheme: const ListTileThemeData(isThreeLine: true)), + home: Material( + child: ListTileTheme( + data: const ListTileThemeData(isThreeLine: false), + child: ListView( + children: const [ + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + ), + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A'), + ), + ], + ), + ), + ), + ), + ); + + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, avatarTop, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, placeholderTop, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 72.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 16.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 24.0, 24.0, 24.0), + ); + + // THREE-LINE + await tester.pumpWidget( + MaterialApp( + key: UniqueKey(), + home: Material( + child: ListTileTheme( + data: const ListTileThemeData(isThreeLine: true), + child: ListView( + children: const [ + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'), + ), + ListTile( + leading: CircleAvatar(), + trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()), + title: Text('A'), + subtitle: Text('A'), + ), + ], + ), + ), + ), + ), + ); + + expect( + tester.getRect(find.byType(ListTile).at(0)), + const Rect.fromLTWH(0.0, 0.0, 800.0, height), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(0)), + const Rect.fromLTWH(16.0, 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(0)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, 8.0, 24.0, 24.0), + ); + expect( + tester.getRect(find.byType(ListTile).at(1)), + const Rect.fromLTWH(0.0, height, 800.0, 88.0), + ); + expect( + tester.getRect(find.byType(CircleAvatar).at(1)), + const Rect.fromLTWH(16.0, height + 8.0, 40.0, 40.0), + ); + expect( + tester.getRect(find.byType(Placeholder).at(1)), + const Rect.fromLTWH(800.0 - 24.0 - 24.0, height + 8.0, 24.0, 24.0), + ); }); } diff --git a/packages/flutter/test/widgets/color_filter_test.dart b/packages/flutter/test/widgets/color_filter_test.dart index 2f39a48e84a9d..5b42a31a6350e 100644 --- a/packages/flutter/test/widgets/color_filter_test.dart +++ b/packages/flutter/test/widgets/color_filter_test.dart @@ -12,8 +12,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../impeller_test_helpers.dart'; - void main() { testWidgets('Color filter - red', (WidgetTester tester) async { await tester.pumpWidget( @@ -56,7 +54,7 @@ void main() { ), ); await expectLater(find.byType(ColorFiltered), matchesGoldenFile('color_filter_sepia.png')); - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616 + }); testWidgets('Color filter - reuses its layer', (WidgetTester tester) async { Future pumpWithColor(Color color) async { diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 362370a0b3f79..8fc6daa2dc064 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -20,7 +20,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; -import '../impeller_test_helpers.dart'; import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show textOffsetToPosition; import '../widgets/semantics_tester.dart'; @@ -5267,7 +5266,7 @@ void main() { find.byType(MaterialApp), matchesGoldenFile('selectable_text_golden.TextSelectionStyle.1.png'), ); - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616 + }); testWidgets('text selection style 2', (WidgetTester tester) async { await tester.pumpWidget( @@ -5304,7 +5303,7 @@ void main() { find.byType(MaterialApp), matchesGoldenFile('selectable_text_golden.TextSelectionStyle.2.png'), ); - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/143616 + }); testWidgets('keeps alive when has focus', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/shader_mask_test.dart b/packages/flutter/test/widgets/shader_mask_test.dart index e1e134baa9d7c..fb6edab04e908 100644 --- a/packages/flutter/test/widgets/shader_mask_test.dart +++ b/packages/flutter/test/widgets/shader_mask_test.dart @@ -10,8 +10,6 @@ library; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../impeller_test_helpers.dart'; - Shader createShader(Rect bounds) { return const LinearGradient( begin: Alignment.topCenter, @@ -102,5 +100,5 @@ void main() { find.byType(RepaintBoundary), matchesGoldenFile('shader_mask.bounds.matches_top_left.png'), ); - }, skip: impellerEnabled); // https://github.com/flutter/flutter/issues/144555 + }); } diff --git a/packages/flutter/test/widgets/sliver_tree_test.dart b/packages/flutter/test/widgets/sliver_tree_test.dart index d5ff22b297a2d..d5511fe91d05d 100644 --- a/packages/flutter/test/widgets/sliver_tree_test.dart +++ b/packages/flutter/test/widgets/sliver_tree_test.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is run as part of a reduced test set in CI on Mac and Windows +// machines. +@Tags(['reduced-test-set']) +library; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -861,4 +866,43 @@ void main() { }); }, ); + + testWidgets('TreeSliver and PinnedHeaderSliver can render correctly when used together.', ( + WidgetTester tester, + ) async { + const ValueKey key = ValueKey('sliver_tree_pined_header'); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: RepaintBoundary( + key: key, + child: SizedBox( + height: 20, + width: 20, + child: CustomScrollView( + slivers: [ + const PinnedHeaderSliver(child: SizedBox(height: 10)), + TreeSliver( + tree: >[TreeSliverNode(Object())], + treeRowExtentBuilder: (_, _) => 10, + treeNodeBuilder: ( + BuildContext context, + TreeSliverNode node, + AnimationStyle animationStyle, + ) { + return const ColoredBox(color: Colors.red); + }, + ), + ], + ), + ), + ), + ), + ), + ); + await expectLater(find.byKey(key), matchesGoldenFile('sliver_tree.pined_header.0.png')); + expect(tester.getTopLeft(find.byType(ColoredBox)), const Offset(0, 10)); + }); } diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart index 66399e23bf4dd..de4b6d8bb5d92 100644 --- a/packages/flutter/test/widgets/slivers_test.dart +++ b/packages/flutter/test/widgets/slivers_test.dart @@ -726,7 +726,7 @@ void main() { }, ); - Widget boilerPlate(Widget sliver) { + Widget boilerPlate(List slivers) { return Localizations( locale: const Locale('en', 'us'), delegates: const >[ @@ -735,10 +735,7 @@ void main() { ], child: Directionality( textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: CustomScrollView(slivers: [sliver]), - ), + child: MediaQuery(data: const MediaQueryData(), child: CustomScrollView(slivers: slivers)), ), ); } @@ -747,7 +744,7 @@ void main() { testWidgets('offstage true', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - boilerPlate(const SliverOffstage(sliver: SliverToBoxAdapter(child: Text('a')))), + boilerPlate([const SliverOffstage(sliver: SliverToBoxAdapter(child: Text('a')))]), ); expect(semantics.nodesWith(label: 'a'), hasLength(0)); @@ -762,9 +759,9 @@ void main() { testWidgets('offstage false', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOffstage(offstage: false, sliver: SliverToBoxAdapter(child: Text('a'))), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -783,12 +780,12 @@ void main() { // Opacity 1.0: Semantics and painting await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 1.0, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -796,12 +793,12 @@ void main() { // Opacity 0.0: Nothing await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.0, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(0)); @@ -809,13 +806,13 @@ void main() { // Opacity 0.0 with semantics: Just semantics await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.0, alwaysIncludeSemantics: true, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -823,12 +820,12 @@ void main() { // Opacity 0.0 without semantics: Nothing await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.0, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(0)); @@ -836,12 +833,12 @@ void main() { // Opacity 0.1: Semantics and painting await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.1, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -849,12 +846,12 @@ void main() { // Opacity 0.1 without semantics: Still has semantics and painting await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.1, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -862,13 +859,13 @@ void main() { // Opacity 0.1 with semantics: Semantics and painting await tester.pumpWidget( - boilerPlate( + boilerPlate([ const SliverOpacity( sliver: SliverToBoxAdapter(child: Text('a', textDirection: TextDirection.rtl)), opacity: 0.1, alwaysIncludeSemantics: true, ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); @@ -883,7 +880,7 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); final List events = []; await tester.pumpWidget( - boilerPlate( + boilerPlate([ SliverIgnorePointer( ignoringSemantics: false, sliver: SliverToBoxAdapter( @@ -895,7 +892,7 @@ void main() { ), ), ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); await tester.tap(find.byType(GestureDetector), warnIfMissed: false); @@ -907,7 +904,7 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); final List events = []; await tester.pumpWidget( - boilerPlate( + boilerPlate([ SliverIgnorePointer( ignoring: false, ignoringSemantics: true, @@ -920,7 +917,7 @@ void main() { ), ), ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(0)); await tester.tap(find.byType(GestureDetector)); @@ -931,13 +928,13 @@ void main() { testWidgets('ignoring only block semantics actions', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - boilerPlate( + boilerPlate([ SliverIgnorePointer( sliver: SliverToBoxAdapter( child: GestureDetector(child: const Text('a'), onTap: () {}), ), ), - ), + ]), ); expect(semantics, includesNodeWith(label: 'a', actions: [])); semantics.dispose(); @@ -947,7 +944,7 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); final List events = []; await tester.pumpWidget( - boilerPlate( + boilerPlate([ SliverIgnorePointer( ignoringSemantics: true, sliver: SliverToBoxAdapter( @@ -959,7 +956,7 @@ void main() { ), ), ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(0)); await tester.tap(find.byType(GestureDetector), warnIfMissed: false); @@ -971,7 +968,7 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); final List events = []; await tester.pumpWidget( - boilerPlate( + boilerPlate([ SliverIgnorePointer( ignoring: false, ignoringSemantics: false, @@ -984,7 +981,7 @@ void main() { ), ), ), - ), + ]), ); expect(semantics.nodesWith(label: 'a'), hasLength(1)); await tester.tap(find.byType(GestureDetector)); @@ -993,6 +990,40 @@ void main() { }); }); + group('SliverEnsureSemantics - ', () { + testWidgets('ensure semantics', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget( + boilerPlate([ + const SliverEnsureSemantics(sliver: SliverToBoxAdapter(child: Text('a'))), + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Lorem Ipsum $index'), + ), + ); + }, + childCount: 50, + semanticIndexOffset: 1, + ), + ), + const SliverEnsureSemantics(sliver: SliverToBoxAdapter(child: Text('b'))), + ]), + ); + + // Even though 'b' is outside of the Viewport and cacheExtent, since it is + // wrapped with a `SliverEnsureSemantics` it will still be included in the + // semantics tree. + expect(semantics.nodesWith(label: 'b'), hasLength(1)); + expect(find.text('b'), findsNothing); + expect(find.byType(SliverEnsureSemantics, skipOffstage: false), findsNWidgets(2)); + semantics.dispose(); + }); + }); + testWidgets('SliverList handles 0 scrollOffsetCorrection', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/62198 await tester.pumpWidget( diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 8165fe3c5e8a8..4cebf976dae3f 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -258,8 +258,6 @@ List generateCommands({required bool verboseHelp, required bool platform: globals.platform, shutdownHooks: globals.shutdownHooks, os: globals.os, - processManager: globals.processManager, - artifacts: globals.artifacts!, ), UpgradeCommand(verboseHelp: verboseHelp), SymbolizeCommand(stdio: globals.stdio, fileSystem: globals.fs), diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 8c94b70cf60a5..abc7e66bbdb63 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -5,9 +5,7 @@ import 'package:args/args.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config.dart'; -import 'package:process/process.dart'; -import '../artifacts.dart'; import '../base/common.dart'; import '../base/deferred_component.dart'; import '../base/file_system.dart'; @@ -26,8 +24,6 @@ import '../linux/build_linux.dart'; import '../macos/build_macos.dart'; import '../project.dart'; import '../runner/flutter_command.dart'; -import '../runner/flutter_command_runner.dart'; -import '../widget_preview/dtd_services.dart'; import '../widget_preview/preview_code_generator.dart'; import '../widget_preview/preview_detector.dart'; import '../widget_preview/preview_manifest.dart'; @@ -45,8 +41,6 @@ class WidgetPreviewCommand extends FlutterCommand { required Platform platform, required ShutdownHooks shutdownHooks, required OperatingSystemUtils os, - required ProcessManager processManager, - required Artifacts artifacts, }) { addSubcommand( WidgetPreviewStartCommand( @@ -58,8 +52,6 @@ class WidgetPreviewCommand extends FlutterCommand { platform: platform, shutdownHooks: shutdownHooks, os: os, - processManager: processManager, - artifacts: artifacts, ), ); addSubcommand( @@ -126,8 +118,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C required this.platform, required this.shutdownHooks, required this.os, - required this.processManager, - required this.artifacts, }) { addPubOptions(); argParser @@ -162,9 +152,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C static const String kHeadlessWeb = 'headless-web'; static const String kWidgetPreviewScaffoldOutputDir = 'scaffold-output-dir'; - /// Environment variable used to pass the DTD URI to the widget preview scaffold. - static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; - @override Future> get requiredArtifacts async => const { // Ensure the Flutter Web SDK is installed. @@ -198,10 +185,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C final OperatingSystemUtils os; - final ProcessManager processManager; - - final Artifacts artifacts; - late final FlutterProject rootProject = getRootProject(); late final PreviewDetector _previewDetector = PreviewDetector( @@ -220,12 +203,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C cache: cache, ); - late final WidgetPreviewDtdServices _dtdService = WidgetPreviewDtdServices( - logger: logger, - shutdownHooks: shutdownHooks, - dtdLauncher: DtdLauncher(logger: logger, artifacts: artifacts, processManager: processManager), - ); - /// The currently running instance of the widget preview scaffold. AppInstance? _widgetPreviewApp; @@ -307,7 +284,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C shutdownHooks.addShutdownHook(() async { await _widgetPreviewApp?.stop(); }); - await configureDtd(); _widgetPreviewApp = await runPreviewEnvironment( widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject, ); @@ -333,31 +309,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C _populatePreviewPubspec(rootProject: rootProject); } - /// Configures the Dart Tooling Daemon connection. - /// - /// If --dtd-uri is provided, the existing DTD instance will be used. If the tool fails to - /// connect to this URI, it will start its own DTD instance. - /// - /// If --dtd-uri is not provided, a DTD instance managed by the tool will be started. - Future configureDtd() async { - final String? existingDtdUriStr = stringArg(FlutterGlobalOptions.kDtdUrl, global: true); - Uri? existingDtdUri; - try { - if (existingDtdUriStr != null) { - existingDtdUri = Uri.parse(existingDtdUriStr); - } - } on FormatException { - logger.printWarning('Failed to parse value of --dtd-uri: $existingDtdUriStr.'); - } - if (existingDtdUri == null) { - logger.printTrace('Launching a fresh DTD instance...'); - await _dtdService.launchAndConnect(); - } else { - logger.printTrace('Connecting to existing DTD instance at: $existingDtdUri...'); - await _dtdService.connect(dtdWsUri: existingDtdUri); - } - } - /// Builds the application binary for the widget preview scaffold the first /// time the widget preview command is run. /// @@ -506,12 +457,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C BuildMode.debug, null, treeShakeIcons: false, - // Provide the DTD connection information directly to the preview scaffold. - // This could, in theory, be provided via a follow up call to a service extension - // registered by the preview scaffold, but there's some uncertainty around how service - // extensions will work with Flutter web embedded in VSCode without a Chrome debugger - // connection. - dartDefines: ['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'], extraFrontEndOptions: isWeb ? ['--dartdevc-canary', '--dartdevc-module-format=ddc'] : null, packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path, @@ -654,7 +599,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C if (offline) '--offline', '--directory', widgetPreviewScaffoldProject.directory.path, - 'dtd', 'flutter_lints', 'stack_trace', ], diff --git a/packages/flutter_tools/lib/src/compute_dev_dependencies.dart b/packages/flutter_tools/lib/src/compute_dev_dependencies.dart index 007f30d8cc5e7..d679d4ad547ae 100644 --- a/packages/flutter_tools/lib/src/compute_dev_dependencies.dart +++ b/packages/flutter_tools/lib/src/compute_dev_dependencies.dart @@ -18,7 +18,13 @@ Future> computeExclusiveDevDependencies( required Logger logger, required FlutterProject project, }) async { - final Map jsonResult = await pub.deps(project); + final Map? jsonResult = await pub.deps(project); + + // Avoid crashing if dart pub deps is not ready. + // See https://github.com/flutter/flutter/issues/166648. + if (jsonResult == null) { + return {}; + } Never fail([String? reason]) { logger.printTrace(const JsonEncoder.withIndent(' ').convert(jsonResult)); diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart index 66aa535189b35..f362f7aface38 100644 --- a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart +++ b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart @@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; import 'package:process/process.dart'; import '../application_package.dart'; +import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; @@ -21,6 +22,7 @@ import '../convert.dart'; import '../device.dart'; import '../device_port_forwarder.dart'; import '../features.dart'; +import '../globals.dart' as globals; import '../project.dart'; import '../protocol_discovery.dart'; import '../vmservice.dart'; @@ -341,6 +343,7 @@ class CustomDeviceAppSession { Map platformArgs = const {}, bool prebuiltApplication = false, String? userIdentifier, + Map additionalReplacementValues = const {}, }) async { final bool traceStartup = platformArgs['trace-startup'] as bool? ?? false; final String? packageName = _appPackage.name; @@ -352,7 +355,7 @@ class CustomDeviceAppSession { 'remotePath': '/tmp/', 'appName': packageName, 'engineOptions': _getEngineOptionsForCmdline(debuggingOptions, traceStartup, route), - }); + }, additionalReplacementValues: additionalReplacementValues); final Process process = await _processUtils.start(interpolated); assert(_process == null); @@ -700,6 +703,16 @@ class CustomDevice extends Device { String? userIdentifier, BundleBuilder? bundleBuilder, }) async { + final TargetPlatform platform = await targetPlatform; + final Artifacts artifacts = globals.artifacts!; + + final Map additionalReplacementValues = { + 'buildMode': debuggingOptions.buildInfo.modeName, + 'icuDataPath': artifacts.getArtifactPath(Artifact.icuData, platform: platform), + 'engineRevision': + artifacts.usesLocalArtifacts ? 'local' : globals.flutterVersion.engineRevision, + }; + if (!prebuiltApplication) { final String assetBundleDir = getAssetBuildDirectory(); @@ -707,7 +720,7 @@ class CustomDevice extends Device { // this just builds the asset bundle, it's the same as `flutter build bundle` await bundleBuilder.build( - platform: await targetPlatform, + platform: platform, buildInfo: debuggingOptions.buildInfo, mainPath: mainPath, depfilePath: defaultDepfilePath, @@ -720,7 +733,11 @@ class CustomDevice extends Device { if (packageName == null) { throwToolExit('Could not start app, name for $package is unknown.'); } - await _tryPostBuild(appName: packageName, localPath: assetBundleDir); + await _tryPostBuild( + appName: packageName, + localPath: assetBundleDir, + additionalReplacementValues: additionalReplacementValues, + ); } } @@ -736,6 +753,7 @@ class CustomDevice extends Device { platformArgs: platformArgs, prebuiltApplication: prebuiltApplication, userIdentifier: userIdentifier, + additionalReplacementValues: additionalReplacementValues, ); } diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index 5d01157463f2c..640c151e6f9dc 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -158,7 +158,9 @@ abstract class Pub { /// While it is guaranteed that, if successful, that the result are a valid /// JSON object, the exact contents returned are _not_ validated, and are left /// as a responsibility of the caller. - Future> deps(FlutterProject project); + /// + /// If `null` is returned, it should be assumed deps could not be determined. + Future?> deps(FlutterProject project); /// Runs pub in 'batch' mode. /// @@ -354,13 +356,22 @@ class _DefaultPub implements Pub { } @override - Future> deps(FlutterProject project) async { + Future?> deps(FlutterProject project) async { final List pubCommand = [..._pubCommand, 'deps', '--json']; + final RunResult runResult; - final RunResult runResult = await _processUtils.run( - pubCommand, - workingDirectory: project.directory.path, - ); + // Don't treat this command as terminal if it fails. + // See https://github.com/flutter/flutter/issues/166648 + try { + runResult = await _processUtils.run( + pubCommand, + workingDirectory: project.directory.path, + throwOnError: true, + ); + } on io.ProcessException catch (e) { + _logger.printWarning('${pubCommand.join(' ')} ${e.message}'); + return null; + } Never fail([String? reason]) { final String stdout = runResult.stdout; @@ -374,11 +385,6 @@ class _DefaultPub implements Pub { ); } - // Guard against dart pub deps crashing. - if (runResult.exitCode != 0) { - fail(); - } - // Guard against dart pub deps having explicitly invalid output. try { final Object? result = json.decode(runResult.stdout); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index d8f342d9b7f84..158550c7cd399 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -36,7 +36,6 @@ abstract final class FlutterGlobalOptions { static const String kMachineFlag = 'machine'; static const String kPackagesOption = 'packages'; static const String kPrefixedErrorsFlag = 'prefixed-errors'; - static const String kDtdUrl = 'dtd-url'; static const String kPrintDtd = 'print-dtd'; static const String kQuietFlag = 'quiet'; static const String kShowTestDeviceFlag = 'show-test-device'; @@ -152,12 +151,6 @@ class FlutterCommandRunner extends CommandRunner { hide: !verboseHelp, help: 'Path to your "package_config.json" file.', ); - argParser.addOption( - FlutterGlobalOptions.kDtdUrl, - help: - 'The address of an existing Dart Tooling Daemon instance to be used by the Flutter CLI.', - hide: !verboseHelp, - ); argParser.addFlag( FlutterGlobalOptions.kPrintDtd, negatable: false, diff --git a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart b/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart deleted file mode 100644 index 1a28fa6c080af..0000000000000 --- a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:dtd/dtd.dart'; -import 'package:process/process.dart'; - -import '../artifacts.dart'; -import '../base/common.dart'; -import '../base/io.dart'; -import '../base/logger.dart'; -import '../base/process.dart'; -import '../convert.dart'; - -/// Provides services, streams, and RPC invocations to interact with the Widget Preview Scaffold. -class WidgetPreviewDtdServices { - WidgetPreviewDtdServices({ - required this.logger, - required this.shutdownHooks, - required this.dtdLauncher, - }) { - shutdownHooks.addShutdownHook(() async { - await _dtd?.close(); - await dtdLauncher.dispose(); - }); - } - - final Logger logger; - final ShutdownHooks shutdownHooks; - final DtdLauncher dtdLauncher; - - DartToolingDaemon? _dtd; - - /// The [Uri] pointing to the currently connected DTD instance. - /// - /// Returns `null` if there is no DTD connection. - Uri? get dtdUri => _dtdUri; - Uri? _dtdUri; - - /// Starts DTD in a child process before invoking [connect] with a [Uri] pointing to the new - /// DTD instance. - Future launchAndConnect() async { - // Connect to the new DTD instance. - await connect(dtdWsUri: await dtdLauncher.launch()); - } - - /// Connects to an existing DTD instance and registers any relevant services. - Future connect({required Uri dtdWsUri}) async { - _dtdUri = dtdWsUri; - _dtd = await DartToolingDaemon.connect(dtdWsUri); - // TODO(bkonyi): register services. - logger.printTrace('Connected to DTD and registered services.'); - } -} - -/// Manages the lifecycle of a Dart Tooling Daemon (DTD) instance. -class DtdLauncher { - DtdLauncher({required this.logger, required this.artifacts, required this.processManager}); - - /// Starts a new DTD instance and returns the web socket URI it's available on. - Future launch() async { - if (_dtdProcess != null) { - throw StateError('Attempted to launch DTD twice.'); - } - - // Start DTD. - _dtdProcess = await processManager.start([ - artifacts.getArtifactPath(Artifact.engineDartBinary), - 'tooling-daemon', - '--machine', - ]); - - // Wait for the DTD connection information. - final Completer dtdUri = Completer(); - late final StreamSubscription sub; - sub = _dtdProcess!.stdout.transform(const Utf8Decoder()).listen((String data) async { - await sub.cancel(); - final Map jsonData = json.decode(data) as Map; - if (jsonData case {'tooling_daemon_details': {'uri': final String dtdUriString}}) { - dtdUri.complete(Uri.parse(dtdUriString)); - } else { - throwToolExit('Unable to start the Dart Tooling Daemon.'); - } - }); - return dtdUri.future; - } - - /// Kills the spawned DTD instance. - Future dispose() async { - _dtdProcess?.kill(); - _dtdProcess = null; - } - - final Logger logger; - final Artifacts artifacts; - final ProcessManager processManager; - - Process? _dtdProcess; -} diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 65fc5cf3723bb..090aa0e8eae32 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: ffi: 2.1.4 file: 7.0.1 flutter_template_images: 5.0.0 - html: 0.15.5 + html: 0.15.5+1 http: 1.3.0 intl: 0.20.2 meta: 1.16.0 @@ -122,4 +122,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: fe37 +# PUBSPEC CHECKSUM: c093 diff --git a/packages/flutter_tools/static/custom-devices.schema.json b/packages/flutter_tools/static/custom-devices.schema.json index da0a78400d96d..9c5b894b82877 100644 --- a/packages/flutter_tools/static/custom-devices.schema.json +++ b/packages/flutter_tools/static/custom-devices.schema.json @@ -59,7 +59,7 @@ "required": false }, "postBuild": { - "description": "The command to be invoked after the build process is done, to do any additional packaging for example.", + "description": "The command to be invoked after the build process is done, to do any additional packaging for example. The following variables are available via string interpolation:\n- ${appName}\n- ${localPath}\n- ${buildMode}\n- ${icuDataPath}\n- ${engineRevision}", "type": ["array", "null"], "items": { "type": "string" @@ -91,7 +91,7 @@ ] }, "runDebug": { - "description": "The command to be invoked to run the app in debug mode. The name of the app to be started is available via the ${appName} string interpolation. Make sure the flutter cmdline output is available via this commands stdout/stderr since the SDK needs the \"VM Service is now listening on ...\" message to function. If the forwardPort command is not specified, the VM Service URL will be connected to as-is, without any port forwarding. In that case you need to make sure it is reachable from your host device, possibly via the \"--vm-service-host=\" engine flag.", + "description": "The command to be invoked to run the app in debug mode. Make sure the flutter cmdline output is available via this commands stdout/stderr since the SDK needs the \"VM Service is now listening on ...\" message to function. If the forwardPort command is not specified, the VM Service URL will be connected to as-is, without any port forwarding. In that case you need to make sure it is reachable from your host device, possibly via the \"--vm-service-host=\" engine flag. The following variables are available via string interpolation:\n- ${appName}\n- ${engineOptions}\n- ${buildMode}\n- ${icuDataPath}\n- ${engineRevision}", "type": "array", "items": { "type": "string" diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index 454040e21ab40..04f74048dafcf 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -356,7 +356,6 @@ "templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl", "templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl", "templates/widget_preview_scaffold/lib/src/controls.dart.tmpl", - "templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl", "templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl", "templates/widget_preview_scaffold/lib/src/utils.dart.tmpl", "templates/widget_preview_scaffold/pubspec.yaml.tmpl", diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl deleted file mode 100644 index eaff8e7fb0872..0000000000000 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd_services.dart.tmpl +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:dtd/dtd.dart'; - -/// Provides services, streams, and RPC invocations to interact with Flutter developer tooling. -class WidgetPreviewScaffoldDtdServices { - /// Environment variable for the DTD URI. - static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; - - /// Connects to the Dart Tooling Daemon (DTD) specified by the Flutter tool. - /// - /// If the connection is successful, the Widget Preview Scaffold will register services and - /// subscribe to various streams to interact directly with other tooling (e.g., IDEs). - Future connect() async { - final Uri dtdWsUri = Uri.parse( - const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar), - ); - _dtd = await DartToolingDaemon.connect(dtdWsUri); - unawaited( - _dtd.postEvent( - 'WidgetPreviewScaffold', - 'Connected', - const {}, - ), - ); - } - - late final DartToolingDaemon _dtd; -} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl index 568743d90edd0..8449dd0ec5b1e 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl @@ -13,7 +13,6 @@ import 'package:flutter/services.dart'; import 'package:stack_trace/stack_trace.dart'; import 'controls.dart'; -import 'dtd_services.dart'; import 'generated_preview.dart'; import 'utils.dart'; import 'widget_preview.dart'; @@ -411,8 +410,6 @@ class PreviewAssetBundle extends PlatformAssetBundle { /// the preview scaffold project which prevents us from being able to use hot /// restart to iterate on this file. Future mainImpl() async { - // TODO(bkonyi): store somewhere. - await WidgetPreviewScaffoldDtdServices().connect(); runApp(_WidgetPreviewScaffold()); } diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/pubspec.yaml.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/pubspec.yaml.tmpl index c74199d7ab578..ca4a8ecee5b7a 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/pubspec.yaml.tmpl @@ -12,7 +12,6 @@ dependencies: flutter_test: sdk: flutter # These will be replaced with proper constraints after the template is hydrated. - dtd: any flutter_lints: any stack_trace: any diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart index b624e5917bde8..cbfaf3b439ffc 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:file/memory.dart'; -import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; @@ -48,8 +47,6 @@ void main() { platform: platform, processManager: processManager, ), - processManager: FakeProcessManager.any(), - artifacts: Artifacts.test(fileSystem: fileSystem), ); rootProject = FakeFlutterProject( projectRoot: 'some_project', diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart index 7a90ec27720ea..d5e4cc81d817b 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview_test.dart @@ -6,7 +6,6 @@ import 'dart:io' as io show IOOverrides; import 'package:args/command_runner.dart'; import 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/bot_detector.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -81,8 +80,6 @@ void main() { logger: logger, platform: platform, ), - artifacts: Artifacts.test(), - processManager: FakeProcessManager.any(), ), ); await runner.run(['widget-preview', ...arguments]); diff --git a/packages/flutter_tools/test/general.shard/compute_dev_dependencies_test.dart b/packages/flutter_tools/test/general.shard/compute_dev_dependencies_test.dart index 4f59e91566e69..1e3a584426290 100644 --- a/packages/flutter_tools/test/general.shard/compute_dev_dependencies_test.dart +++ b/packages/flutter_tools/test/general.shard/compute_dev_dependencies_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io' as io; + import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -356,6 +358,17 @@ void main() { ); }); + test('a pub error is treated as no data available instead of terminal', () async { + final ProcessManager processes = _dartPubDepsCrashes(project: project); + final Set dependencies = await computeExclusiveDevDependencies( + pub(processes), + project: project, + logger: logger, + ); + + expect(dependencies, isEmpty, reason: 'pub deps crashed, but was not terminal'); + }); + test('throws and logs on invalid JSON', () async { final ProcessManager processes = _dartPubDepsReturns(''' { @@ -420,3 +433,18 @@ ProcessManager _dartPubDepsReturns(String dartPubDepsOutput, {required FlutterPr ), ]); } + +ProcessManager _dartPubDepsCrashes({required FlutterProject project}) { + return FakeProcessManager.list([ + FakeCommand( + command: const [_dartBin, 'pub', '--suppress-analytics', 'deps', '--json'], + workingDirectory: project.directory.path, + exception: const io.ProcessException('pub', [ + 'pub', + '--suppress-analytics', + 'deps', + '--json', + ]), + ), + ]); +} diff --git a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart index bf0f9f2ddac96..3250d11bd5c96 100644 --- a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart +++ b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; @@ -530,6 +531,100 @@ void main() { }, ); + testUsingContext( + 'custom device command string interpolation end-to-end test', + () async { + final Completer runDebugCompleter = Completer(); + + final CustomDeviceConfig config = testConfig.copyWith( + platform: TargetPlatform.linux_arm64, + postBuildCommand: const [ + 'testpostbuild', + r'--buildMode=${buildMode}', + r'--icuDataPath=${icuDataPath}', + r'--engineRevision=${engineRevision}', + ], + runDebugCommand: const [ + 'testrundebug', + r'--buildMode=${buildMode}', + r'--icuDataPath=${icuDataPath}', + r'--engineRevision=${engineRevision}', + ], + ); + + final List commandArgumentsPattern = [ + RegExp(r'--buildMode=.*'), + RegExp(r'--icuDataPath=.*'), + RegExp(r'--engineRevision=.*'), + ]; + + final String expectedIcuDataPath = globals.artifacts!.getArtifactPath( + Artifact.icuData, + platform: config.platform, + ); + final String expectedEngineRevision = globals.flutterVersion.engineRevision; + + final List expectedCommandArguments = [ + '--buildMode=debug', + '--icuDataPath=$expectedIcuDataPath', + '--engineRevision=$expectedEngineRevision', + ]; + + final List expectedRunDebugCommand = [ + 'testrundebug', + ...expectedCommandArguments, + ]; + final List expectedPostBuildCommand = [ + 'testpostbuild', + ...expectedCommandArguments, + ]; + + final FakeProcessManager processManager = FakeProcessManager.list([ + FakeCommand( + command: ['testpostbuild', ...commandArgumentsPattern], + onRun: (List command) => expect(command, expectedPostBuildCommand), + ), + FakeCommand(command: config.uninstallCommand), + FakeCommand(command: config.installCommand), + FakeCommand( + command: ['testrundebug', ...commandArgumentsPattern], + completer: runDebugCompleter, + onRun: (List command) => expect(command, expectedRunDebugCommand), + stdout: 'The Dart VM service is listening on http://127.0.0.1:12345/abcd/\n', + ), + FakeCommand( + command: config.forwardPortCommand!, + stdout: testConfigForwardPortSuccessOutput, + ), + ]); + + // CustomDevice.startApp doesn't care whether we pass a prebuilt app or + // buildable app as long as we pass prebuiltApplication as false + final PrebuiltLinuxApp app = PrebuiltLinuxApp(executable: 'testexecutable'); + + // finally start actually testing things + final CustomDevice device = CustomDevice( + config: config, + logger: BufferLogger.test(), + processManager: processManager, + ); + + await device.startApp( + app, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + bundleBuilder: FakeBundleBuilder(), + ); + expect(runDebugCompleter.isCompleted, false); + + expect(await device.stopApp(app), true); + expect(runDebugCompleter.isCompleted, true); + }, + overrides: { + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }, + ); + testWithoutContext('CustomDevice screenshotting', () async { bool screenshotCommandWasExecuted = false; diff --git a/packages/flutter_tools/test/general.shard/dart/pub_deps_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_deps_test.dart index d8306d0bdde6e..5b1f546af8864 100644 --- a/packages/flutter_tools/test/general.shard/dart/pub_deps_test.dart +++ b/packages/flutter_tools/test/general.shard/dart/pub_deps_test.dart @@ -43,7 +43,7 @@ void main() { ); }); - testWithoutContext('fails on non-zero exit code', () async { + testWithoutContext('returns null on non-zero exit code', () async { final BufferLogger logger = BufferLogger.test(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final ProcessManager processManager = _dartPubDepsFails( @@ -62,14 +62,8 @@ void main() { ); await expectLater( - () => pub.deps(FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)), - throwsA( - isA().having( - (StateError e) => e.message, - 'message', - contains('dart pub --suppress-analytics deps --json failed'), - ), - ), + pub.deps(FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)), + completion(isNull), ); }); diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_test.dart index f080f6b0514ea..14cfc1c6ae160 100644 --- a/packages/flutter_tools/test/integration.shard/widget_preview_test.dart +++ b/packages/flutter_tools/test/integration.shard/widget_preview_test.dart @@ -5,18 +5,12 @@ import 'dart:async'; import 'dart:convert'; -import 'package:dtd/dtd.dart'; import 'package:file/file.dart'; import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/commands/widget_preview.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; -import 'package:flutter_tools/src/widget_preview/dtd_services.dart'; import 'package:process/process.dart'; import '../src/common.dart'; -import '../src/context.dart'; import 'test_data/basic_project.dart'; import 'test_utils.dart'; @@ -49,13 +43,10 @@ const List subsequentLaunchMessagesWeb = [ void main() { late Directory tempDir; Process? process; - Logger? logger; - DtdLauncher? dtdLauncher; final BasicProject project = BasicProject(); const ProcessManager processManager = LocalProcessManager(); setUp(() async { - logger = BufferLogger.test(); tempDir = createResolvedTempDirectorySync('widget_preview_test.'); await project.setUpIn(tempDir); }); @@ -63,15 +54,12 @@ void main() { tearDown(() async { process?.kill(); process = null; - await dtdLauncher?.dispose(); - dtdLauncher = null; tryToDelete(tempDir); }); Future runWidgetPreview({ required List expectedMessages, bool useWeb = false, - Uri? dtdUri, }) async { expect(expectedMessages, isNotEmpty); int i = 0; @@ -84,7 +72,6 @@ void main() { '--${WidgetPreviewStartCommand.kHeadlessWeb}' else '--${WidgetPreviewStartCommand.kUseFlutterDesktop}', - if (dtdUri != null) '--${FlutterGlobalOptions.kDtdUrl}=$dtdUri', ], workingDirectory: tempDir.path); final Completer completer = Completer(); @@ -116,6 +103,8 @@ void main() { }), ); await completer.future; + process!.kill(); + process = null; } group('flutter widget-preview start', () { @@ -143,38 +132,5 @@ void main() { // We shouldn't regenerate the scaffold after the initial run. await runWidgetPreview(expectedMessages: subsequentLaunchMessagesWeb, useWeb: true); }); - - testUsingContext('can connect to an existing DTD instance', () async { - dtdLauncher = DtdLauncher( - logger: logger!, - artifacts: globals.artifacts!, - processManager: globals.processManager, - ); - - // Start a DTD instance. - final Uri dtdUri = await dtdLauncher!.launch(); - - // Connect to it and listen to the WidgetPreviewScaffold stream. - // - // The preview scaffold will send a 'Connected' event on this stream once it has initialized - // and is ready. - final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri); - const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; - final Completer completer = Completer(); - dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) { - expect(event.stream, kWidgetPreviewScaffoldStream); - expect(event.kind, 'Connected'); - completer.complete(); - }); - await dtdConnection.streamListen(kWidgetPreviewScaffoldStream); - - // Start the widget preview and wait for the 'Connected' event. - await runWidgetPreview( - expectedMessages: firstLaunchMessagesWeb, - useWeb: true, - dtdUri: dtdUri, - ); - await completer.future; - }); }); } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd_services.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd_services.dart deleted file mode 100644 index eaff8e7fb0872..0000000000000 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd_services.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:dtd/dtd.dart'; - -/// Provides services, streams, and RPC invocations to interact with Flutter developer tooling. -class WidgetPreviewScaffoldDtdServices { - /// Environment variable for the DTD URI. - static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; - - /// Connects to the Dart Tooling Daemon (DTD) specified by the Flutter tool. - /// - /// If the connection is successful, the Widget Preview Scaffold will register services and - /// subscribe to various streams to interact directly with other tooling (e.g., IDEs). - Future connect() async { - final Uri dtdWsUri = Uri.parse( - const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar), - ); - _dtd = await DartToolingDaemon.connect(dtdWsUri); - unawaited( - _dtd.postEvent( - 'WidgetPreviewScaffold', - 'Connected', - const {}, - ), - ); - } - - late final DartToolingDaemon _dtd; -} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart index 568743d90edd0..8449dd0ec5b1e 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart @@ -13,7 +13,6 @@ import 'package:flutter/services.dart'; import 'package:stack_trace/stack_trace.dart'; import 'controls.dart'; -import 'dtd_services.dart'; import 'generated_preview.dart'; import 'utils.dart'; import 'widget_preview.dart'; @@ -411,8 +410,6 @@ class PreviewAssetBundle extends PlatformAssetBundle { /// the preview scaffold project which prevents us from being able to use hot /// restart to iterate on this file. Future mainImpl() async { - // TODO(bkonyi): store somewhere. - await WidgetPreviewScaffoldDtdServices().connect(); runApp(_WidgetPreviewScaffold()); } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml index 411eb4a65c29c..598273ede0be7 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: flutter_test: sdk: flutter # These will be replaced with proper constraints after the template is hydrated. - dtd: 2.5.0 flutter_lints: 5.0.0 stack_trace: 1.12.1 @@ -21,13 +20,7 @@ dependencies: characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - json_rpc_2: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" leak_tracker: 10.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -41,15 +34,10 @@ dependencies: string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - unified_analytics: 7.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vm_service: 15.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - web: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - web_socket_channel: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4b12 +# PUBSPEC CHECKSUM: 367e From eeb81b9a8a680295cbd9684d3487954d68fe1236 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 11 Apr 2025 12:11:52 -0700 Subject: [PATCH 02/53] Create `engine.version` (#166995) Point to the latest build on this branch, 72ee26e314f471012ee4ee60b5cf1831c0ed6a45. --- bin/internal/engine.version | 1 + 1 file changed, 1 insertion(+) create mode 100644 bin/internal/engine.version diff --git a/bin/internal/engine.version b/bin/internal/engine.version new file mode 100644 index 0000000000000..8f37841b17601 --- /dev/null +++ b/bin/internal/engine.version @@ -0,0 +1 @@ +72ee26e314f471012ee4ee60b5cf1831c0ed6a45 From 2cf78c55837b25ae07da36e33d76d14c32985a64 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:09:26 -0700 Subject: [PATCH 03/53] [CP-beta][skwasm] Use `queueMicrotask` instead of `postMessage` when single-threaded (#167154) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) ### Issue Link: https://github.com/flutter/flutter/issues/166905 ### Changelog Description: * [flutter/166905](https://github.com/flutter/flutter/issues/166905) Fixes a performance regression in skwasm when running in single-threaded mode. ### Impact Description: This fixes a significant regression in the skwasm renderer when running single-thraaded (i.e. in a non-`crossOriginIsolated` browsing context) ### Workaround: Is there a workaround for this issue? The only workaround is to run skwasm in a multi-threaded context or to disable skwasm. ### Risk: What is the risk level of this cherry-pick? - [ x ] Low This essentially returns the single-threaded renderer to the previous message passing strategy. ### Test Coverage: Are you confident that your fix is well-tested by automated tests? - [ x ] Yes ### Validation Steps: What are the steps to validate that this fix works? Built the Wonderous app and take a Chrome profile. --- .../web_ui/skwasm/library_skwasm_support.js | 99 +++++++++++-------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js b/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js index 702724e71d82e..ac98d599ca9c7 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js +++ b/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js @@ -8,51 +8,72 @@ mergeInto(LibraryManager.library, { $skwasm_support_setup__postset: 'skwasm_support_setup();', $skwasm_support_setup: function() { - // This value represents the difference between the time origin of the main - // thread and whichever web worker this code is running on. This is so that - // when we report frame timings, that they are in the same time domain - // regardless of whether they are captured on the main thread or the web - // worker. - let timeOriginDelta = 0; - skwasm_registerMessageListener = function(threadId, listener) { - const eventListener = function({data}) { - const skwasmMessage = data.skwasmMessage; - if (!skwasmMessage) { - return; + if (Module["skwasmSingleThreaded"]) { + _skwasm_isSingleThreaded = function() { + return true; + }; + + let messageListener; + // In single threaded mode, we simply invoke the message listener as a + // microtask, as it's much cheaper than doing a full postMessage + skwasm_registerMessageListener = function(threadId, listener) { + messageListener = listener; + } + skwasm_getCurrentTimestamp = function() { + return performance.now(); + }; + skwasm_postMessage = function(message, transfers, threadId) { + // If we're in single-threaded mode, we shouldn't use postMessage, as + // it ends up being quite expensive. Instead, just queue a microtask. + queueMicrotask(() => messageListener(message)); + }; + } else { + _skwasm_isSingleThreaded = function() { + return false; + }; + + // This value represents the difference between the time origin of the main + // thread and whichever web worker this code is running on. This is so that + // when we report frame timings, that they are in the same time domain + // regardless of whether they are captured on the main thread or the web + // worker. + let timeOriginDelta = 0; + skwasm_registerMessageListener = function(threadId, listener) { + const eventListener = function({data}) { + const skwasmMessage = data.skwasmMessage; + if (!skwasmMessage) { + return; + } + if (skwasmMessage == 'syncTimeOrigin') { + timeOriginDelta = performance.timeOrigin - data.timeOrigin; + return; + } + listener(data); + }; + if (!threadId) { + addEventListener("message", eventListener); + } else { + _wasmWorkers[threadId].addEventListener("message", eventListener); + _wasmWorkers[threadId].postMessage({ + skwasmMessage: 'syncTimeOrigin', + timeOrigin: performance.timeOrigin, + }); } - if (skwasmMessage == 'syncTimeOrigin') { - timeOriginDelta = performance.timeOrigin - data.timeOrigin; - return; + }; + skwasm_getCurrentTimestamp = function() { + return performance.now() + timeOriginDelta; + }; + skwasm_postMessage = function(message, transfers, threadId) { + if (threadId) { + _wasmWorkers[threadId].postMessage(message, { transfer: transfers } ); + } else { + postMessage(message, { transfer: transfers }); } - listener(data); }; - if (!threadId) { - addEventListener("message", eventListener); - } else { - _wasmWorkers[threadId].addEventListener("message", eventListener); - _wasmWorkers[threadId].postMessage({ - skwasmMessage: 'syncTimeOrigin', - timeOrigin: performance.timeOrigin, - }); - } - }; - skwasm_getCurrentTimestamp = function() { - return performance.now() + timeOriginDelta; - }; - skwasm_postMessage = function(message, transfers, threadId) { - if (threadId) { - _wasmWorkers[threadId].postMessage(message, transfers); - } else { - postMessage(message, transfers); - } - }; + } const handleToCanvasMap = new Map(); const associatedObjectsMap = new Map(); - - _skwasm_isSingleThreaded = function() { - return Module["skwasmSingleThreaded"]; - }; _skwasm_setAssociatedObjectOnThread = function(threadId, pointer, object) { skwasm_postMessage({ skwasmMessage: 'setAssociatedObject', From 0089210951e8f07ebd8c6d078dd8d2bc456a898a Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:32:01 -0700 Subject: [PATCH 04/53] [CP-beta]Run tests on either iOS 17 or iOS 18 (#167216) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/148968 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Update CI configurations and tests to use either iOS 17 or iOS 18 devices. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Runs tests on either iOS 17 or iOS 18 devices. ### Workaround: Is there a workaround for this issue? No ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? N/A --- .ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 2571271d5ef15..8d644cd5dceb5 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -286,7 +286,7 @@ platform_properties: ] os: Mac-14 cpu: x86 - device_os: iOS-17 + device_os: iOS-17|iOS-18 $flutter/osx_sdk : >- { "sdk_version": "16c5032a" @@ -304,7 +304,7 @@ platform_properties: ] os: Mac-14 cpu: x86 - device_os: iOS-17 + device_os: iOS-17|iOS-18 $flutter/osx_sdk : >- { "sdk_version": "16c5032a" @@ -322,7 +322,7 @@ platform_properties: ] os: Mac-14 cpu: arm64 - device_os: iOS-17 + device_os: iOS-17|iOS-18 $flutter/osx_sdk : >- { "sdk_version": "16c5032a" @@ -5380,7 +5380,7 @@ targets: ["devicelab", "ios", "mac"] task_name: flutter_gallery__transition_perf_e2e_ios drone_dimensions: > - ["device_os=iOS-17","os=Mac-14", "cpu=x86"] + ["device_os=iOS-17|iOS-18","os=Mac-14", "cpu=x86"] - name: Mac_ios animated_blur_backdrop_filter_perf_ios__timeline_summary recipe: devicelab/devicelab_drone From 382be0028d370607f76215a9be322e5514b263e0 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:11:00 -0700 Subject: [PATCH 05/53] [flutter-3.32-candidate.0] Update Dart DEPS (#167303) Updates the Dart DEPS for 3.8.0-278.2.beta: https://github.com/dart-lang/sdk/commits/beta/ This PR was created by running `tools/dart/create_updated_flutter_deps.py -d /path/to/dart-sdk/sdk/DEPS` I believe we also need to update the Dart SHA to [9003f7927a24fd87c34154e04585bfbcfd4d9188](https://github.com/dart-lang/sdk/commit/9003f7927a24fd87c34154e04585bfbcfd4d9188) --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index c57b9b2fe5389..c0834e3a811ef 100644 --- a/DEPS +++ b/DEPS @@ -63,7 +63,7 @@ vars = { 'dart_binaryen_rev': 'b4bdcc33115b31758c56b83bb9de4642c411a042', 'dart_boringssl_rev': '8d8df26fc54f6e5fb0bf404a76ce973c0413eafc', 'dart_core_rev': '7a80178ca72b01b0efb99a9a9a654d83ca21d6b7', - 'dart_devtools_rev': '1fb2f4ce5099042b7f2dfa93dec675a21861d21f', + 'dart_devtools_rev': '74b3c517ed8e6f52c3e0b84a066e3f3a623b7be9', 'dart_ecosystem_rev': '391a80ccb774cfebe4865bcd7e933d1ab016eea5', 'dart_http_rev': '6fabf06b90d962cf9a6c009bbe919902ff1a1471', 'dart_i18n_rev': 'de1943629469719bf34269bf90fcdbe9334a73f3', @@ -308,7 +308,7 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@b4bdcc33115b31758c56b83bb9de4642c411a042', 'engine/src/flutter/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:1fb2f4ce5099042b7f2dfa93dec675a21861d21f'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:74b3c517ed8e6f52c3e0b84a066e3f3a623b7be9'}]}, 'engine/src/flutter/third_party/dart/third_party/pkg/core': Var('dart_git') + '/core.git' + '@' + Var('dart_core_rev'), From b71a1214d283a517f2fe59155b419e234644d768 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 17 Apr 2025 17:21:38 -0700 Subject: [PATCH 06/53] Update `engine.version` (#167295) Points to the latest git SHA of 3.32 to include 382be0028d370607f76215a9be322e5514b263e0. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 8f37841b17601..b6a16f4c208c1 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -72ee26e314f471012ee4ee60b5cf1831c0ed6a45 +382be0028d370607f76215a9be322e5514b263e0 From d3abd00e69a89a615e13969c02832ef1e0119639 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 18 Apr 2025 10:08:55 -0700 Subject: [PATCH 07/53] Make a NOP edit to (re)-trigger postsubmits (#167407) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76698e34d95df..603f9ec54eaed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ docs/releases/Hotfix-Documentation-Best-Practices.md INTERNAL NOTE --> + + + ## Flutter 3.29 Changes ### [3.29.2](https://github.com/flutter/flutter/releases/tag/3.29.2) From 80230cc7c4954c4502761b7d5722e5480ea8f43e Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:32:08 -0700 Subject: [PATCH 08/53] [CP-beta]Refine developer message for announcement use (#167505) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? This was a follow-up to https://github.com/flutter/flutter/pull/165195 and part of issue https://github.com/flutter/flutter/issues/165510 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Developer warning message in `SemanticService.announce` is not clear when using internal flutter widgets. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Developers see non-actionable warning messages when Accessibility Services are on and developers are using widgets like TimePicker, DatePicker, ExpansionTile, and Form. ### Workaround: Is there a workaround for this issue? No workaround. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Run the example below: https://github.com/flutter/flutter/blob/5491c8c146441d3126aff91beaa3fb5df6d710d0/examples/api/lib/material/time_picker/show_time_picker.0.dart#L10 When opening the time picker, the corrected warning message should show. --- .../android/io/flutter/view/AccessibilityBridge.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java index a6cc440ed506a..3ae1d86999274 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -308,7 +308,11 @@ public void announce(@NonNull String message) { TAG, "Using AnnounceSemanticsEvent for accessibility is deprecated on Android. " + "Migrate to using semantic properties for a more robust and accessible " - + "user experience. See https://developer.android.com/reference/android/view/View#announceForAccessibility(java.lang.CharSequence)"); + + "user experience.\n" + + "Flutter: If you are unsure why you are seeing this bug, it might be because " + + "you are using a widget that calls this method. See https://github.com/flutter/flutter/issues/165510 " + + "for more details.\n" + + "Android documentation: https://developer.android.com/reference/android/view/View#announceForAccessibility(java.lang.CharSequence)"); } rootAccessibilityView.announceForAccessibility(message); } From 7e2815c9574c3680818093b5d84379bfe4040440 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:33:03 -0700 Subject: [PATCH 09/53] [flutter-3.32-candidate.0] Update Dart revision to 9003f7927a24fd87c34154e04585bfbcfd4d9188 (#167514) * Updated Dart revision to [`9003f7927a24fd87c34154e04585bfbcfd4d9188`](https://github.com/dart-lang/sdk/commit/9003f7927a24fd87c34154e04585bfbcfd4d9188) * Ran `tools/dart/create_updated_flutter_deps.py -d /path/to/dart-sdk/sdk/DEPS` Note: I believe it's expected that none of the Dart SDK deps were updated, since the [Dart beta branch](https://github.com/dart-lang/sdk/commits/beta/) has not changed since https://github.com/flutter/flutter/pull/167303 was submitted. --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c0834e3a811ef..ecb4b3edfb8b6 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '0d6811928830b87e36a0f49eb7fe554c308d3699', + 'dart_revision': '9003f7927a24fd87c34154e04585bfbcfd4d9188', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From a2e3722fc63c5e44ff70c67053142f4bd5a67564 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 22 Apr 2025 10:34:23 -0700 Subject: [PATCH 10/53] Update `engine.version` (#167575) ... to prepare to publish another beta release. 7e2815c9574c3680818093b5d84379bfe4040440 is the latest merged commit: https://github.com/flutter/flutter/commits/flutter-3.32-candidate.0/. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index b6a16f4c208c1..dd16504df73be 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -382be0028d370607f76215a9be322e5514b263e0 +7e2815c9574c3680818093b5d84379bfe4040440 From 030e2a5517c378cbc15a6eb7505a0a1508fe63e3 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:08:49 -0700 Subject: [PATCH 11/53] [CP-beta]Unbreak https://github.com/flutter/flutter/pull/164034 (#167736) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/167247 We identified a breaking change in beta, this change un-breaks it. ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Restore RenderConstrainedLayoutBuilder with default layoutInfo implementation to undo a breaking change. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) A class was renamed, which left developers with a "class not found" error as their only guide. ### Workaround: Is there a workaround for this issue? Nope. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Classes that mixin RenderConstrainedLayoutBuilder are no longer broken and the code can compile. --- .../lib/src/widgets/layout_builder.dart | 23 ++++++++++++------- .../src/widgets/sliver_layout_builder.dart | 8 ++----- .../test/widgets/layout_builder_test.dart | 4 ---- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/flutter/lib/src/widgets/layout_builder.dart b/packages/flutter/lib/src/widgets/layout_builder.dart index 79980bb458319..45e9608f5907c 100644 --- a/packages/flutter/lib/src/widgets/layout_builder.dart +++ b/packages/flutter/lib/src/widgets/layout_builder.dart @@ -302,8 +302,12 @@ class _LayoutBuilderElement extends RenderObjectElement { /// Provides a [layoutCallback] implementation which, if needed, invokes /// [AbstractLayoutBuilder]'s builder callback. /// -/// Implementers must provide a [layoutInfo] implementation that is safe to -/// access in [layoutCallback], which is called in [performLayout]. +/// Implementers can override the [layoutInfo] implementation with a value +/// that is safe to access in [layoutCallback], which is called in +/// [performLayout]. The default [layoutInfo] returns the incoming +/// [Constraints]. +/// +/// This mixin replaces [RenderConstrainedLayoutBuilder]. mixin RenderAbstractLayoutBuilderMixin on RenderObjectWithChildMixin, RenderObjectWithLayoutCallbackMixin { LayoutCallback? _callback; @@ -334,11 +338,18 @@ mixin RenderAbstractLayoutBuilderMixin constraints as LayoutInfoType; } +/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with +/// the the same `LayoutInfoType`. +/// +/// Use [RenderAbstractLayoutBuilderMixin] instead, which replaces this mixin. +typedef RenderConstrainedLayoutBuilder = + RenderAbstractLayoutBuilderMixin; + /// Builds a widget tree that can depend on the parent widget's size. /// /// Similar to the [Builder] widget except that the framework calls the [builder] @@ -476,10 +487,6 @@ class _RenderLayoutBuilder extends RenderBox return true; } - - @protected - @override - BoxConstraints get layoutInfo => constraints; } FlutterErrorDetails _reportException( diff --git a/packages/flutter/lib/src/widgets/sliver_layout_builder.dart b/packages/flutter/lib/src/widgets/sliver_layout_builder.dart index a7daaae35d86d..dd06fbd1edb73 100644 --- a/packages/flutter/lib/src/widgets/sliver_layout_builder.dart +++ b/packages/flutter/lib/src/widgets/sliver_layout_builder.dart @@ -29,7 +29,7 @@ class SliverLayoutBuilder extends ConstrainedLayoutBuilder { const SliverLayoutBuilder({super.key, required super.builder}); @override - RenderAbstractLayoutBuilderMixin createRenderObject( + RenderConstrainedLayoutBuilder createRenderObject( BuildContext context, ) => _RenderSliverLayoutBuilder(); } @@ -38,17 +38,13 @@ class _RenderSliverLayoutBuilder extends RenderSliver with RenderObjectWithChildMixin, RenderObjectWithLayoutCallbackMixin, - RenderAbstractLayoutBuilderMixin { + RenderConstrainedLayoutBuilder { @override double childMainAxisPosition(RenderObject child) { assert(child == this.child); return 0; } - @protected - @override - SliverConstraints get layoutInfo => constraints; - @override void performLayout() { runLayoutCallback(); diff --git a/packages/flutter/test/widgets/layout_builder_test.dart b/packages/flutter/test/widgets/layout_builder_test.dart index e11dfd52021ef..2133ab111ff1a 100644 --- a/packages/flutter/test/widgets/layout_builder_test.dart +++ b/packages/flutter/test/widgets/layout_builder_test.dart @@ -1067,10 +1067,6 @@ class _RenderSmartLayoutBuilder extends RenderProxyBox onChildWasPainted(extraOffset); } } - - @protected - @override - BoxConstraints get layoutInfo => constraints; } class _LayoutSpy extends LeafRenderObjectWidget { From 3d03881a90cd15a3ac80e2e194e14c4a433433eb Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:42:07 -0700 Subject: [PATCH 12/53] [CP-beta]Move `backfill: "false"` out of properties, make it a boolean. (#167831) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/167755 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A (CI Infrastructure Only) ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Allows removing technical debt from Cocoon. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? It is already live on the master channel. --- .ci.yaml | 2 +- engine/src/flutter/.ci.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 8d644cd5dceb5..5cb391f8a5a0f 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -585,6 +585,7 @@ targets: - name: Linux docs_publish recipe: flutter/docs presubmit: false + backfill: false timeout: 60 dimensions: os: "Linux" @@ -603,7 +604,6 @@ targets: ] tags: > ["framework", "hostonly", "linux"] - backfill: "false" validation: docs validation_name: Docs firebase_project: main-docs-flutter-prod diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml index 49098544a4656..041679665f668 100644 --- a/engine/src/flutter/.ci.yaml +++ b/engine/src/flutter/.ci.yaml @@ -263,9 +263,9 @@ targets: recipe: engine_v2/engine_v2 bringup: true timeout: 120 + backfill: false properties: release_build: "false" - backfill: "false" config_name: linux_host_engine_ddm dependencies: >- [ @@ -305,10 +305,10 @@ targets: recipe: engine_v2/engine_v2 bringup: true timeout: 120 + backfill: false properties: config_name: linux_android_aot_engine_ddm release_build: "false" - backfill: "false" # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform drone_dimensions: @@ -343,9 +343,9 @@ targets: recipe: engine_v2/engine_v2 bringup: true timeout: 120 + backfill: false properties: release_build: "false" - backfill: "false" config_name: linux_android_debug_engine_ddm # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform From 5cb9ca93eae4b0ceee9fa8307fd6f9fea578190c Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 29 Apr 2025 07:04:22 -0700 Subject: [PATCH 13/53] [CP-beta]Fix `late` variable non-assignment when WASM is enabled (#167987) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/167887 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Tool crashes while running WASM web apps ### Workaround: Is there a workaround for this issue? No ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? See attached issue --- .../lib/src/isolated/resident_web_runner.dart | 65 +++++++------- .../resident_web_runner_test.dart | 89 +++++++++++++++++++ 2 files changed, 123 insertions(+), 31 deletions(-) diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index a4ef4e4df769d..55361794ff615 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -435,7 +435,8 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). final String targetPlatform = getNameForTargetPlatform(TargetPlatform.web_javascript); final String sdkName = await device!.device!.sdkNameAndVersion; - late UpdateFSReport report; + // Will be null if there is no report. + final UpdateFSReport? report; if (debuggingOptions.buildInfo.isDebug && !debuggingOptions.webUseWasm) { await runSourceGenerators(); // Don't reset the resident compiler for web, since the extra recompile is @@ -469,6 +470,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). return OperationResult(1, 'Failed to recompile application.'); } } else { + report = null; try { final WebBuilder webBuilder = WebBuilder( logger: _logger, @@ -490,8 +492,9 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). } } - late Duration reloadDuration; - late Duration reassembleDuration; + // Both will be null when not assigned. + Duration? reloadDuration; + Duration? reassembleDuration; try { if (!deviceIsDebuggable) { _logger.printStatus('Recompile complete. Page requires refresh.'); @@ -578,12 +581,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: true, reason: reason, overallTimeInMs: elapsed.inMilliseconds, - syncedBytes: report.syncedBytes, - invalidatedSourcesCount: report.invalidatedSourcesCount, - transferTimeInMs: report.transferDuration.inMilliseconds, - compileTimeInMs: report.compileDuration.inMilliseconds, - findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, - scannedSourcesCount: report.scannedSourcesCount, + syncedBytes: report?.syncedBytes, + invalidatedSourcesCount: report?.invalidatedSourcesCount, + transferTimeInMs: report?.transferDuration.inMilliseconds, + compileTimeInMs: report?.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report?.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report?.scannedSourcesCount, ).send(); _analytics.send( Event.hotRunnerInfo( @@ -594,12 +597,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: true, reason: reason, overallTimeInMs: elapsed.inMilliseconds, - syncedBytes: report.syncedBytes, - invalidatedSourcesCount: report.invalidatedSourcesCount, - transferTimeInMs: report.transferDuration.inMilliseconds, - compileTimeInMs: report.compileDuration.inMilliseconds, - findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, - scannedSourcesCount: report.scannedSourcesCount, + syncedBytes: report?.syncedBytes, + invalidatedSourcesCount: report?.invalidatedSourcesCount, + transferTimeInMs: report?.transferDuration.inMilliseconds, + compileTimeInMs: report?.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report?.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report?.scannedSourcesCount, ), ); } else { @@ -618,14 +621,14 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: false, reason: reason, overallTimeInMs: elapsed.inMilliseconds, - syncedBytes: report.syncedBytes, - invalidatedSourcesCount: report.invalidatedSourcesCount, - transferTimeInMs: report.transferDuration.inMilliseconds, - compileTimeInMs: report.compileDuration.inMilliseconds, - findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, - scannedSourcesCount: report.scannedSourcesCount, - reassembleTimeInMs: reassembleDuration.inMilliseconds, - reloadVMTimeInMs: reloadDuration.inMilliseconds, + syncedBytes: report?.syncedBytes, + invalidatedSourcesCount: report?.invalidatedSourcesCount, + transferTimeInMs: report?.transferDuration.inMilliseconds, + compileTimeInMs: report?.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report?.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report?.scannedSourcesCount, + reassembleTimeInMs: reassembleDuration?.inMilliseconds, + reloadVMTimeInMs: reloadDuration?.inMilliseconds, ).send(); _analytics.send( Event.hotRunnerInfo( @@ -636,14 +639,14 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: false, reason: reason, overallTimeInMs: elapsed.inMilliseconds, - syncedBytes: report.syncedBytes, - invalidatedSourcesCount: report.invalidatedSourcesCount, - transferTimeInMs: report.transferDuration.inMilliseconds, - compileTimeInMs: report.compileDuration.inMilliseconds, - findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, - scannedSourcesCount: report.scannedSourcesCount, - reassembleTimeInMs: reassembleDuration.inMilliseconds, - reloadVMTimeInMs: reloadDuration.inMilliseconds, + syncedBytes: report?.syncedBytes, + invalidatedSourcesCount: report?.invalidatedSourcesCount, + transferTimeInMs: report?.transferDuration.inMilliseconds, + compileTimeInMs: report?.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report?.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report?.scannedSourcesCount, + reassembleTimeInMs: reassembleDuration?.inMilliseconds, + reloadVMTimeInMs: reloadDuration?.inMilliseconds, ), ); } diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 93fc2c05ae00e..03c15c46fafd6 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -17,6 +17,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/dart/pub.dart'; @@ -46,6 +47,7 @@ import '../src/fake_pub_deps.dart'; import '../src/fake_vm_services.dart'; import '../src/fakes.dart' as test_fakes; import '../src/package_config.dart'; +import '../src/test_build_system.dart'; const List kAttachLogExpectations = [ FakeVmServiceRequest(method: 'streamListen', args: {'streamId': 'Stdout'}), @@ -914,6 +916,88 @@ name: my_app }, ); + // Regression test for https://github.com/flutter/flutter/issues/167887. + testUsingContext( + 'WASM builds report analysis without crashing', + () async { + final BufferLogger logger = BufferLogger.test(); + final ResidentRunner residentWebRunner = setUpResidentRunner( + flutterDevice, + logger: logger, + systemClock: SystemClock.fixed(DateTime(2001)), + debuggingOptions: DebuggingOptions.enabled( + const BuildInfo( + BuildMode.debug, + null, + trackWidgetCreation: true, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + // Hot reload only supported with these flags for now. + extraFrontEndOptions: kDdcLibraryBundleFlags, + ), + webUseWasm: true, + ), + ); + fakeVmServiceHost = FakeVmServiceHost( + requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: kReloadSourcesServiceName, + args: {'isolateId': ''}, + jsonResponse: {'type': 'ReloadReport', 'success': true}, + ), + const FakeVmServiceRequest( + method: 'ext.flutter.reassemble', + jsonResponse: {'type': 'ReloadReport', 'success': true}, + ), + const FakeVmServiceRequest( + method: 'streamListen', + args: {'streamId': 'Isolate'}, + ), + ], + ); + setupMocks(); + final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher(); + final FakeProcess process = FakeProcess(); + final Chromium chrome = Chromium( + 1, + chromeConnection, + chromiumLauncher: chromiumLauncher, + process: process, + logger: logger, + ); + chromiumLauncher.setInstance(chrome); + + flutterDevice.device = GoogleChromeDevice( + fileSystem: fileSystem, + chromiumLauncher: chromiumLauncher, + logger: BufferLogger.test(), + platform: FakePlatform(), + processManager: FakeProcessManager.any(), + ); + webDevFS.report = UpdateFSReport(success: true); + + final Completer connectionInfoCompleter = + Completer(); + unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter)); + final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; + + expect(debugConnectionInfo, isNotNull); + + final OperationResult result = await residentWebRunner.restart(); + expect(logger.statusText, contains('Reloaded application in')); + expect(result.code, 0); + }, + overrides: { + Analytics: () => fakeAnalytics, + BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + FeatureFlags: enableExplicitPackageDependencies, + Pub: FakePubWithPrimedDeps.new, + }, + ); + // Hot restart is available with and without the DDC library bundle format. // Test one extra config where `fullRestart` is false without the DDC library // bundle format - we should do a hot restart in this case because hot reload @@ -1995,6 +2079,11 @@ class FakeChromeTab extends Fake implements ChromeTab { class FakeWipConnection extends Fake implements WipConnection { @override final WipDebugger debugger = FakeWipDebugger(); + + @override + Future sendCommand(String method, [Map? params]) async { + return WipResponse({'id': 0, 'result': {}}); + } } /// A test implementation of the [ChromiumLauncher] that launches a fixed instance. From 6423adf0ddbe82548753529505de30ff9b0277ef Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 29 Apr 2025 10:43:15 -0700 Subject: [PATCH 14/53] Update `engine.version` to last SHA. (#167999) Updates to https://github.com/flutter/flutter/commit/5cb9ca93eae4b0ceee9fa8307fd6f9fea578190c. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index dd16504df73be..e0c2ba404533e 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -7e2815c9574c3680818093b5d84379bfe4040440 +5cb9ca93eae4b0ceee9fa8307fd6f9fea578190c From 453dd7174cc6555e3e9eb5f006c86e6ec221a0d2 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:15:41 -0700 Subject: [PATCH 15/53] [CP-beta]feat: Arbitrary format options for localizations generation (#102983) (#168035) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? < Replace with issue link here > ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples < Replace with changelog description here > ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) < Replace with impact description here > ### Workaround: Is there a workaround for this issue? < Replace with workaround here > ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? < Replace with validation steps here > --- .../localizations/localizations_utils.dart | 2 +- .../hermetic/generate_localizations_test.dart | 80 ++++++++++++++++++- .../generate_localizations_test.dart | 16 ++-- 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart index 283024010883e..6389b7fbefeac 100644 --- a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart +++ b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart @@ -356,7 +356,7 @@ class LocalizationOptions { syntheticPackage = syntheticPackage ?? !featureFlags.isExplicitPackageDependenciesEnabled, requiredResourceAttributes = requiredResourceAttributes ?? false, nullableGetter = nullableGetter ?? true, - format = format ?? false, + format = format ?? true, useEscaping = useEscaping ?? false, suppressWarnings = suppressWarnings ?? false, relaxSyntax = relaxSyntax ?? false, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart index d937de7adc547..6619c631fd92b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart @@ -202,7 +202,7 @@ flutter: fileSystem: fileSystem, logger: logger, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); await createTestCommandRunner(command).run(['gen-l10n']); @@ -241,7 +241,7 @@ flutter: fileSystem: fileSystem, logger: logger, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); await createTestCommandRunner(command).run(['gen-l10n']); expect(command.usage, contains(' If this value is set to false, then ')); @@ -299,6 +299,43 @@ flutter: }, ); + testUsingContext( + 'dart format is not run when --no-format is passed', + () async { + final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')) + ..createSync(recursive: true); + arbFile.writeAsStringSync(''' +{ + "helloWorld": "Hello, World!", + "@helloWorld": { + "description": "Sample description" + } +}'''); + final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec); + + final GenerateLocalizationsCommand command = GenerateLocalizationsCommand( + fileSystem: fileSystem, + logger: logger, + artifacts: artifacts, + processManager: processManager, + ); + + await createTestCommandRunner(command).run(['gen-l10n', '--no-format']); + + final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('lib', 'l10n')); + expect(outputDirectory.existsSync(), true); + expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true); + expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true); + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + FeatureFlags: enableExplicitPackageDependencies, + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }, + ); + testUsingContext( 'dart format is run when format: true is passed into l10n.yaml', () async { @@ -348,6 +385,45 @@ format: true }, ); + testUsingContext( + 'dart format is not running when format: false is passed into l10n.yaml', + () async { + final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')) + ..createSync(recursive: true); + arbFile.writeAsStringSync(''' +{ + "helloWorld": "Hello, World!", + "@helloWorld": { + "description": "Sample description" + } +}'''); + final File configFile = fileSystem.file('l10n.yaml')..createSync(); + configFile.writeAsStringSync(''' +format: false +'''); + final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec); + final GenerateLocalizationsCommand command = GenerateLocalizationsCommand( + fileSystem: fileSystem, + logger: logger, + artifacts: artifacts, + processManager: processManager, + ); + await createTestCommandRunner(command).run(['gen-l10n']); + + final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('lib', 'l10n')); + expect(outputDirectory.existsSync(), true); + expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true); + expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true); + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + FeatureFlags: enableExplicitPackageDependencies, + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }, + ); + // Regression test for https://github.com/flutter/flutter/issues/119594 testUsingContext( 'dart format is working when the untranslated messages file is produced', diff --git a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart index fcd5f21e60f00..1a4b8babb7fbf 100644 --- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart @@ -76,7 +76,6 @@ void main() { late MemoryFileSystem fs; late BufferLogger logger; late Artifacts artifacts; - late ProcessManager processManager; late String defaultL10nPathString; late String syntheticPackagePath; late String syntheticL10nPackagePath; @@ -152,7 +151,6 @@ void main() { fs = MemoryFileSystem.test(); logger = BufferLogger.test(); artifacts = Artifacts.test(); - processManager = FakeProcessManager.empty(); defaultL10nPathString = fs.path.join('lib', 'l10n'); syntheticPackagePath = fs.path.join('.dart_tool', 'flutter_gen'); @@ -757,7 +755,7 @@ flutter: projectDir: projectDir, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); }); @@ -780,7 +778,7 @@ flutter: projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); }); @@ -809,7 +807,7 @@ flutter: projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); expect(generator.inputDirectory.path, '/lib/l10n/'); @@ -880,7 +878,7 @@ flutter: projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ), throwsToolExit( message: @@ -916,7 +914,7 @@ flutter:\r projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); final String content = getInPackageGeneratedFileContent(locale: 'en'); expect(content, contains('\r\n')); @@ -940,7 +938,7 @@ flutter:\r projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), ''' @@ -973,7 +971,7 @@ class AppLocalizationsEn extends AppLocalizations { projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, artifacts: artifacts, - processManager: processManager, + processManager: FakeProcessManager.any(), ); expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), ''' From 3bd718ee44049e25d33327ee9886ec30c97ffa33 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 30 Apr 2025 10:11:08 -0700 Subject: [PATCH 16/53] Update `engine.version` (#168069) Rolls up to https://github.com/flutter/flutter/commit/453dd7174cc6555e3e9eb5f006c86e6ec221a0d2. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index e0c2ba404533e..ff5873be07f1a 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -5cb9ca93eae4b0ceee9fa8307fd6f9fea578190c +453dd7174cc6555e3e9eb5f006c86e6ec221a0d2 From a5ca02f31c6d7b90d6478196a95915274ce44eba Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 30 Apr 2025 14:11:35 -0700 Subject: [PATCH 17/53] [CP][Impeller] report application version info as 2.0 (#168086) Cherry pick of https://github.com/flutter/flutter/pull/167961 --- .../impeller/renderer/backend/vulkan/context_vk.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc index 5b8400d5620f7..6066065ca2b34 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc @@ -205,7 +205,18 @@ void ContextVK::Setup(Settings settings) { } vk::ApplicationInfo application_info; - application_info.setApplicationVersion(VK_API_VERSION_1_0); + + // Use the same encoding macro as vulkan versions, but otherwise application + // version is intended to be the version of the Impeller engine. This version + // information, along with the application name below is provided to allow + // IHVs to make optimizations and/or disable functionality based on knowledge + // of the engine version (for example, to work around bugs). We don't tie this + // to the overall Flutter version as that version is not yet defined when the + // engine is compiled. Instead we can manually bump it occassionally. + // + // variant, major, minor, patch + application_info.setApplicationVersion( + VK_MAKE_API_VERSION(0, 2, 0, 0) /*version 2.0.0*/); application_info.setApiVersion(VK_API_VERSION_1_1); application_info.setEngineVersion(VK_API_VERSION_1_0); application_info.setPEngineName("Impeller"); From 5f0ca4df711d6796e10fbec22a14e999d32df3d2 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 1 May 2025 08:53:23 -0700 Subject: [PATCH 18/53] [CP-beta]Remove `release_build: "false"` which is interpreted as` true` due to a recipes bug. (#168147) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/pull/168098 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Works around infrastructure bug that would cause the release build process for release candidates to take longer than expected. ### Workaround: Is there a workaround for this issue? Yes, wait longer. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Already validated on master. --- engine/src/flutter/.ci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml index 041679665f668..4b6f795d4d5c3 100644 --- a/engine/src/flutter/.ci.yaml +++ b/engine/src/flutter/.ci.yaml @@ -308,7 +308,6 @@ targets: backfill: false properties: config_name: linux_android_aot_engine_ddm - release_build: "false" # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform drone_dimensions: @@ -345,7 +344,6 @@ targets: timeout: 120 backfill: false properties: - release_build: "false" config_name: linux_android_debug_engine_ddm # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform From 87cd28951eb6ba6660129d78075cf703af6c2cf4 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 1 May 2025 15:33:15 -0700 Subject: [PATCH 19/53] [CP-beta]Only build and download Fuchsia artifacts on `master` (#168169) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/168088 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) N/A: Infrastructure only change. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? N/A --- .ci.yaml | 3 +++ engine/src/flutter/.ci.yaml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.ci.yaml b/.ci.yaml index 5cb391f8a5a0f..6404c288c0543 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -836,6 +836,9 @@ targets: - name: Linux fuchsia_precache recipe: flutter/flutter_drone timeout: 60 + enabled_branches: + # Don't run this on release branches + - master presubmit: false properties: shard: fuchsia_precache diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml index 4b6f795d4d5c3..2c476704ac02d 100644 --- a/engine/src/flutter/.ci.yaml +++ b/engine/src/flutter/.ci.yaml @@ -143,6 +143,9 @@ targets: - name: Linux linux_fuchsia recipe: engine_v2/engine_v2 timeout: 120 + enabled_branches: + # Don't run this on release branches + - master properties: release_build: "true" config_name: linux_fuchsia From 8545480266f3a5fd8aa421345ef2779b01f31083 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 6 May 2025 12:29:22 -0700 Subject: [PATCH 20/53] [beta] CP request for https://github.com/flutter/flutter/pull/167677 (#168386) ### CP request for https://github.com/flutter/flutter/pull/167677 into flutter-3.32-candidate.0 **Impacted Users:** Some subset of widget inspector users with a specific configuration of `package:go_router`, see upvotes and comments on https://github.com/flutter/flutter/issues/166118. **Impact Description:** Depending on how users have configured their routes using `package:go_router`, enabling / disabling the widget inspector can be destructive to their app's routing state, preventing them from inspecting widgets on secondary screens of their app. **Workaround:** No workaround available. **Risk:** Low **Test Coverage:** Yes, tests were added and this has been manually tested as well **Validation Steps:** - Run a Flutter app - Open the DevTools Inspector for the running app - Toggle "Select widget mode" - An additional button has been added to the on-device inspector that allows developers to both interact with their app (e.g. navigate to a new page) and select widgets while in Widget Selection mode. See gif below. ![new_on_device_inspector](https://github.com/user-attachments/assets/7202ccb3-05cd-4262-be70-9cd08513933a) --- packages/flutter/lib/src/cupertino/app.dart | 131 +++++-- packages/flutter/lib/src/material/app.dart | 163 +++++++-- packages/flutter/lib/src/widgets/app.dart | 23 +- packages/flutter/lib/src/widgets/binding.dart | 11 + .../lib/src/widgets/widget_inspector.dart | 328 +++++++++++++++--- .../test/widgets/widget_inspector_test.dart | 136 +++++++- 6 files changed, 668 insertions(+), 124 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart index 0b7ce65d21cbd..fc13d4e99ae7b 100644 --- a/packages/flutter/lib/src/cupertino/app.dart +++ b/packages/flutter/lib/src/cupertino/app.dart @@ -15,6 +15,7 @@ import 'package:flutter/widgets.dart'; import 'button.dart'; import 'colors.dart'; +import 'constants.dart'; import 'icons.dart'; import 'interface_level.dart'; import 'localizations.dart'; @@ -534,46 +535,44 @@ class _CupertinoAppState extends State { Widget _exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { - return CupertinoButton( - key: key, - color: _widgetSelectionButtonsBackgroundColor(context), - padding: EdgeInsets.zero, + return _CupertinoInspectorButton.filled( onPressed: onPressed, - child: Icon( - CupertinoIcons.xmark, - size: 28.0, - color: _widgetSelectionButtonsForegroundColor(context), - semanticLabel: 'Exit Select Widget mode.', - ), + semanticLabel: semanticLabel, + icon: CupertinoIcons.xmark, + buttonKey: key, ); } Widget _moveExitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, bool isLeftAligned = true, }) { - return CupertinoButton( + return _CupertinoInspectorButton.iconOnly( onPressed: onPressed, - padding: EdgeInsets.zero, - child: Icon( - isLeftAligned ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left, - size: 32.0, - color: _widgetSelectionButtonsBackgroundColor(context), - semanticLabel: - 'Move "Exit Select Widget mode" button to the ${isLeftAligned ? 'right' : 'left'}.', - ), + semanticLabel: semanticLabel, + icon: isLeftAligned ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left, ); } - Color _widgetSelectionButtonsForegroundColor(BuildContext context) { - return CupertinoTheme.of(context).primaryContrastingColor; - } - - Color _widgetSelectionButtonsBackgroundColor(BuildContext context) { - return CupertinoTheme.of(context).primaryColor; + Widget _tapBehaviorButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required bool selectionOnTapEnabled, + }) { + return _CupertinoInspectorButton.toggle( + onPressed: onPressed, + semanticLabel: semanticLabel, + // This icon is also used for the Material-styled button and for DevTools. + // It should be updated in all 3 places if changed. + icon: CupertinoIcons.cursor_rays, + toggledOn: selectionOnTapEnabled, + ); } WidgetsApp _buildWidgetApp(BuildContext context) { @@ -607,6 +606,7 @@ class _CupertinoAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder, shortcuts: widget.shortcuts, actions: widget.actions, restorationScopeId: widget.restorationScopeId, @@ -642,6 +642,7 @@ class _CupertinoAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder, shortcuts: widget.shortcuts, actions: widget.actions, restorationScopeId: widget.restorationScopeId, @@ -680,3 +681,83 @@ class _CupertinoAppState extends State { ); } } + +class _CupertinoInspectorButton extends InspectorButton { + const _CupertinoInspectorButton.filled({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + super.buttonKey, + }) : super.filled(); + + const _CupertinoInspectorButton.toggle({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + super.toggledOn, + }) : super.toggle(); + + const _CupertinoInspectorButton.iconOnly({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + }) : super.iconOnly(); + + @override + Widget build(BuildContext context) { + final Icon buttonIcon = Icon( + icon, + semanticLabel: semanticLabel, + size: iconSizeForVariant, + color: foregroundColor(context), + ); + + return Padding( + key: buttonKey, + padding: const EdgeInsets.all( + (kMinInteractiveDimensionCupertino - InspectorButton.buttonSize) / 2, + ), + child: + variant == InspectorButtonVariant.toggle && !toggledOn! + ? CupertinoButton.tinted( + minSize: InspectorButton.buttonSize, + onPressed: onPressed, + padding: EdgeInsets.zero, + child: buttonIcon, + ) + : CupertinoButton( + minSize: InspectorButton.buttonSize, + onPressed: onPressed, + padding: EdgeInsets.zero, + color: backgroundColor(context), + child: buttonIcon, + ), + ); + } + + @override + Color foregroundColor(BuildContext context) { + final Color primaryColor = CupertinoTheme.of(context).primaryColor; + final Color secondaryColor = CupertinoTheme.of(context).primaryContrastingColor; + switch (variant) { + case InspectorButtonVariant.filled: + return secondaryColor; + case InspectorButtonVariant.iconOnly: + return primaryColor; + case InspectorButtonVariant.toggle: + return !toggledOn! ? primaryColor : secondaryColor; + } + } + + @override + Color backgroundColor(BuildContext context) { + final Color primaryColor = CupertinoTheme.of(context).primaryColor; + switch (variant) { + case InspectorButtonVariant.filled: + case InspectorButtonVariant.toggle: + return primaryColor; + case InspectorButtonVariant.iconOnly: + return const Color(0x00000000); + } + } +} diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index bab92ee235bda..d2083c0353946 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -20,8 +20,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'arc.dart'; +import 'button_style.dart'; import 'colors.dart'; -import 'floating_action_button.dart'; import 'icon_button.dart'; import 'icons.dart'; import 'material_localizations.dart'; @@ -29,6 +29,7 @@ import 'page.dart'; import 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState; import 'scrollbar.dart'; import 'theme.dart'; +import 'theme_data.dart'; import 'tooltip.dart'; // Examples can assume: @@ -903,9 +904,6 @@ class MaterialScrollBehavior extends ScrollBehavior { } class _MaterialAppState extends State { - static const double _moveExitWidgetSelectionIconSize = 32; - static const double _moveExitWidgetSelectionTargetSize = 40; - late HeroController _heroController; bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null; @@ -938,52 +936,47 @@ class _MaterialAppState extends State { Widget _exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { - return FloatingActionButton( - key: key, + return _MaterialInspectorButton.filled( onPressed: onPressed, - mini: true, - backgroundColor: _widgetSelectionButtonsBackgroundColor(context), - foregroundColor: _widgetSelectionButtonsForegroundColor(context), - child: const Icon(Icons.close, semanticLabel: 'Exit Select Widget mode.'), + semanticLabel: semanticLabel, + icon: Icons.close, + isDarkTheme: _isDarkTheme(context), + buttonKey: key, ); } Widget _moveExitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, bool isLeftAligned = true, }) { - return IconButton( - color: _widgetSelectionButtonsBackgroundColor(context), - padding: EdgeInsets.zero, - iconSize: _moveExitWidgetSelectionIconSize, + return _MaterialInspectorButton.iconOnly( onPressed: onPressed, - constraints: const BoxConstraints( - minWidth: _moveExitWidgetSelectionTargetSize, - minHeight: _moveExitWidgetSelectionTargetSize, - ), - icon: Icon( - isLeftAligned ? Icons.arrow_right : Icons.arrow_left, - semanticLabel: - 'Move "Exit Select Widget mode" button to the ${isLeftAligned ? 'right' : 'left'}.', - ), + semanticLabel: semanticLabel, + icon: isLeftAligned ? Icons.arrow_right : Icons.arrow_left, + isDarkTheme: _isDarkTheme(context), ); } - Color _widgetSelectionButtonsForegroundColor(BuildContext context) { - final ThemeData theme = Theme.of(context); - return _isDarkTheme(context) - ? theme.colorScheme.onPrimaryContainer - : theme.colorScheme.primaryContainer; - } - - Color _widgetSelectionButtonsBackgroundColor(BuildContext context) { - final ThemeData theme = Theme.of(context); - return _isDarkTheme(context) - ? theme.colorScheme.primaryContainer - : theme.colorScheme.onPrimaryContainer; + Widget _tapBehaviorButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required bool selectionOnTapEnabled, + }) { + return _MaterialInspectorButton.toggle( + onPressed: onPressed, + semanticLabel: semanticLabel, + // This icon is also used for the Cupertino-styled button and for DevTools. + // It should be updated in all 3 places if changed. + icon: CupertinoIcons.cursor_rays, + isDarkTheme: _isDarkTheme(context), + toggledOn: selectionOnTapEnabled, + ); } bool _isDarkTheme(BuildContext context) { @@ -1100,6 +1093,7 @@ class _MaterialAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder, shortcuts: widget.shortcuts, actions: widget.actions, restorationScopeId: widget.restorationScopeId, @@ -1135,6 +1129,7 @@ class _MaterialAppState extends State { debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder, shortcuts: widget.shortcuts, actions: widget.actions, restorationScopeId: widget.restorationScopeId, @@ -1173,3 +1168,101 @@ class _MaterialAppState extends State { ); } } + +class _MaterialInspectorButton extends InspectorButton { + const _MaterialInspectorButton.filled({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + required this.isDarkTheme, + super.buttonKey, + }) : super.filled(); + + const _MaterialInspectorButton.toggle({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + required this.isDarkTheme, + super.toggledOn, + }) : super.toggle(); + + const _MaterialInspectorButton.iconOnly({ + required super.onPressed, + required super.semanticLabel, + required super.icon, + required this.isDarkTheme, + }) : super.iconOnly(); + + final bool isDarkTheme; + + static const EdgeInsets _buttonPadding = EdgeInsets.zero; + static const BoxConstraints _buttonConstraints = BoxConstraints.tightFor( + width: InspectorButton.buttonSize, + height: InspectorButton.buttonSize, + ); + + @override + Widget build(BuildContext context) { + return IconButton( + key: buttonKey, + onPressed: onPressed, + iconSize: iconSizeForVariant, + padding: _buttonPadding, + constraints: _buttonConstraints, + style: _selectionButtonsIconStyle(context), + icon: Icon(icon, semanticLabel: semanticLabel), + ); + } + + ButtonStyle _selectionButtonsIconStyle(BuildContext context) { + final Color foreground = foregroundColor(context); + final Color background = backgroundColor(context); + + return IconButton.styleFrom( + foregroundColor: foreground, + backgroundColor: background, + side: + variant == InspectorButtonVariant.toggle && !toggledOn! + ? BorderSide(color: foreground) + : null, + tapTargetSize: MaterialTapTargetSize.padded, + ); + } + + @override + Color foregroundColor(BuildContext context) { + final Color primaryColor = _primaryColor(context); + final Color secondaryColor = _secondaryColor(context); + switch (variant) { + case InspectorButtonVariant.filled: + return primaryColor; + case InspectorButtonVariant.iconOnly: + return secondaryColor; + case InspectorButtonVariant.toggle: + return !toggledOn! ? secondaryColor : primaryColor; + } + } + + @override + Color backgroundColor(BuildContext context) { + final Color secondaryColor = _secondaryColor(context); + switch (variant) { + case InspectorButtonVariant.filled: + return secondaryColor; + case InspectorButtonVariant.iconOnly: + return Colors.transparent; + case InspectorButtonVariant.toggle: + return !toggledOn! ? Colors.transparent : secondaryColor; + } + } + + Color _primaryColor(BuildContext context) { + final ThemeData theme = Theme.of(context); + return isDarkTheme ? theme.colorScheme.onPrimaryContainer : theme.colorScheme.primaryContainer; + } + + Color _secondaryColor(BuildContext context) { + final ThemeData theme = Theme.of(context); + return isDarkTheme ? theme.colorScheme.primaryContainer : theme.colorScheme.onPrimaryContainer; + } +} diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index dce1f17bc9ca6..b892ba9c0e7ed 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -355,6 +355,7 @@ class WidgetsApp extends StatefulWidget { this.debugShowCheckedModeBanner = true, this.exitWidgetSelectionButtonBuilder, this.moveExitWidgetSelectionButtonBuilder, + this.tapBehaviorButtonBuilder, this.shortcuts, this.actions, this.restorationScopeId, @@ -446,6 +447,7 @@ class WidgetsApp extends StatefulWidget { this.debugShowCheckedModeBanner = true, this.exitWidgetSelectionButtonBuilder, this.moveExitWidgetSelectionButtonBuilder, + this.tapBehaviorButtonBuilder, this.shortcuts, this.actions, this.restorationScopeId, @@ -1041,19 +1043,27 @@ class WidgetsApp extends StatefulWidget { /// Builds the widget the [WidgetInspector] uses to exit selection mode. /// - /// This lets [MaterialApp] to use a Material Design button to exit the - /// inspector select mode without requiring [WidgetInspector] to depend on the - /// Material package. + /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled + /// button for their design systems without requiring [WidgetInspector] to + /// depend on the Material or Cupertino packages. final ExitWidgetSelectionButtonBuilder? exitWidgetSelectionButtonBuilder; /// Builds the widget the [WidgetInspector] uses to move the exit selection /// mode button. /// - /// This lets [MaterialApp] to use a Material Design button to change the - /// alignment without requiring [WidgetInspector] to depend on the Material - /// package. + /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled + /// button for their design systems without requiring [WidgetInspector] to + /// depend on the Material or Cupertino packages. final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder; + /// Builds the widget the [WidgetInspector] uses to change the default + /// behavior when tapping on widgets in the app. + /// + /// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled + /// button for their design systems without requiring [WidgetInspector] to + /// depend on the Material or Cupertino packages. + final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder; + /// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner} /// Turns on a little "DEBUG" banner in debug mode to indicate /// that the app is in debug mode. This is on by default (in @@ -1824,6 +1834,7 @@ class _WidgetsAppState extends State with WidgetsBindingObserver { return WidgetInspector( exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: widget.tapBehaviorButtonBuilder, child: child!, ); } diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 22bf618056a53..bce192d1926e0 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -492,6 +492,15 @@ mixin WidgetsBinding _debugShowWidgetInspectorOverrideNotifierObject ??= ValueNotifier(false); ValueNotifier? _debugShowWidgetInspectorOverrideNotifierObject; + /// The notifier for whether or not taps on the device are treated as widget + /// selections when the widget inspector is enabled. + /// + /// - If true, taps in the app are intercepted by the widget inspector. + /// - If false, taps in the app are not intercepted by the widget inspector. + ValueNotifier get debugWidgetInspectorSelectionOnTapEnabled => + _debugWidgetInspectorSelectionOnTapEnabledNotifierObject ??= ValueNotifier(true); + ValueNotifier? _debugWidgetInspectorSelectionOnTapEnabledNotifierObject; + @visibleForTesting @override void resetInternalState() { @@ -499,6 +508,8 @@ mixin WidgetsBinding super.resetInternalState(); _debugShowWidgetInspectorOverrideNotifierObject?.dispose(); _debugShowWidgetInspectorOverrideNotifierObject = null; + _debugWidgetInspectorSelectionOnTapEnabledNotifierObject?.dispose(); + _debugWidgetInspectorSelectionOnTapEnabledNotifierObject = null; } void _debugAddStackFilters() { diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index e862bac44d142..d1cebbd11aeb7 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -34,6 +34,7 @@ import 'binding.dart'; import 'debug.dart'; import 'framework.dart'; import 'gesture_detector.dart'; +import 'icon_data.dart'; import 'service_extensions.dart'; import 'view.dart'; @@ -43,13 +44,29 @@ typedef ExitWidgetSelectionButtonBuilder = Widget Function( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }); /// Signature for the builder callback used by /// [WidgetInspector.moveExitWidgetSelectionButtonBuilder]. typedef MoveExitWidgetSelectionButtonBuilder = - Widget Function(BuildContext context, {required VoidCallback onPressed, bool isLeftAligned}); + Widget Function( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + bool isLeftAligned, + }); + +/// Signature for the builder callback used by +/// [WidgetInspector.tapBehaviorButtonBuilder]. +typedef TapBehaviorButtonBuilder = + Widget Function( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required bool selectionOnTapEnabled, + }); /// Signature for a method that registers the service extension `callback` with /// the given `name`. @@ -2768,6 +2785,7 @@ class WidgetInspector extends StatefulWidget { const WidgetInspector({ super.key, required this.child, + required this.tapBehaviorButtonBuilder, required this.exitWidgetSelectionButtonBuilder, required this.moveExitWidgetSelectionButtonBuilder, }); @@ -2790,6 +2808,15 @@ class WidgetInspector extends StatefulWidget { /// The button UI should respond to the `leftAligned` argument. final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder; + /// A builder that is called to create the button that changes the default tap + /// behavior when Select Widget mode is enabled. + /// + /// The `onPressed` callback passed as an argument to the builder should be + /// hooked up to the returned widget. + /// + /// The button UI should respond to the `selectionOnTapEnabled` argument. + final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder; + @override State createState() => _WidgetInspectorState(); } @@ -2809,6 +2836,11 @@ class _WidgetInspectorState extends State with WidgetsBindingOb /// as selecting the edge of the bounding box. static const double _edgeHitMargin = 2.0; + ValueNotifier get _selectionOnTapEnabled => + WidgetsBinding.instance.debugWidgetInspectorSelectionOnTapEnabled; + + bool get _isSelectModeWithSelectionOnTapEnabled => isSelectMode && _selectionOnTapEnabled.value; + @override void initState() { super.initState(); @@ -2817,6 +2849,7 @@ class _WidgetInspectorState extends State with WidgetsBindingOb WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.addListener( _selectionInformationChanged, ); + _selectionOnTapEnabled.addListener(_selectionInformationChanged); selection = WidgetInspectorService.instance.selection; isSelectMode = WidgetsBinding.instance.debugShowWidgetInspectorOverride; } @@ -2827,6 +2860,7 @@ class _WidgetInspectorState extends State with WidgetsBindingOb WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.removeListener( _selectionInformationChanged, ); + _selectionOnTapEnabled.removeListener(_selectionInformationChanged); super.dispose(); } @@ -2911,7 +2945,7 @@ class _WidgetInspectorState extends State with WidgetsBindingOb } void _inspectAt(Offset position) { - if (!isSelectMode) { + if (!_isSelectModeWithSelectionOnTapEnabled) { return; } @@ -2952,7 +2986,7 @@ class _WidgetInspectorState extends State with WidgetsBindingOb } void _handleTap() { - if (!isSelectMode) { + if (!_isSelectModeWithSelectionOnTapEnabled) { return; } if (_lastPointerLocation != null) { @@ -2975,11 +3009,16 @@ class _WidgetInspectorState extends State with WidgetsBindingOb onPanUpdate: _handlePanUpdate, behavior: HitTestBehavior.opaque, excludeFromSemantics: true, - child: IgnorePointer(ignoring: isSelectMode, key: _ignorePointerKey, child: widget.child), + child: IgnorePointer( + ignoring: _isSelectModeWithSelectionOnTapEnabled, + key: _ignorePointerKey, + child: widget.child, + ), ), _InspectorOverlay(selection: selection), if (isSelectMode && widget.exitWidgetSelectionButtonBuilder != null) - _ExitWidgetSelectionButtonGroup( + _WidgetInspectorButtonGroup( + tapBehaviorButtonBuilder: widget.tapBehaviorButtonBuilder, exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder!, moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder, ), @@ -2988,6 +3027,123 @@ class _WidgetInspectorState extends State with WidgetsBindingOb } } +/// Defines the visual and behavioral variants for an [InspectorButton]. +enum InspectorButtonVariant { + /// A standard button with a filled background and foreground icon. + filled, + + /// A button that can be toggled on or off, visually representing its state. + /// + /// The [InspectorButton.toggledOn] property determines its current state. + toggle, + + /// A button that displays only an icon, typically with a transparent background. + iconOnly, +} + +/// An abstract base class for creating Material or Cupertino-styled inspector +/// buttons. +/// +/// Subclasses are responsible for implementing the design-specific rendering +/// logic in the [build] method and providing design-specific colors via +/// [foregroundColor] and [backgroundColor]. +abstract class InspectorButton extends StatelessWidget { + /// Creates an inspector button. + /// + /// This is the base constructor used by named constructors. + const InspectorButton({ + super.key, + required this.onPressed, + required this.semanticLabel, + required this.icon, + this.buttonKey, + required this.variant, + this.toggledOn, + }); + + /// Creates an inspector button with the [InspectorButtonVariant.filled] style. + /// + /// This button typically has a solid background color and a contrasting icon. + const InspectorButton.filled({ + super.key, + required this.onPressed, + required this.semanticLabel, + required this.icon, + this.buttonKey, + }) : variant = InspectorButtonVariant.filled, + toggledOn = null; + + /// Creates an inspector button with the [InspectorButtonVariant.toggle] style. + /// + /// This button can be in an "on" or "off" state, visually indicated. + /// The [toggledOn] parameter defaults to `true`. + const InspectorButton.toggle({ + super.key, + required this.onPressed, + required this.semanticLabel, + required this.icon, + bool this.toggledOn = true, + }) : buttonKey = null, + variant = InspectorButtonVariant.toggle; + + /// Creates an inspector button with the [InspectorButtonVariant.iconOnly] style. + /// + /// This button typically displays only an icon with a transparent background. + const InspectorButton.iconOnly({ + super.key, + required this.onPressed, + required this.semanticLabel, + required this.icon, + }) : buttonKey = null, + variant = InspectorButtonVariant.iconOnly, + toggledOn = null; + + /// The callback that is called when the button is tapped. + final VoidCallback onPressed; + + /// The semantic label for the button, used for accessibility. + final String semanticLabel; + + /// The icon to display within the button. + final IconData icon; + + /// An optional key to identify the button widget. + final GlobalKey? buttonKey; + + /// The visual and behavioral variant of the button. + /// + /// See [InspectorButtonVariant] for available styles. + final InspectorButtonVariant variant; + + /// For [InspectorButtonVariant.toggle] buttons, this determines if the button + /// is currently in the "on" (true) or "off" (false) state. + final bool? toggledOn; + + /// The standard height and width for the button. + static const double buttonSize = 32.0; + + /// The standard size for the icon when it's not the only element (e.g., in filled or toggle buttons). + /// + /// For [InspectorButtonVariant.iconOnly], the icon typically takes up the full [buttonSize]. + static const double buttonIconSize = 18.0; + + /// Gets the appropriate icon size based on the button's [variant]. + /// + /// Returns [buttonSize] if the variant is [InspectorButtonVariant.iconOnly], + /// otherwise returns [buttonIconSize]. + double get iconSizeForVariant => + variant == InspectorButtonVariant.iconOnly ? buttonSize : buttonIconSize; + + /// Provides the appropriate foreground color for the button's icon. + Color foregroundColor(BuildContext context); + + /// Provides the appropriate background color for the button. + Color backgroundColor(BuildContext context); + + @override + Widget build(BuildContext context); +} + /// Mutable selection state of the inspector. class InspectorSelection with ChangeNotifier { /// Creates an instance of [InspectorSelection]. @@ -3455,22 +3611,24 @@ const double _kOffScreenMargin = 1.0; const TextStyle _messageStyle = TextStyle(color: Color(0xFFFFFFFF), fontSize: 10.0, height: 1.2); -class _ExitWidgetSelectionButtonGroup extends StatefulWidget { - const _ExitWidgetSelectionButtonGroup({ +class _WidgetInspectorButtonGroup extends StatefulWidget { + const _WidgetInspectorButtonGroup({ required this.exitWidgetSelectionButtonBuilder, required this.moveExitWidgetSelectionButtonBuilder, + required this.tapBehaviorButtonBuilder, }); final ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder; final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder; + final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder; @override - State<_ExitWidgetSelectionButtonGroup> createState() => _ExitWidgetSelectionButtonGroupState(); + State<_WidgetInspectorButtonGroup> createState() => _WidgetInspectorButtonGroupState(); } -class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionButtonGroup> { - static const double _kExitWidgetSelectionButtonPadding = 4.0; +class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup> { static const double _kExitWidgetSelectionButtonMargin = 10.0; + static const bool _defaultSelectionOnTapEnabled = true; final GlobalKey _exitWidgetSelectionButtonKey = GlobalKey( debugLabel: 'Exit Widget Selection button', @@ -3480,31 +3638,78 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut bool _leftAligned = true; + ValueNotifier get _selectionOnTapEnabled => + WidgetsBinding.instance.debugWidgetInspectorSelectionOnTapEnabled; + + Widget? get _moveExitWidgetSelectionButton { + final MoveExitWidgetSelectionButtonBuilder? buttonBuilder = + widget.moveExitWidgetSelectionButtonBuilder; + if (buttonBuilder == null) { + return null; + } + + final String buttonLabel = 'Move to the ${_leftAligned ? 'right' : 'left'}'; + return _WidgetInspectorButton( + button: buttonBuilder( + context, + onPressed: () { + _changeButtonGroupAlignment(); + _onTooltipHidden(); + }, + semanticLabel: buttonLabel, + isLeftAligned: _leftAligned, + ), + onTooltipVisible: () { + _changeTooltipMessage(buttonLabel); + }, + onTooltipHidden: _onTooltipHidden, + ); + } + + Widget get _exitWidgetSelectionButton { + const String buttonLabel = 'Exit Select Widget mode'; + return _WidgetInspectorButton( + button: widget.exitWidgetSelectionButtonBuilder( + context, + onPressed: _exitWidgetSelectionMode, + semanticLabel: buttonLabel, + key: _exitWidgetSelectionButtonKey, + ), + onTooltipVisible: () { + _changeTooltipMessage(buttonLabel); + }, + onTooltipHidden: _onTooltipHidden, + ); + } + + Widget? get _tapBehaviorButton { + final TapBehaviorButtonBuilder? buttonBuilder = widget.tapBehaviorButtonBuilder; + if (buttonBuilder == null) { + return null; + } + + return _WidgetInspectorButton( + button: buttonBuilder( + context, + onPressed: _changeSelectionOnTapMode, + semanticLabel: 'Change widget selection mode for taps', + selectionOnTapEnabled: _selectionOnTapEnabled.value, + ), + onTooltipVisible: _changeSelectionOnTapTooltip, + onTooltipHidden: _onTooltipHidden, + ); + } + + bool get _tooltipVisible => _tooltipMessage != null; + @override Widget build(BuildContext context) { - final Widget? moveExitWidgetSelectionButton = - widget.moveExitWidgetSelectionButtonBuilder != null - ? Padding( - padding: EdgeInsets.only( - left: _leftAligned ? _kExitWidgetSelectionButtonPadding : 0.0, - right: _leftAligned ? 0.0 : _kExitWidgetSelectionButtonPadding, - ), - child: _TooltipGestureDetector( - button: widget.moveExitWidgetSelectionButtonBuilder!( - context, - onPressed: () { - _changeButtonGroupAlignment(); - _onTooltipHidden(); - }, - isLeftAligned: _leftAligned, - ), - onTooltipVisible: () { - _changeTooltipMessage('Move to the ${_leftAligned ? 'right' : 'left'}'); - }, - onTooltipHidden: _onTooltipHidden, - ), - ) - : null; + final Widget selectionModeButtons = Column( + children: [ + if (_tapBehaviorButton != null) _tapBehaviorButton!, + _exitWidgetSelectionButton, + ], + ); final Widget buttonGroup = Stack( alignment: AlignmentDirectional.topCenter, @@ -3517,22 +3722,12 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut ), ), Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, children: [ - if (!_leftAligned && moveExitWidgetSelectionButton != null) - moveExitWidgetSelectionButton, - _TooltipGestureDetector( - button: widget.exitWidgetSelectionButtonBuilder( - context, - onPressed: _exitWidgetSelectionMode, - key: _exitWidgetSelectionButtonKey, - ), - onTooltipVisible: () { - _changeTooltipMessage('Exit Select Widget mode'); - }, - onTooltipHidden: _onTooltipHidden, - ), - if (_leftAligned && moveExitWidgetSelectionButton != null) - moveExitWidgetSelectionButton, + if (_leftAligned) selectionModeButtons, + if (_moveExitWidgetSelectionButton != null) _moveExitWidgetSelectionButton!, + if (!_leftAligned) selectionModeButtons, ], ), ], @@ -3548,6 +3743,25 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut void _exitWidgetSelectionMode() { WidgetInspectorService.instance._changeWidgetSelectionMode(false); + // Reset to default selection on tap behavior on exit. + _changeSelectionOnTapMode(selectionOnTapEnabled: _defaultSelectionOnTapEnabled); + } + + void _changeSelectionOnTapMode({bool? selectionOnTapEnabled}) { + final bool newValue = selectionOnTapEnabled ?? !_selectionOnTapEnabled.value; + _selectionOnTapEnabled.value = newValue; + WidgetInspectorService.instance.selection.clear(); + if (_tooltipVisible) { + _changeSelectionOnTapTooltip(); + } + } + + void _changeSelectionOnTapTooltip() { + _changeTooltipMessage( + _selectionOnTapEnabled.value + ? 'Disable widget selection for taps' + : 'Enable widget selection for taps', + ); } void _changeButtonGroupAlignment() { @@ -3571,8 +3785,8 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut } } -class _TooltipGestureDetector extends StatefulWidget { - const _TooltipGestureDetector({ +class _WidgetInspectorButton extends StatefulWidget { + const _WidgetInspectorButton({ required this.button, required this.onTooltipVisible, required this.onTooltipHidden, @@ -3586,10 +3800,10 @@ class _TooltipGestureDetector extends StatefulWidget { static const Duration _tooltipDelayDuration = Duration(milliseconds: 100); @override - State<_TooltipGestureDetector> createState() => _TooltipGestureDetectorState(); + State<_WidgetInspectorButton> createState() => _WidgetInspectorButtonState(); } -class _TooltipGestureDetectorState extends State<_TooltipGestureDetector> { +class _WidgetInspectorButtonState extends State<_WidgetInspectorButton> { Timer? _tooltipVisibleTimer; Timer? _tooltipHiddenTimer; @@ -3609,18 +3823,18 @@ class _TooltipGestureDetectorState extends State<_TooltipGestureDetector> { children: [ GestureDetector( onLongPress: () { - _tooltipVisibleAfter(_TooltipGestureDetector._tooltipDelayDuration); + _tooltipVisibleAfter(_WidgetInspectorButton._tooltipDelayDuration); _tooltipHiddenAfter( - _TooltipGestureDetector._tooltipShownOnLongPressDuration + - _TooltipGestureDetector._tooltipDelayDuration, + _WidgetInspectorButton._tooltipShownOnLongPressDuration + + _WidgetInspectorButton._tooltipDelayDuration, ); }, child: MouseRegion( onEnter: (_) { - _tooltipVisibleAfter(_TooltipGestureDetector._tooltipDelayDuration); + _tooltipVisibleAfter(_WidgetInspectorButton._tooltipDelayDuration); }, onExit: (_) { - _tooltipHiddenAfter(_TooltipGestureDetector._tooltipDelayDuration); + _tooltipHiddenAfter(_WidgetInspectorButton._tooltipDelayDuration); }, child: widget.button, ), diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index e6f03740b6933..fc37b4802612a 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -343,6 +343,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Stack( children: [ Text('a', textDirection: TextDirection.ltr), @@ -370,6 +371,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { Widget exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { exitWidgetSelectionButtonKey = key; @@ -422,6 +424,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { key: inspectorKey, exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Material( child: ListView( children: [ @@ -515,6 +518,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Transform( transform: Matrix4.identity()..scale(0.0), child: const Stack( @@ -545,6 +549,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { Widget exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { exitWidgetSelectionButtonKey = key; @@ -558,6 +563,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { key: inspectorKey, exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: ListView( dragStartBehavior: DragStartBehavior.down, children: [Container(key: childKey, height: 5000.0)], @@ -621,6 +627,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: GestureDetector( onLongPress: () { expect(didLongPress, isFalse); @@ -688,6 +695,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { key: inspectorKey, exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Overlay( initialEntries: [ entry1 = OverlayEntry( @@ -747,6 +755,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: ColoredBox( color: Colors.white, child: Center( @@ -791,7 +800,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final GlobalKey child2Key = GlobalKey(); ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder(Key key) { - return (BuildContext context, {required VoidCallback onPressed, required GlobalKey key}) { + return ( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required GlobalKey key, + }) { return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null)); }; } @@ -813,6 +827,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { selectButton1Key, ), moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Container(key: child1Key, child: const Text('Child 1')), ), ), @@ -823,6 +838,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { selectButton2Key, ), moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: Container(key: child2Key, child: const Text('Child 2')), ), ), @@ -858,6 +874,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { Widget exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null)); @@ -869,6 +886,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( key: inspectorKey, exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, child: const Text('Child 1'), ), @@ -918,6 +936,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { Widget exitWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, required GlobalKey key, }) { return Material( @@ -932,6 +951,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { Widget moveWidgetSelectionButtonBuilder( BuildContext context, { required VoidCallback onPressed, + required String semanticLabel, bool isLeftAligned = true, }) { return Material( @@ -953,6 +973,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { key: inspectorKey, exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: null, child: const Text('APP'), ), ), @@ -990,6 +1011,118 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), ); + testWidgets( + 'WidgetInspector Tap behavior button', + (WidgetTester tester) async { + Widget exitWidgetSelectionButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required GlobalKey key, + }) { + return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null)); + } + + Widget tapBehaviorButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticLabel, + required bool selectionOnTapEnabled, + }) { + return Material( + child: ElevatedButton( + onPressed: onPressed, + child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'), + ), + ); + } + + Finder buttonFinder(String buttonText) { + return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton)); + } + + int navigateEventsCount() => + service.dispatchedEvents('navigate', stream: 'ToolEvent').length; + + // Enable widget selection mode. + WidgetInspectorService.instance.isSelectMode = true; + + // Pump the test widget. + final GlobalKey inspectorKey = GlobalKey(); + setupDefaultPubRootDirectory(service); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WidgetInspector( + key: inspectorKey, + exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: tapBehaviorButtonBuilder, + moveExitWidgetSelectionButtonBuilder: null, + child: const Row(children: [Text('Child 1'), Text('Child 2')]), + ), + ), + ); + + // Verify there are no navigate events yet. + expect(navigateEventsCount(), equals(0)); + + // Tap on the first child widget. + final Finder child1 = find.text('Child 1'); + await tester.tap(child1, warnIfMissed: false); + await tester.pump(); + + // Verify the selection matches the first child widget. + final Element child1Element = child1.evaluate().first; + expect(service.selection.current, equals(child1Element.renderObject)); + + // Verify that a navigate event was sent. + expect(navigateEventsCount(), equals(1)); + + // Tap on the SELECTION ON TAP button. + final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP'); + expect(tapBehaviorButtonBefore, findsOneWidget); + await tester.tap(tapBehaviorButtonBefore); + await tester.pump(); + + // Verify the tap behavior button's UI has been updated. + expect(tapBehaviorButtonBefore, findsNothing); + final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP'); + expect(tapBehaviorButtonAfter, findsOneWidget); + + // Tap on the second child widget. + final Finder child2 = find.text('Child 2'); + await tester.tap(child2, warnIfMissed: false); + await tester.pump(); + + // Verify there is no selection. + expect(service.selection.current, isNull); + + // Verify no navigate events were sent. + expect(navigateEventsCount(), equals(1)); + + // Tap on the SELECTION ON TAP button again. + await tester.tap(tapBehaviorButtonAfter); + await tester.pump(); + + // Verify the tap behavior button's UI has been reset. + expect(tapBehaviorButtonAfter, findsNothing); + expect(tapBehaviorButtonBefore, findsOneWidget); + + // Tap on the second child widget again. + await tester.tap(child2, warnIfMissed: false); + await tester.pump(); + + // Verify the selection now matches the second child widget. + final Element child2Element = child2.evaluate().first; + expect(service.selection.current, equals(child2Element.renderObject)); + + // Verify another navigate event was sent. + expect(navigateEventsCount(), equals(2)); + }, + // [intended] Test requires --track-widget-creation flag. + skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), + ); + testWidgets('test transformDebugCreator will re-order if after stack trace', ( WidgetTester tester, ) async { @@ -3867,6 +4000,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { child: WidgetInspector( exitWidgetSelectionButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, + tapBehaviorButtonBuilder: null, child: _applyConstructor(_TrivialWidget.new), ), ); From b7e009ca24b03ad2544c10d63369ab5cd8dc5488 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 8 May 2025 12:54:26 -0700 Subject: [PATCH 21/53] Exclude a specific auto-generated Dart file from engine license checks (#168544) Manual cherrypick of https://github.com/flutter/flutter/pull/168508. Closes https://github.com/flutter/flutter/issues/168493. --- engine/src/flutter/ci/licenses_golden/excluded_files | 1 + engine/src/flutter/ci/licenses_golden/licenses_dart | 6 +----- engine/src/flutter/ci/licenses_golden/tool_signature | 2 +- engine/src/flutter/tools/licenses/lib/paths.dart | 1 + 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index 053b7e2452fa2..c8691cd8db102 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -1672,6 +1672,7 @@ ../../../flutter/third_party/dart/sdk/lib/svg/dart2js ../../../flutter/third_party/dart/sdk/lib/vmservice_libraries.json ../../../flutter/third_party/dart/sdk/lib/vmservice_libraries.yaml +../../../flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart ../../../flutter/third_party/dart/sdk/version ../../../flutter/third_party/dart/sdk_packages.yaml ../../../flutter/third_party/dart/tests diff --git a/engine/src/flutter/ci/licenses_golden/licenses_dart b/engine/src/flutter/ci/licenses_golden/licenses_dart index df73f9d8f9d4e..0e14926daddc0 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_dart +++ b/engine/src/flutter/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: c96f6439e1d3cadfbb6adc790efdcc37 +Signature: e41496825525b0b25bd65a5a8e93c81a ==================================================================================================== LIBRARY: dart @@ -842,7 +842,6 @@ ORIGIN: ../../../flutter/third_party/dart/sdk/lib/isolate/isolate.dart + ../../. ORIGIN: ../../../flutter/third_party/dart/sdk/lib/math/math.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/math/random.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart + ../../../flutter/third_party/dart/LICENSE -ORIGIN: ../../../flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/web_sql/dart2js/web_sql_dart2js.dart + ../../../flutter/third_party/dart/LICENSE TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/dart/runtime/bin/builtin.cc @@ -1191,7 +1190,6 @@ FILE: ../../../flutter/third_party/dart/sdk/lib/isolate/isolate.dart FILE: ../../../flutter/third_party/dart/sdk/lib/math/math.dart FILE: ../../../flutter/third_party/dart/sdk/lib/math/random.dart FILE: ../../../flutter/third_party/dart/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart -FILE: ../../../flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart FILE: ../../../flutter/third_party/dart/sdk/lib/web_sql/dart2js/web_sql_dart2js.dart ---------------------------------------------------------------------------------------------------- Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file @@ -1507,7 +1505,6 @@ ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/message.dart + ../.. ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/message_router.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/running_isolate.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/sdk/lib/vmservice/running_isolates.dart + ../../../flutter/third_party/dart/LICENSE -ORIGIN: ../../../flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart + ../../../flutter/third_party/dart/LICENSE ORIGIN: ../../../flutter/third_party/dart/utils/compiler/create_snapshot_entry.dart + ../../../flutter/third_party/dart/LICENSE TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/dart/runtime/bin/eventhandler_win.cc @@ -1792,7 +1789,6 @@ FILE: ../../../flutter/third_party/dart/sdk/lib/vmservice/message.dart FILE: ../../../flutter/third_party/dart/sdk/lib/vmservice/message_router.dart FILE: ../../../flutter/third_party/dart/sdk/lib/vmservice/running_isolate.dart FILE: ../../../flutter/third_party/dart/sdk/lib/vmservice/running_isolates.dart -FILE: ../../../flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart FILE: ../../../flutter/third_party/dart/utils/compiler/create_snapshot_entry.dart ---------------------------------------------------------------------------------------------------- Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file diff --git a/engine/src/flutter/ci/licenses_golden/tool_signature b/engine/src/flutter/ci/licenses_golden/tool_signature index ece9890fea9d1..16f57803c0ce0 100644 --- a/engine/src/flutter/ci/licenses_golden/tool_signature +++ b/engine/src/flutter/ci/licenses_golden/tool_signature @@ -1,2 +1,2 @@ -Signature: 72079bc1edfa9c01900da12816e18f0e +Signature: 05c5162009fbeea0e177225292e649c4 diff --git a/engine/src/flutter/tools/licenses/lib/paths.dart b/engine/src/flutter/tools/licenses/lib/paths.dart index 09033686e0804..099f144068e18 100644 --- a/engine/src/flutter/tools/licenses/lib/paths.dart +++ b/engine/src/flutter/tools/licenses/lib/paths.dart @@ -69,6 +69,7 @@ final Set skippedPaths = { r'flutter/third_party/dart/sdk/lib/html/dart2js', // generated from other sources r'flutter/third_party/dart/sdk/lib/html/doc', r'flutter/third_party/dart/sdk/lib/svg/dart2js', // generated from other sources + r'flutter/third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart', // generated from other sources r'flutter/third_party/dart/third_party/binary_size', // not linked in r'flutter/third_party/dart/third_party/binaryen', // not linked in r'flutter/third_party/dart/third_party/d3', // Siva says "that is the charting library used by the binary size tool" From 38e19d01dcac654bbb941b771cc06efd4ed4a93f Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 9 May 2025 09:48:01 -0700 Subject: [PATCH 22/53] Update Dart revision to `3.8.0-278.4.beta` (#168391) * Updated Dart revision to [`e7f2f0556e3e57acb60749467e54f9a44b2bfc76`](http://goto.google.com/dart-hash/e7f2f0556e3e57acb60749467e54f9a44b2bfc76) (`3.8.0-278.4.beta`). * ~Ran `gclient sync -D` and `tools/dart/create_updated_flutter_deps.py` (nothing was updated)~ ran wrong command * Ran `gclient sync -D` and `tools/dart/create_updated_flutter_deps.py -f ` (Dart style revision was updated) --- DEPS | 4 +- .../flutter/ci/licenses_golden/licenses_dart | 4 +- .../src/widget_preview_rendering.dart.tmpl | 37 ++++++++----------- .../lib/src/widget_preview_rendering.dart | 37 ++++++++----------- 4 files changed, 34 insertions(+), 48 deletions(-) diff --git a/DEPS b/DEPS index ecb4b3edfb8b6..4d20390ae311f 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '9003f7927a24fd87c34154e04585bfbcfd4d9188', + 'dart_revision': 'e7f2f0556e3e57acb60749467e54f9a44b2bfc76', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -314,7 +314,7 @@ deps = { Var('dart_git') + '/core.git' + '@' + Var('dart_core_rev'), 'engine/src/flutter/third_party/dart/third_party/pkg/dart_style': - Var('dart_git') + '/dart_style.git@21de99ec0ff8ace4d946a746fb427fffd6afa535', + Var('dart_git') + '/dart_style.git@100db45075abdd66fd8788b205243e90ff0595df', 'engine/src/flutter/third_party/dart/third_party/pkg/dartdoc': Var('dart_git') + '/dartdoc.git@62aefbb788baf5b73b2d704d66d9735a7ca56a69', diff --git a/engine/src/flutter/ci/licenses_golden/licenses_dart b/engine/src/flutter/ci/licenses_golden/licenses_dart index 0e14926daddc0..0a67c1ebb3231 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_dart +++ b/engine/src/flutter/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: e41496825525b0b25bd65a5a8e93c81a +Signature: f4f0a7ce457a816a7b8e0f13355125f4 ==================================================================================================== LIBRARY: dart @@ -4858,7 +4858,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/0d6811928830b87e36a0f49eb7fe554c308d3699 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/e7f2f0556e3e57acb60749467e54f9a44b2bfc76 /third_party/fallback_root_certificates/ ==================================================================================================== diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl index 8449dd0ec5b1e..3e26bb2d4e1f8 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl @@ -96,11 +96,10 @@ class _WidgetPreviewErrorWidget extends StatelessWidget { TextSpan( text: frame.location, style: linkTextStyle, - recognizer: - TapGestureRecognizer() - ..onTap = () { - // TODO(bkonyi): notify IDEs to navigate to the source location via DTD. - }, + recognizer: TapGestureRecognizer() + ..onTap = () { + // TODO(bkonyi): notify IDEs to navigate to the source location via DTD. + }, ), TextSpan(text: ' ' * (longest - frame.location.length)), const TextSpan(text: ' '), @@ -244,11 +243,8 @@ class WidgetPreviewerWindowConstraints extends InheritedWidget { final BoxConstraints constraints; static BoxConstraints getRootConstraints(BuildContext context) { - final result = - context - .dependOnInheritedWidgetOfExactType< - WidgetPreviewerWindowConstraints - >(); + final result = context + .dependOnInheritedWidgetOfExactType(); assert( result != null, 'No WidgetPreviewerWindowConstraints founds in context', @@ -348,10 +344,9 @@ class _WidgetPreviewWrapperBox extends RenderShiftedBox { // the previewer. In this case, apply finite constraints (e.g., the // constraints for the root of the previewer). Otherwise, use the // widget's actual constraints. - _constraintOverride = - minInstrinsicHeight == 0 - ? _previewerConstraints - : const BoxConstraints(); + _constraintOverride = minInstrinsicHeight == 0 + ? _previewerConstraints + : const BoxConstraints(); } super.layout(constraints, parentUsesSize: parentUsesSize); } @@ -483,18 +478,16 @@ class _WidgetPreviewScaffold extends StatelessWidget { IconButton( onPressed: () => _toggleLayout(LayoutType.gridView), icon: Icon(Icons.grid_on), - color: - selectedLayout == LayoutType.gridView - ? Colors.blue - : Colors.black, + color: selectedLayout == LayoutType.gridView + ? Colors.blue + : Colors.black, ), IconButton( onPressed: () => _toggleLayout(LayoutType.listView), icon: Icon(Icons.view_list), - color: - selectedLayout == LayoutType.listView - ? Colors.blue - : Colors.black, + color: selectedLayout == LayoutType.listView + ? Colors.blue + : Colors.black, ), ], ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart index 8449dd0ec5b1e..3e26bb2d4e1f8 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart @@ -96,11 +96,10 @@ class _WidgetPreviewErrorWidget extends StatelessWidget { TextSpan( text: frame.location, style: linkTextStyle, - recognizer: - TapGestureRecognizer() - ..onTap = () { - // TODO(bkonyi): notify IDEs to navigate to the source location via DTD. - }, + recognizer: TapGestureRecognizer() + ..onTap = () { + // TODO(bkonyi): notify IDEs to navigate to the source location via DTD. + }, ), TextSpan(text: ' ' * (longest - frame.location.length)), const TextSpan(text: ' '), @@ -244,11 +243,8 @@ class WidgetPreviewerWindowConstraints extends InheritedWidget { final BoxConstraints constraints; static BoxConstraints getRootConstraints(BuildContext context) { - final result = - context - .dependOnInheritedWidgetOfExactType< - WidgetPreviewerWindowConstraints - >(); + final result = context + .dependOnInheritedWidgetOfExactType(); assert( result != null, 'No WidgetPreviewerWindowConstraints founds in context', @@ -348,10 +344,9 @@ class _WidgetPreviewWrapperBox extends RenderShiftedBox { // the previewer. In this case, apply finite constraints (e.g., the // constraints for the root of the previewer). Otherwise, use the // widget's actual constraints. - _constraintOverride = - minInstrinsicHeight == 0 - ? _previewerConstraints - : const BoxConstraints(); + _constraintOverride = minInstrinsicHeight == 0 + ? _previewerConstraints + : const BoxConstraints(); } super.layout(constraints, parentUsesSize: parentUsesSize); } @@ -483,18 +478,16 @@ class _WidgetPreviewScaffold extends StatelessWidget { IconButton( onPressed: () => _toggleLayout(LayoutType.gridView), icon: Icon(Icons.grid_on), - color: - selectedLayout == LayoutType.gridView - ? Colors.blue - : Colors.black, + color: selectedLayout == LayoutType.gridView + ? Colors.blue + : Colors.black, ), IconButton( onPressed: () => _toggleLayout(LayoutType.listView), icon: Icon(Icons.view_list), - color: - selectedLayout == LayoutType.listView - ? Colors.blue - : Colors.black, + color: selectedLayout == LayoutType.listView + ? Colors.blue + : Colors.black, ), ], ); From dbc3660e848145748d81e3c81795571f36307a29 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 9 May 2025 13:35:12 -0700 Subject: [PATCH 23/53] Update `engine.version` (#168601) Rolls up to last commit on this branch: https://github.com/flutter/flutter/commit/38e19d01dcac654bbb941b771cc06efd4ed4a93f. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index ff5873be07f1a..409f1dc8dcfc1 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -453dd7174cc6555e3e9eb5f006c86e6ec221a0d2 +38e19d01dcac654bbb941b771cc06efd4ed4a93f From c4f885d2a4be2fefafab917e6c338e0a871bbc47 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Wed, 14 May 2025 09:25:57 -0700 Subject: [PATCH 24/53] [CP-beta]Remove `docs_deploy_beta`, fix `docs_publish`, add comments. (#168824) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? Closes https://github.com/flutter/flutter/issues/168709. ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A (CI Infra Change) ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Not user facing - this fixes API doc generation for release candidate branches, which was accidentally broken in https://github.com/flutter/flutter/pull/162557. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Merge a commit to `flutter-3.32-candidate.0` and observe the post-submits. --- .ci.yaml | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 6404c288c0543..9d8f8f5439c93 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -582,19 +582,25 @@ targets: # tests are sharded. test_timeout_secs: "3600" # Allows 60 minutes (up from 30 default) + # Despite the name, this really means "generate api_docs.zip", and "upload zip + # to GCS", and conditionally on the "master" channel will also deploy the docs + # to Firebase (https://main-api.flutter.dev/). + # + # See "Linux docs_deploy_stable" for how the docs are deployed to stable API. - name: Linux docs_publish recipe: flutter/docs presubmit: false backfill: false + # This means "allow this to be scheduled by the release/release_builder" + # recipe. Normally we'd use "schedule: true", but that *also* means "do not + # run this in normal presubmit/postsubmit", and we do want it to run in + # postsubmit for the "master" channel. Sorry. + # + # See https://github.com/flutter/flutter/issues/168709 for details. + schedule_during_release_override: true timeout: 60 dimensions: os: "Linux" - enabled_branches: - # Produces docs for main-api.flutter.dev - # stable and beta are managed by the targets: - # - Linux docs_deploy_beta - # - Linux docs_deploy_stable - - master properties: cores: "32" dependencies: >- @@ -6915,30 +6921,10 @@ targets: drone_dimensions: - os=Windows - - - name: Linux docs_deploy_beta - recipe: flutter/docs - scheduler: release - bringup: true - enabled_branches: - - beta - presubmit: false - timeout: 60 - properties: - cores: "32" - dependencies: >- - [ - {"dependency": "dashing", "version": "0.4.0"}, - {"dependency": "firebase", "version": "v11.0.1"} - ] - tags: > - ["framework", "hostonly", "linux"] - validation: docs_deploy - validation_name: Docs_deploy - firebase_project: master-docs-flutter-dev - drone_dimensions: - - os=Linux - + # This step runs on the release channel "stable", after the same commit SHA + # has been run and built by Linux flutter_release_builder as part of a release + # candidate branch (i.e. /flutter-\d+\.\d+-candidate\.\d+/) in the previous + # target, "Linux docs_publish". - name: Linux docs_deploy_stable recipe: flutter/docs scheduler: release From aed4bfd318f189d648e595b89442fc19e3147b47 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Wed, 14 May 2025 09:26:00 -0700 Subject: [PATCH 25/53] [CP-beta]Run `{Platform} flutter_packaging` builders on release candidates. (#168825) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/168745 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A - Internal CI infra change. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Allows removing legacy logic in the `flutter_release_builder` recipe. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Merge on the candidate branch and observe post-submits. --- .ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.ci.yaml b/.ci.yaml index 9d8f8f5439c93..24628b1969842 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -6866,6 +6866,7 @@ targets: scheduler: release bringup: true # https://github.com/flutter/flutter/issues/126286 enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6880,6 +6881,7 @@ targets: timeout: 60 scheduler: release enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6896,6 +6898,7 @@ targets: timeout: 60 scheduler: release enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6912,6 +6915,7 @@ targets: scheduler: release bringup: true enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: From 0fe9c10cc5381d870dccdc57371a22c305280a8b Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 May 2025 10:12:37 -0700 Subject: [PATCH 26/53] [3.32] Revert: "Run `flutter_packaging` builders on release candidates (#168918) This reverts commit aed4bfd318f189d648e595b89442fc19e3147b47. `flutter_packaging` builders only should run on `stable` or `beta`, the original definition was correct. --- .ci.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 24628b1969842..9d8f8f5439c93 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -6866,7 +6866,6 @@ targets: scheduler: release bringup: true # https://github.com/flutter/flutter/issues/126286 enabled_branches: - - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6881,7 +6880,6 @@ targets: timeout: 60 scheduler: release enabled_branches: - - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6898,7 +6896,6 @@ targets: timeout: 60 scheduler: release enabled_branches: - - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: @@ -6915,7 +6912,6 @@ targets: scheduler: release bringup: true enabled_branches: - - flutter-\d+\.\d+-candidate\.\d+ - beta - stable properties: From 3e54f90f1af3b4b9846fba24a1cace62ddf7f77a Mon Sep 17 00:00:00 2001 From: Reid Baker <1063596+reidbaker@users.noreply.github.com> Date: Thu, 15 May 2025 21:50:14 +0000 Subject: [PATCH 27/53] update dart hash to stable 3.32 (#168927) Align stable with stable dart hash. I did run gclient sync then engine/src/tools/dart/create_updated_flutter_deps.py but nothing changed. --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4d20390ae311f..b3d3cb6d0c3b6 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'e7f2f0556e3e57acb60749467e54f9a44b2bfc76', + 'dart_revision': 'b04011c77cd93e6ab9144af37976733b558d716c', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 48ea72a87d7fc69d73aa2531ded8a5da9d13b2bd Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 16 May 2025 09:00:06 -0700 Subject: [PATCH 28/53] [3.32] Update `bin/internal/engine.version` to latest SHA. (#168979) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 409f1dc8dcfc1..541ab64da443b 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -38e19d01dcac654bbb941b771cc06efd4ed4a93f +3e54f90f1af3b4b9846fba24a1cace62ddf7f77a From 2e6bde0946bdd5458557fd6d574cc1cf3aac6cbc Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 16 May 2025 13:36:23 -0700 Subject: [PATCH 29/53] Stage the `CHANGELOG` for 3.32 so that a stable publish is 1-step. (#169010) This will not require an `engine.version` revision, so the last step will just be a publish step. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 603f9ec54eaed..1544980a5611a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ INTERNAL NOTE +## Flutter 3.32 Changes + +### [3.32.0](https://github.com/flutter/flutter/releases/tag/3.32.0) +Initial stable release. + ## Flutter 3.29 Changes ### [3.29.2](https://github.com/flutter/flutter/releases/tag/3.29.2) From 18818009497c581ede5d8a3b8b833b81d00cebb7 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Mon, 19 May 2025 10:54:07 -0700 Subject: [PATCH 30/53] [CP-beta][Impeller] separate immutable sampler descriptors. (#169074) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? #168114 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples This cherry pick fixes an issue with video flickering after a user scroll event in the package video_player on devices using Vulkan for rendering. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Visual jank on devices using Vulkan rendering and the video_player package. ### Workaround: Is there a workaround for this issue? No. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? pixel diffing is difficult to do without being flakey, video rendering pixel diffing is even harder. Was tested manually in the initial pr. ### Validation Steps: What are the steps to validate that this fix works? Using a device that utilizes Vulkan rendering, start the video_player example app, press play on the video, then scroll up or down - the visual flickering should be evident. --- .../flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc | 4 +++- engine/src/flutter/impeller/renderer/pipeline.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc index d02956cd2b65e..72ba5b99be8cd 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_vk.cc @@ -582,8 +582,10 @@ std::shared_ptr PipelineVK::CreateVariantForImmutableSamplers( if (!device_holder) { return nullptr; } + // Note: immutable sampler variant of a pipeline is the negation of the + // existing pipeline key. This keeps the descriptors separate. return (immutable_sampler_variants_[cache_key] = - Create(desc_, device_holder, library_, pipeline_key_, + Create(desc_, device_holder, library_, -1 * pipeline_key_, immutable_sampler)); } diff --git a/engine/src/flutter/impeller/renderer/pipeline.h b/engine/src/flutter/impeller/renderer/pipeline.h index 0bb5c7c4756d7..2dec9431bcdc7 100644 --- a/engine/src/flutter/impeller/renderer/pipeline.h +++ b/engine/src/flutter/impeller/renderer/pipeline.h @@ -18,7 +18,7 @@ namespace impeller { -using PipelineKey = uint64_t; +using PipelineKey = int64_t; class PipelineLibrary; template From be698c48a6750c8cb8e61c740ca9991bb947aba2 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 19 May 2025 12:59:14 -0700 Subject: [PATCH 31/53] [3.32] Set engine.version to `18818009497c581ede5d8a3b8b833b81d00cebb7`. (#169082) Sets to 18818009497c581ede5d8a3b8b833b81d00cebb7, the last git SHA. --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 541ab64da443b..7706e0df1349a 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -3e54f90f1af3b4b9846fba24a1cace62ddf7f77a +18818009497c581ede5d8a3b8b833b81d00cebb7 From 673806fbc16dc396f8bce3a027fda871ff1aa841 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 20 May 2025 09:52:05 -0700 Subject: [PATCH 32/53] [CP: 3.32] [Widget Inspector] Update on-device inspector button to generic unicode icon (#169092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/168846 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples This cherry pick fixes an issue where some users would see a question mark instead of the appropriate icon in the widget inspector button which toggles select-mode on/off. ### Impact Description: Users who don't have `cupertino_icons` as a dependency in their pubspec will see a question mark in one of the widget inspector buttons. Screenshot 2025-05-19 at 12 50 15 PM ### Workaround: Is there a workaround for this issue? Yes, add `cupertino_icons` package to user's pubspec. ### Risk: What is the risk level of this cherry-pick? Low ### Test Coverage: Are you confident that your fix is well-tested by automated tests? This was tested manually, there are no screenshot tests for this. However, this is only a UI change and not a behavior change. ### Validation Steps: What are the steps to validate that this fix works? 1. Run an app that doesn't have `cupertino_icons` in its pubspec 2. Open Flutter DevTools > Inspector 3. Enable select widget mode 4. Should not see a ? on any of the buttons --- packages/flutter/lib/src/cupertino/app.dart | 6 +++--- packages/flutter/lib/src/material/app.dart | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart index fc13d4e99ae7b..1deda1f9dd9bf 100644 --- a/packages/flutter/lib/src/cupertino/app.dart +++ b/packages/flutter/lib/src/cupertino/app.dart @@ -568,9 +568,9 @@ class _CupertinoAppState extends State { return _CupertinoInspectorButton.toggle( onPressed: onPressed, semanticLabel: semanticLabel, - // This icon is also used for the Material-styled button and for DevTools. - // It should be updated in all 3 places if changed. - icon: CupertinoIcons.cursor_rays, + // This unicode icon is also used for the Material-styled button and for + // DevTools. It should be updated in all 3 places if changed. + icon: const IconData(0x1F74A), toggledOn: selectionOnTapEnabled, ); } diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index d2083c0353946..7c3f9f61bce52 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -971,9 +971,9 @@ class _MaterialAppState extends State { return _MaterialInspectorButton.toggle( onPressed: onPressed, semanticLabel: semanticLabel, - // This icon is also used for the Cupertino-styled button and for DevTools. - // It should be updated in all 3 places if changed. - icon: CupertinoIcons.cursor_rays, + // This unicode icon is also used for the Cupertino-styled button and for + // DevTools. It should be updated in all 3 places if changed. + icon: const IconData(0x1F74A), isDarkTheme: _isDarkTheme(context), toggledOn: selectionOnTapEnabled, ); From 44b32ecbb704ceb5497199891d11efbdf39e50aa Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Fri, 23 May 2025 09:56:15 -0700 Subject: [PATCH 33/53] [CP-stable][reland] Fix regression in NDK version checking (#169289) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? There was no associated issue, but the problem is that https://github.com/flutter/flutter/pull/166727 broke Flutter Android builds on apps that use plugins with AGP versions less than 8.2. ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixes Flutter Android builds for apps which use plugins with old Android Gradle Plugin versions. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) The impact is a crash in the build process. ### Workaround: Is there a workaround for this issue? No ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Build an app that uses AGP < 8.2. --- .ci.yaml | 4 ++++ ...android_java11_dependency_smoke_tests.dart | 3 +++ ...dependency_smoke_test_task_definition.dart | 24 ++++++++++++++++--- .../src/main/kotlin/FlutterPluginUtils.kt | 19 ++++++++++++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 9d8f8f5439c93..ca1620d888f41 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1520,10 +1520,12 @@ targets: test_timeout_secs: "2700" runIf: - packages/flutter_tools/templates/** + - packages/flutter_tools/gradle/** - .ci.yaml - engine/** - DEPS - dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart + - dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart - name: Linux android_java17_dependency_smoke_tests recipe: devicelab/devicelab_drone @@ -1542,10 +1544,12 @@ targets: test_timeout_secs: "2700" runIf: - packages/flutter_tools/templates/** + - packages/flutter_tools/gradle/** - .ci.yaml - engine/** - DEPS - dev/devicelab/bin/tasks/android_java17_dependency_smoke_tests.dart + - dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart - name: Linux tool_tests_commands recipe: flutter/flutter_drone diff --git a/dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart b/dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart index 94c3d37a465ee..40883ebe88e72 100644 --- a/dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart +++ b/dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart @@ -40,11 +40,14 @@ List versionTuples = [ kotlinVersion: '1.7.10', compileSdkVersion: '34', ), + // minSdk bump required due to a bug in the default version of r8 used by AGP + // 7.4.0. See http://issuetracker.google.com/issues/357553178. VersionTuple( agpVersion: '7.4.0', gradleVersion: '7.5', kotlinVersion: '1.8.10', compileSdkVersion: '34', + minSdkVersion: '24', ), ]; diff --git a/dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart b/dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart index d333046fffe29..ee46e68bdaae5 100644 --- a/dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart +++ b/dev/devicelab/lib/framework/dependency_smoke_test_task_definition.dart @@ -61,6 +61,7 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-GRADLE_REPLACE const String gradleReplacementString = 'GRADLE_REPLACE_ME'; final RegExp flutterCompileSdkString = RegExp(r'flutter\.compileSdkVersion|flutter\.compileSdk'); +final RegExp flutterMinSdkString = RegExp(r'flutter\.minSdkVersion|flutter\.minSdk'); /// A simple class containing a Kotlin, Gradle, and AGP version. class VersionTuple { @@ -69,12 +70,14 @@ class VersionTuple { required this.gradleVersion, required this.kotlinVersion, this.compileSdkVersion, + this.minSdkVersion, }); String agpVersion; String gradleVersion; String kotlinVersion; String? compileSdkVersion; + String? minSdkVersion; @override String toString() { @@ -108,10 +111,10 @@ Future buildFlutterApkWithSpecifiedDependencyVersions({ final String appPath = '${innerTempDir.absolute.path}/dependency_checker_app'; + final File appGradleBuild = getAndroidBuildFile( + localFileSystem.path.join(appPath, 'android', 'app'), + ); if (versions.compileSdkVersion != null) { - final File appGradleBuild = getAndroidBuildFile( - localFileSystem.path.join(appPath, 'android', 'app'), - ); final String appBuildContent = appGradleBuild.readAsStringSync().replaceFirst( flutterCompileSdkString, versions.compileSdkVersion!, @@ -119,6 +122,14 @@ Future buildFlutterApkWithSpecifiedDependencyVersions({ appGradleBuild.writeAsStringSync(appBuildContent); } + if (versions.minSdkVersion != null) { + final String appBuildContent = appGradleBuild.readAsStringSync().replaceFirst( + flutterMinSdkString, + versions.minSdkVersion!, + ); + appGradleBuild.writeAsStringSync(appBuildContent); + } + // Modify gradle version to passed in version. final File gradleWrapperProperties = localFileSystem.file( localFileSystem.path.join( @@ -144,6 +155,13 @@ Future buildFlutterApkWithSpecifiedDependencyVersions({ .replaceFirst(kgpReplacementString, versions.kotlinVersion); await gradleSettingsFile.writeAsString(settingsContent, flush: true); + section('Add a dependency on a plugin'); + await flutter( + 'pub', + options: ['add', 'shared_preferences_android:2.4.7'], // Chosen randomly. + workingDirectory: appPath, + ); + // Ensure that gradle files exists from templates. section( "Ensure 'flutter build apk' succeeds with Gradle ${versions.gradleVersion}, AGP ${versions.agpVersion}, and Kotlin ${versions.kotlinVersion}", diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 2b622591eb8ef..ff4b6c8b32090 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -517,8 +517,16 @@ object FlutterPluginUtils { getCompileSdkFromProject(project).toIntOrNull() ?: Int.MAX_VALUE var maxPluginCompileSdkVersion = projectCompileSdkVersion - val projectNdkVersion = - getAndroidExtension(project).ndkVersion + // TODO(gmackall): This should be updated to reflect newer templates. + // The default for AGP 4.1.0 used in old templates. + val ndkVersionIfUnspecified = "21.1.6352462" + + // TODO(gmackall): We can remove this elvis when our minimum AGP is >= 8.2. + // This value (ndkVersion) is nullable on AGP versions below that. + // See https://developer.android.com/reference/tools/gradle-api/8.1/com/android/build/api/dsl/CommonExtension#ndkVersion(). + @Suppress("USELESS_ELVIS") + val projectNdkVersion: String = + getAndroidExtension(project).ndkVersion ?: ndkVersionIfUnspecified var maxPluginNdkVersion = projectNdkVersion var numProcessedPlugins = pluginList.size val pluginsWithHigherSdkVersion = mutableListOf() @@ -543,8 +551,13 @@ object FlutterPluginUtils { ) ) } + + // TODO(gmackall): We can remove this elvis when our minimum AGP is >= 8.2. + // This value (ndkVersion) is nullable on AGP versions below that. + // See https://developer.android.com/reference/tools/gradle-api/8.1/com/android/build/api/dsl/CommonExtension#ndkVersion(). + @Suppress("USELESS_ELVIS") val pluginNdkVersion: String = - getAndroidExtension(pluginProject).ndkVersion + getAndroidExtension(pluginProject).ndkVersion ?: ndkVersionIfUnspecified maxPluginNdkVersion = VersionUtils.mostRecentSemanticVersion( pluginNdkVersion, From 6e07e1f4bd7a7bfe11a8cfbf5ec556e62fcf22ea Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Fri, 23 May 2025 13:43:22 -0700 Subject: [PATCH 34/53] [CP-stable]Skip running `Linux fuchsia_test` on non-master channel. (#169380) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/169101 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Reduces the cost of running the (sometimes flaky) `Linux fuchsia_test` on release branches. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) N/A - CI infra optimization. ### Workaround: Is there a workaround for this issue? N/A - CI infra optimization ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? N/A - CI infra optimization --- engine/src/flutter/.ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml index 2c476704ac02d..874f018545dfa 100644 --- a/engine/src/flutter/.ci.yaml +++ b/engine/src/flutter/.ci.yaml @@ -163,6 +163,9 @@ targets: - name: Linux linux_fuchsia_tests recipe: engine_v2/engine_v2 timeout: 90 + enabled_branches: + # Don't run this on release branches + - master properties: config_name: linux_fuchsia_tests # Do not remove(https://github.com/flutter/flutter/issues/144644) From 9057717ebce9a2e19d08e8b17612158c451f270a Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Mon, 26 May 2025 18:01:40 -0700 Subject: [PATCH 35/53] [CP-stable]Use `.flutter-plugins-dependencies` for crash reporting. (#169484) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/pull/169319 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixed a bug where the `flutter` tool crash reporting did not include what plugins were being used by the current project. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Tool crash reports are missing plugins in use. ### Workaround: Is there a workaround for this issue? Yes, `flutter config --no-explicit-package-dependencies`, but that has other project impact, or copying and pasting the plugins manually. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? See unit tests. --- .../lib/src/reporting/github_template.dart | 66 ++++++++++++++----- .../general.shard/github_template_test.dart | 34 ++++++++-- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/packages/flutter_tools/lib/src/reporting/github_template.dart b/packages/flutter_tools/lib/src/reporting/github_template.dart index b7ca3ed67e2f8..d0d7f2f265791 100644 --- a/packages/flutter_tools/lib/src/reporting/github_template.dart +++ b/packages/flutter_tools/lib/src/reporting/github_template.dart @@ -143,26 +143,62 @@ ${_projectMetadataInformation()} ..writeln('**Creation channel**: ${metadata.versionChannel}') ..writeln('**Creation framework version**: ${metadata.versionRevision}'); - final File file = project.flutterPluginsFile; + final File file = project.flutterPluginsDependenciesFile; if (file.existsSync()) { - description.writeln('### Plugins'); - // Format is: - // camera=/path/to/.pub-cache/hosted/pub.dartlang.org/camera-0.5.7+2/ - for (final String plugin in project.flutterPluginsFile.readAsLinesSync()) { - final List pluginParts = plugin.split('='); - if (pluginParts.length != 2) { - continue; - } - // Write the last part of the path, which includes the plugin name and version. - // Example: camera-0.5.7+2 - final List pathParts = _fileSystem.path.split(pluginParts[1]); - description.writeln(pathParts.isEmpty ? pluginParts.first : pathParts.last); - } + _writePlugins(description, file); } - return description.toString(); } on Exception catch (exception) { return exception.toString(); } } + + void _writePlugins(StringBuffer description, File file) { + description.writeln('### Plugins'); + // Format is: + // { + // "plugins": { + // "ios": [ + // { + // "path": "/path/to/.pub-cache/hosted/pub.dartlang.org/camera-0.5.7+2/", + // } + // ] + // } + // } + final Object? json = jsonDecode(file.readAsStringSync()); + if (json is! Map) { + return; + } + final Object? plugins = json['plugins']; + if (plugins is! Map) { + return; + } + final Set pluginPaths = {}; + for (final Object? pluginList in plugins.values) { + if (pluginList is! List) { + continue; + } + for (final Object? plugin in pluginList) { + if (plugin is! Map) { + continue; + } + final String? path = plugin['path'] as String?; + if (path != null) { + pluginPaths.add(path); + } + } + } + if (pluginPaths.isEmpty) { + return; + } + for (final String path in pluginPaths) { + // Write the last part of the path, which includes the plugin name and version. + // Example: camera-0.5.7+2 + final List pathParts = _fileSystem.path.split(path); + if (pathParts.isEmpty) { + continue; + } + description.writeln(pathParts.last); + } + } } diff --git a/packages/flutter_tools/test/general.shard/github_template_test.dart b/packages/flutter_tools/test/general.shard/github_template_test.dart index 6821f2eed9d6a..6afabec1803f0 100644 --- a/packages/flutter_tools/test/general.shard/github_template_test.dart +++ b/packages/flutter_tools/test/general.shard/github_template_test.dart @@ -13,6 +13,33 @@ import 'package:flutter_tools/src/reporting/github_template.dart'; import '../src/common.dart'; import '../src/context.dart'; +const String _kPluginsFile = ''' +{ + "plugins": { + "ios": [ + { + "name": "camera", + "path": "/fake/pub.dartlang.org/camera-0.5.7+2/" + }, + { + "name": "device_info", + "path": "/fake/pub.dartlang.org/device_info-0.4.1+4/" + } + ], + "android": [ + { + "name": "camera", + "path": "/fake/pub.dartlang.org/camera-0.5.7+2/" + }, + { + "name": "device_info", + "path": "/fake/pub.dartlang.org/device_info-0.4.1+4/" + } + ] + } +} +'''; + void main() { late BufferLogger logger; late FileSystem fs; @@ -199,11 +226,8 @@ flutter: iosBundleIdentifier: com.example.failing.ios '''); - final File pluginsFile = projectDirectory.childFile('.flutter-plugins'); - pluginsFile.writeAsStringSync(''' -camera=/fake/pub.dartlang.org/camera-0.5.7+2/ -device_info=/fake/pub.dartlang.org/pub.dartlang.org/device_info-0.4.1+4/ - '''); + final File pluginsFile = projectDirectory.childFile('.flutter-plugins-dependencies'); + pluginsFile.writeAsStringSync(_kPluginsFile); final File metadataFile = projectDirectory.childFile('.metadata'); metadataFile.writeAsStringSync(''' From 80f085c97d620edc08a27748a6591e6d48430151 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 27 May 2025 15:55:08 -0400 Subject: [PATCH 36/53] [stable] Roll `package:dds` to 5.0.2 (#169471) (#169515) Fixes https://github.com/flutter/flutter/issues/156793 ### Issue Link: https://github.com/flutter/flutter/issues/156793 ### Changelog Description: Fix flaky crash when targeting web applications via IDEs using the DAP. ### Impact Description: The `flutter debug-adapter` process started by IDEs can crash when requesting isolate information from a Flutter web application that has disposed its isolate (i.e., at shutdown or due to a hot restart). This is the top crasher for `flutter_tools`, accounting for ~66% of all crashes for 3.32.0. ### Workaround: Is there a workaround for this issue? No. ### Risk: This CP is low risk as only additional exception handling was added to the DAP logic in `package:dds` (see https://dart-review.googlesource.com/c/sdk/+/431060) to handle the specific case outlined in the issue. ### Test Coverage: This issue is difficult to reproduce without injecting test-only code via custom VM service RPCs across multiple repositories. Testing was done manually with this approach, but the testing code is not committed. ### Validation Steps: IDE extensions don't crash when interacting with Flutter Web applications during hot restart / application shutdown. --- CHANGELOG.md | 4 +++ packages/flutter_tools/pubspec.yaml | 6 ++-- .../debug_adapter/flutter_adapter_test.dart | 28 +++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1544980a5611a..ceed58e4a3be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ INTERNAL NOTE ## Flutter 3.32 Changes +### [3.32.1](https://github.com/flutter/flutter/releases/tag/3.32.1) + +- [flutter/156793](https://github.com/flutter/flutter/issues/156793) - Fix flaky crash when targeting web applications via IDEs using the DAP. + ### [3.32.0](https://github.com/flutter/flutter/releases/tag/3.32.0) Initial stable release. diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 090aa0e8eae32..4e3488dad069a 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: # https://github.com/flutter/flutter/blob/main/docs/infra/Updating-dependencies-in-Flutter.md archive: 3.6.1 args: 2.7.0 - dds: 5.0.0 + dds: 5.0.2 dwds: 24.3.10 code_builder: 4.10.1 completion: 1.0.1 @@ -78,7 +78,7 @@ dependencies: csslib: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dap: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dds_service_extensions: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - devtools_shared: 11.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + devtools_shared: 11.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dtd: 2.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" extension_discovery: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fixnum: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -122,4 +122,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: c093 +# PUBSPEC CHECKSUM: 7396 diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart index c14ec35db808c..c85946ee8d6fd 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart @@ -272,16 +272,18 @@ The relevant error-causing widget was: ); }); - testWithoutContext('correctly outputs colored exceptions when supported', () async { - final BasicProjectThatThrows project = BasicProjectThatThrows(); - final String output = await getExceptionOutput(project, noDebug: false, ansiColors: true); - - // Frames in the stack trace that are the users own code will be unformatted, but - // frames from the framework are faint (starting with `\x1B[2m`). - - expect( - output, - contains(''' + testWithoutContext( + 'correctly outputs colored exceptions when supported', + () async { + final BasicProjectThatThrows project = BasicProjectThatThrows(); + final String output = await getExceptionOutput(project, noDebug: false, ansiColors: true); + + // Frames in the stack trace that are the users own code will be unformatted, but + // frames from the framework are faint (starting with `\x1B[2m`). + + expect( + output, + contains(''' ════════ Exception caught by widgets library ═══════════════════════════════════ The following _Exception was thrown building App(dirty): Exception: c @@ -298,8 +300,10 @@ When the exception was thrown, this was the stack: ^ source: package:flutter/src/widgets/framework.dart \x1B[2m#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:1:1)\x1B[0m ^ source: package:flutter/src/widgets/framework.dart'''), - ); - }); + ); + }, + skip: true, // DAP URI parsing bug, https://github.com/dart-lang/sdk/issues/60797 + ); testWithoutContext('correctly outputs exceptions in noDebug mode', () async { final BasicProjectThatThrows project = BasicProjectThatThrows(); From c56879b5f5a7cbae4e0a900cdf363d89b5659b86 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 28 May 2025 09:18:18 -0700 Subject: [PATCH 37/53] Roll Skia to the flutter/3.32 branch (#169531) That branch includes a cherry pick of a fix for https://github.com/flutter/flutter/issues/168849 --- DEPS | 2 +- engine/src/flutter/ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index b3d3cb6d0c3b6..6b89e29ff682f 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'ac01f9306a0c08acf128d37bbd7b3e199525cc40', + 'skia_revision': '1f39b47bf067193e98f92a75f7ae824129c18d03', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. diff --git a/engine/src/flutter/ci/licenses_golden/licenses_skia b/engine/src/flutter/ci/licenses_golden/licenses_skia index 63ffe75d7de4a..622b0c51b1e12 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_skia +++ b/engine/src/flutter/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 6385fd7cd2f7be2e7ceee9d4b05eaf04 +Signature: 508e4fd642003d8487bf34df9a6b4ac2 ==================================================================================================== LIBRARY: etc1 From 7d3efe46434dd2ed33b58e7277c8b1c25930b4b3 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Wed, 28 May 2025 09:39:01 -0700 Subject: [PATCH 38/53] [CP-stable]Fixes tab semantics gets dropped if the child produce a semantics node (#169362) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/169175 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixed unexpected crash when using Tab and TabBar widgets. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) app crash ### Workaround: Is there a workaround for this issue? Wrap the Tab widget with a MergeSemantics widget will mitigate the issue. ### Risk: What is the risk level of this cherry-pick? - [O] Low ### Test Coverage: Are you confident that your fix is well-tested by automated tests? - [O] Yes ### Validation Steps: What are the steps to validate that this fix works? create a TabBar that has a Tab with image widget ```dart TabBar( tabs: [ Tab(icon: Image.network('https://some-url')), Tab(icon: Icon(Icons.beach_access_sharp)), Tab(icon: Icon(Icons.brightness_5_sharp)), ], ), ``` --- packages/flutter/lib/src/material/tabs.dart | 1 + .../flutter/lib/src/semantics/semantics.dart | 3 +++ packages/flutter/test/material/tabs_test.dart | 18 +++++++++++++++-- .../test/semantics/semantics_test.dart | 20 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 084b3b0a7614b..c85ef0caf7748 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1976,6 +1976,7 @@ class _TabBarState extends State { ), ), ); + wrappedTabs[index] = MergeSemantics(child: wrappedTabs[index]); if (!widget.isScrollable && effectiveTabAlignment == TabAlignment.fill) { wrappedTabs[index] = Expanded(child: wrappedTabs[index]); } diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index f4b5e0fe2093b..c8fedd4b4b18a 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -4300,6 +4300,9 @@ class SemanticsOwner extends ChangeNotifier { return null; } if (node.mergeAllDescendantsIntoThisNode) { + if (node._canPerformAction(action)) { + return node._actions[action]; + } SemanticsNode? result; node._visitDescendants((SemanticsNode child) { if (child._canPerformAction(action)) { diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 0e71d0a9cac34..ba1bb8385d2b5 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -156,6 +154,22 @@ void main() { expect(tester.renderObject(find.byType(CustomPaint)).debugNeedsPaint, true); }); + testWidgets('tab semantics role test', (WidgetTester tester) async { + // Regressing test for https://github.com/flutter/flutter/issues/169175 + // Creates an image semantics node with zero size. + await tester.pumpWidget( + boilerplate( + child: DefaultTabController( + length: 1, + child: TabBar( + tabs: [Tab(icon: Semantics(image: true, child: const SizedBox.shrink()))], + ), + ), + ), + ); + expect(find.byType(Tab), findsOneWidget); + }); + testWidgets('Tab sizing - icon', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index f30ec100805c2..c5c103404f7db 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -918,6 +918,26 @@ void main() { expect(newNode.id, expectId); }); + test('performActionAt can hit test on merged semantics node', () { + bool tapped = false; + final SemanticsOwner owner = SemanticsOwner(onSemanticsUpdate: (SemanticsUpdate update) {}); + final SemanticsNode root = SemanticsNode.root(owner: owner) + ..rect = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + final SemanticsNode merged = SemanticsNode()..rect = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + final SemanticsConfiguration mergeConfig = + SemanticsConfiguration() + ..isSemanticBoundary = true + ..isMergingSemanticsOfDescendants = true + ..onTap = () => tapped = true; + final SemanticsConfiguration rootConfig = SemanticsConfiguration()..isSemanticBoundary = true; + + merged.updateWith(config: mergeConfig, childrenInInversePaintOrder: []); + root.updateWith(config: rootConfig, childrenInInversePaintOrder: [merged]); + + owner.performActionAt(const Offset(5, 5), SemanticsAction.tap); + expect(tapped, isTrue); + }); + test('Tags show up in debug properties', () { final SemanticsNode actionNode = SemanticsNode()..tags = {RenderViewport.useTwoPaneSemantics}; From 1425e5e9ec5eeb4f225c401d8db69b860e0fde9a Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Wed, 28 May 2025 14:26:27 -0700 Subject: [PATCH 39/53] Update dart revision (3.8.1) for 3.32.1 release (#169601) This updated the dart revision for the upcoming flutter stable release, 3.32.1 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 6b89e29ff682f..870a8300a9a76 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'b04011c77cd93e6ab9144af37976733b558d716c', + 'dart_revision': '05589740efb305ceef593b3db6cab2910c17d480', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 637479460db96455824c39dd622747f6c6f38a62 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 29 May 2025 08:54:51 -0700 Subject: [PATCH 40/53] Update `engine.version` to `1425e5e9ec5eeb4f225c401d8db69b860e0fde9a`. (#169675) ```sh git log -1 --pretty=format:%H -- "$(git rev-parse --show-toplevel)/DEPS" "$(git rev-parse --show-toplevel)/engine" ``` --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 7706e0df1349a..722ab7cd4271b 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -18818009497c581ede5d8a3b8b833b81d00cebb7 +1425e5e9ec5eeb4f225c401d8db69b860e0fde9a From 0a159b315d33153beba629e3bc93182f331853c2 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 29 May 2025 09:23:50 -0700 Subject: [PATCH 41/53] [CP-stable]Roll forward: "Initialize default-app-flavor" (#169298) (#169623) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/pull/169602 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixes a bug where `appFlavor` is `null` when being run with `flutter test` or being hot-restarted. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Cannot reliably use `appFlavor` without rebuilding the app from scratch. ### Workaround: Is there a workaround for this issue? Do not use hot restart, do not use `flutter test`. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Automated test coverage. --- .../lib/src/build_system/targets/common.dart | 24 ++--- .../lib/src/runner/flutter_command.dart | 12 +++ .../build_system/targets/common_test.dart | 99 ++++++++++--------- .../runner/flutter_command_test.dart | 92 +++++++++++++++++ .../default_flavor_test.dart | 74 ++++++++++++++ .../test/integration.shard/test_driver.dart | 5 +- 6 files changed, 247 insertions(+), 59 deletions(-) create mode 100644 packages/flutter_tools/test/integration.shard/default_flavor_test.dart diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index 735c380b10f84..67731019a050d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -6,16 +6,14 @@ import 'package:package_config/package_config.dart'; import '../../artifacts.dart'; import '../../base/build.dart'; -import '../../base/common.dart'; import '../../base/file_system.dart'; import '../../base/io.dart'; import '../../build_info.dart'; import '../../compile.dart'; import '../../dart/package_map.dart'; import '../../devfs.dart'; -import '../../globals.dart' as globals show platform, xcode; +import '../../globals.dart' as globals show xcode; import '../../project.dart'; -import '../../runner/flutter_command.dart'; import '../build_system.dart'; import '../depfile.dart'; import '../exceptions.dart'; @@ -310,15 +308,17 @@ class KernelSnapshot extends Target { if (flavor == null) { return; } - if (globals.platform.environment[kAppFlavor] != null) { - throwToolExit('$kAppFlavor is used by the framework and cannot be set in the environment.'); - } - if (dartDefines.any((String define) => define.startsWith(kAppFlavor))) { - throwToolExit( - '$kAppFlavor is used by the framework and cannot be ' - 'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}', - ); - } + + // It is possible there is a flavor already in dartDefines, from another + // part of the build process, but this should take precedence as it happens + // last (xcodebuild execution). + // + // See https://github.com/flutter/flutter/issues/169598. + + // If the flavor is already in the dart defines, remove it. + dartDefines.removeWhere((String define) => define.startsWith(kAppFlavor)); + + // Then, add it to the end. dartDefines.add('$kAppFlavor=$flavor'); } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 17ab07e321684..f9dceecb34a13 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1438,6 +1438,18 @@ abstract class FlutterCommand extends Command { final String? cliFlavor = argParser.options.containsKey('flavor') ? stringArg('flavor') : null; final String? flavor = cliFlavor ?? defaultFlavor; + if (globals.platform.environment[kAppFlavor] != null) { + throwToolExit('$kAppFlavor is used by the framework and cannot be set in the environment.'); + } + if (dartDefines.any((String define) => define.startsWith(kAppFlavor))) { + throwToolExit( + '$kAppFlavor is used by the framework and cannot be ' + 'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}', + ); + } + if (flavor != null) { + dartDefines.add('$kAppFlavor=$flavor'); + } _addFlutterVersionToDartDefines(globals.flutterVersion, dartDefines); return BuildInfo( diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart index 8ceab06877d4a..5591d93710c09 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_tools/src/build_system/exceptions.dart'; import 'package:flutter_tools/src/build_system/targets/common.dart'; import 'package:flutter_tools/src/build_system/targets/ios.dart'; import 'package:flutter_tools/src/compile.dart'; +import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:test/fake.dart'; @@ -440,58 +441,65 @@ void main() { ); testUsingContext( - "tool exits when $kAppFlavor is already set in user's environment", + 'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if ios app', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - final Future buildResult = const KernelSnapshot().build( - androidEnvironment - ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.android) - ..defines[kBuildMode] = BuildMode.debug.cliName - ..defines[kFlavor] = 'strawberry' - ..defines[kTrackWidgetCreation] = 'false', + final String build = iosEnvironment.buildDir.path; + final String flutterPatchedSdkPath = artifacts.getArtifactPath( + Artifact.flutterPatchedSdkPath, + platform: TargetPlatform.ios, + mode: BuildMode.debug, ); - - expect( - buildResult, - throwsToolExit( - message: '$kAppFlavor is used by the framework and cannot be set in the environment.', + fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true); + processManager.addCommands([ + FakeCommand( + command: [ + artifacts.getArtifactPath(Artifact.engineDartAotRuntime), + artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), + '--sdk-root', + '$flutterPatchedSdkPath/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-D$kAppFlavor=chocolate', + ...buildModeOptions(BuildMode.debug, []), + '--no-link-platform', + '--packages', + '/.dart_tool/package_config.json', + '--output-dill', + '$build/app.dill', + '--depfile', + '$build/kernel_snapshot_program.d', + '--incremental', + '--initialize-from-dill', + '$build/app.dill', + '--verbosity=error', + 'file:///lib/main.dart', + ], + stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n', ), - ); - }, - overrides: { - Platform: () => FakePlatform(environment: {kAppFlavor: 'I was already set'}), - }, - ); + ]); - testUsingContext( - 'tool exits when $kAppFlavor is set in --dart-define or --dart-define-from-file', - () async { - fileSystem.file('.dart_tool/package_config.json') - ..createSync(recursive: true) - ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - final Future buildResult = const KernelSnapshot().build( - androidEnvironment - ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.android) + await const KernelSnapshot().build( + iosEnvironment + ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.ios) ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' - ..defines[kDartDefines] = encodeDartDefines([kAppFlavor, 'strawberry']) + ..defines[kXcodeConfiguration] = 'Debug-chocolate' ..defines[kTrackWidgetCreation] = 'false', ); - expect( - buildResult, - throwsToolExit( - message: - '$kAppFlavor is used by the framework and cannot be set using --dart-define or --dart-define-from-file', - ), - ); + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + XcodeProjectInterpreter: + () => FakeXcodeProjectInterpreter(schemes: ['Runner', 'chocolate']), }, ); testUsingContext( - 'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if ios app', + 'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if macos app', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) @@ -499,10 +507,10 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.ios, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); - fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true); + fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true); processManager.addCommands([ FakeCommand( command: [ @@ -514,7 +522,6 @@ void main() { '--no-print-incremental-dependencies', '-D$kAppFlavor=chocolate', ...buildModeOptions(BuildMode.debug, []), - '--no-link-platform', '--packages', '/.dart_tool/package_config.json', '--output-dill', @@ -533,7 +540,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.ios) + ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin) ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' ..defines[kXcodeConfiguration] = 'Debug-chocolate' @@ -549,7 +556,7 @@ void main() { ); testUsingContext( - 'KernelSnapshot sets flavor in dartDefines from Xcode build configuration if macos app', + 'KernelSnapshot does not add kAppFlavor twice to Dart defines', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) @@ -560,7 +567,6 @@ void main() { platform: TargetPlatform.darwin, mode: BuildMode.debug, ); - fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true); processManager.addCommands([ FakeCommand( command: [ @@ -570,7 +576,7 @@ void main() { '$flutterPatchedSdkPath/', '--target=flutter', '--no-print-incremental-dependencies', - '-D$kAppFlavor=chocolate', + '-D$kAppFlavor=strawberry', ...buildModeOptions(BuildMode.debug, []), '--packages', '/.dart_tool/package_config.json', @@ -592,16 +598,17 @@ void main() { iosEnvironment ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin) ..defines[kBuildMode] = BuildMode.debug.cliName + ..defines[kDartDefines] = base64Encode(utf8.encode('FLUTTER_APP_FLAVOR=vanilla')) ..defines[kFlavor] = 'strawberry' - ..defines[kXcodeConfiguration] = 'Debug-chocolate' ..defines[kTrackWidgetCreation] = 'false', ); expect(processManager, hasNoRemainingExpectations); }, overrides: { - XcodeProjectInterpreter: - () => FakeXcodeProjectInterpreter(schemes: ['Runner', 'chocolate']), + Platform: () => macPlatform, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, }, ); diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index 00b603d354f2a..fbda6491f992a 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -1270,6 +1270,98 @@ flutter: ); }); + testUsingContext( + "tool exits when $kAppFlavor is already set in user's environemnt", + () async { + final CommandRunner runner = createTestCommandRunner( + _TestRunCommandThatOnlyValidates(), + ); + expect( + runner.run(['run', '--no-pub', '--no-hot']), + throwsToolExit( + message: '$kAppFlavor is used by the framework and cannot be set in the environment.', + ), + ); + }, + overrides: { + DeviceManager: + () => FakeDeviceManager()..attachedDevices = [FakeDevice('name', 'id')], + FileSystem: () { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + fileSystem.file('lib/main.dart').createSync(recursive: true); + fileSystem.file('pubspec.yaml').createSync(); + return fileSystem; + }, + ProcessManager: FakeProcessManager.empty, + Platform: () => FakePlatform()..environment = {kAppFlavor: 'AlreadySet'}, + }, + ); + + testUsingContext( + 'tool exits when $kAppFlavor is set in --dart-define', + () async { + final CommandRunner runner = createTestCommandRunner( + _TestRunCommandThatOnlyValidates(), + ); + expect( + runner.run([ + 'run', + '--dart-define=$kAppFlavor=AlreadySet', + '--no-pub', + '--no-hot', + ]), + throwsToolExit( + message: '$kAppFlavor is used by the framework and cannot be set using --dart-define', + ), + ); + }, + overrides: { + DeviceManager: + () => FakeDeviceManager()..attachedDevices = [FakeDevice('name', 'id')], + FileSystem: () { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + fileSystem.file('lib/main.dart').createSync(recursive: true); + fileSystem.file('pubspec.yaml').createSync(); + return fileSystem; + }, + ProcessManager: FakeProcessManager.empty, + }, + ); + + testUsingContext( + 'tool exits when $kAppFlavor is set in --dart-define-from-file', + () async { + final CommandRunner runner = createTestCommandRunner( + _TestRunCommandThatOnlyValidates(), + ); + expect( + runner.run([ + 'run', + '--dart-define-from-file=config.json', + '--no-pub', + '--no-hot', + ]), + throwsToolExit( + message: '$kAppFlavor is used by the framework and cannot be set using --dart-define', + ), + ); + }, + overrides: { + DeviceManager: + () => FakeDeviceManager()..attachedDevices = [FakeDevice('name', 'id')], + FileSystem: () { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + fileSystem.file('lib/main.dart').createSync(recursive: true); + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('config.json') + ..createSync() + ..writeAsStringSync('{"$kAppFlavor": "AlreadySet"}'); + return fileSystem; + }, + ProcessManager: FakeProcessManager.empty, + }, + ); + group('Flutter version', () { for (final String dartDefine in FlutterCommand.flutterVersionDartDefines) { testUsingContext( diff --git a/packages/flutter_tools/test/integration.shard/default_flavor_test.dart b/packages/flutter_tools/test/integration.shard/default_flavor_test.dart new file mode 100644 index 0000000000000..f84008a05d9c7 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/default_flavor_test.dart @@ -0,0 +1,74 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@Tags(['flutter-test-driver']) +library; + +import 'package:flutter_tools/src/base/file_system.dart'; + +import '../src/common.dart'; +import 'test_data/project.dart'; +import 'test_driver.dart'; +import 'test_utils.dart'; + +void main() { + final Project project = _DefaultFlavorProject(); + late Directory tempDir; + late FlutterTestTestDriver flutter; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('default_flavor_test.'); + await project.setUpIn(tempDir); + flutter = FlutterTestTestDriver(tempDir); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + testWithoutContext('Reads "default-flavor" in "flutter test"', () async { + await flutter.test(); + + // Without an assertion, this test always passes. + final int? exitCode = await flutter.done; + expect(exitCode, 0, reason: 'flutter test failed with exit code $exitCode'); + }); +} + +final class _DefaultFlavorProject extends Project { + @override + final String main = r''' + // Irrelevant to this test. + void main() {} + '''; + + @override + final String pubspec = r''' + name: test + environment: + sdk: ^3.7.0-0 + + flutter: + default-flavor: dev + + dependencies: + flutter: + sdk: flutter + dev_dependencies: + flutter_test: + sdk: flutter + '''; + + @override + final String test = r''' + import 'package:flutter/services.dart'; + import 'package:flutter_test/flutter_test.dart'; + + void main() { + test('receives default-flavor with flutter test', () async { + expect(appFlavor, 'dev'); + }); + } + '''; +} diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index 0dd6ab3356d5d..1f8af11a11a90 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -138,7 +138,10 @@ abstract final class FlutterTestDriver { _stderr.stream.listen((String message) => _debugPrint(message, topic: '<=stderr=')); } - Future get done async => _process?.exitCode; + /// Completes when process exits with the given exit code. + /// + /// If the process has never been started, complets with `null`. + Future get done async => _process?.exitCode; Future connectToVmService({bool pauseOnExceptions = false}) async { _vmService = await vmServiceConnectUri('$_vmServiceWsUri'); From b25305a8832cfc6ba632a7f87ad455e319dccce8 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 29 May 2025 10:40:06 -0700 Subject: [PATCH 42/53] Update engine.version and changelog for upcoming stable release 3.32.1 (#169619) Update engine.version and changelog for upcoming stable release 3.32.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceed58e4a3be2..02a31bd06675e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,13 @@ INTERNAL NOTE ### [3.32.1](https://github.com/flutter/flutter/releases/tag/3.32.1) +- [flutter/156793](https://github.com/flutter/flutter/issues/156793) - Fixes flaky crash when targeting web applications via IDEs using the DAP. +- [flutter/168849](https://github.com/flutter/flutter/issues/168849) - Fixes an issue rendering wide gamut images. +- [flutter/168846](https://github.com/flutter/flutter/issues/168846) - Fixes an issue displaying the wrong icons in the widget inspector for some apps. +- [flutter/167011](https://github.com/flutter/flutter/pull/167011) - Fixes Flutter Android builds for apps which use plugins with old Android Gradle Plugin versions. +- [flutter/169101](https://github.com/flutter/flutter/issues/169101) - Reduces the cost of running the (sometimes flaky) Linux fuchsia_test on release branches. +- [flutter/169318](https://github.com/flutter/flutter/issues/169318) - Fixed a bug where the flutter tool crash reporting did not include what plugins were being used by the current project. +- [flutter/169160](https://github.com/flutter/flutter/issues/169160) Fixed a bug where `appFlavor` is null after hot restarts or during `flutter test`. - [flutter/156793](https://github.com/flutter/flutter/issues/156793) - Fix flaky crash when targeting web applications via IDEs using the DAP. ### [3.32.0](https://github.com/flutter/flutter/releases/tag/3.32.0) From 109150893958777c8f2215f6cfd3e89e984e8dea Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 30 May 2025 12:17:36 -0700 Subject: [PATCH 43/53] [CP-Stable] Use `Linux windows_*_engine` orchestrators (#169772) Cherry-picks https://github.com/flutter/flutter/pull/168941. This is a pure infra change so release builds do not use a valuable Windows release builder just to spawn other builds. --- engine/src/flutter/.ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/.ci.yaml b/engine/src/flutter/.ci.yaml index 874f018545dfa..848e612fe5342 100644 --- a/engine/src/flutter/.ci.yaml +++ b/engine/src/flutter/.ci.yaml @@ -503,7 +503,7 @@ targets: - os=Mac-14 - cpu=x86 - - name: Windows windows_android_aot_engine + - name: Linux windows_android_aot_engine recipe: engine_v2/engine_v2 timeout: 120 properties: @@ -513,9 +513,9 @@ targets: # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform drone_dimensions: - - os=Windows + - os=Linux - - name: Windows windows_host_engine + - name: Linux windows_host_engine recipe: engine_v2/engine_v2 timeout: 120 properties: @@ -525,7 +525,7 @@ targets: # Do not remove(https://github.com/flutter/flutter/issues/144644) # Scheduler will fail to get the platform drone_dimensions: - - os=Windows + - os=Linux - name: Windows windows_host_engine_test recipe: engine_v2/engine_v2 From 04f9ead567a7fe8fa11fc6c6b83ad65b8ccd1892 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Fri, 30 May 2025 15:11:06 -0700 Subject: [PATCH 44/53] =?UTF-8?q?[CP-stable]=F0=9F=90=9B=20Use=20consist?= =?UTF-8?q?=20slashes=20when=20generating=20dep=20files=20(#169630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/163591 ### Changelog Description: Normalizes file paths in every depfile, especially on Windows. It eliminates the inconsistency that can occur when other codes find the file paths are different and produce unexpected results. ### Impact Description: The most noticeable impact so far is that people are unable to build flavored Android packages on Windows repeatedly until the next clean. ### Workaround: Is there a workaround for this issue? The workaround is to manually patch the project's gradle script: https://github.com/flutter/flutter/issues/163591#issuecomment-2887039609 From my experience, the patch is not always working and is hard to maintain. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Follow the *steps to reproduce* section in https://github.com/flutter/flutter/issues/163591#issue-2862606263. --- .../lib/src/build_system/depfile.dart | 20 +++-- .../build_system/depfile_test.dart | 74 ++++++++++++++++--- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/packages/flutter_tools/lib/src/build_system/depfile.dart b/packages/flutter_tools/lib/src/build_system/depfile.dart index a27e986e18850..9d90194c93d1f 100644 --- a/packages/flutter_tools/lib/src/build_system/depfile.dart +++ b/packages/flutter_tools/lib/src/build_system/depfile.dart @@ -65,16 +65,19 @@ class DepfileService { } void _writeFilesToBuffer(List files, StringBuffer buffer) { + final bool backslash = _fileSystem.path.style.separator == r'\'; for (final File outputFile in files) { - if (_fileSystem.path.style.separator == r'\') { - // backslashes and spaces in a depfile have to be escaped if the - // platform separator is a backslash. - final String path = outputFile.path.replaceAll(r'\', r'\\').replaceAll(r' ', r'\ '); - buffer.write(' $path'); + String path = _fileSystem.path.normalize(outputFile.path); + if (backslash) { + // Backslashes in a depfile have to be escaped if the platform separator is a backslash. + path = path.replaceAll(r'\', r'\\'); } else { - final String path = outputFile.path.replaceAll(r' ', r'\ '); - buffer.write(' $path'); + // Convert all path separators to forward slashes. + path = path.replaceAll(r'\', r'/'); } + // Escape spaces. + path = path.replaceAll(r' ', r'\ '); + buffer.write(' $path'); } } @@ -92,7 +95,8 @@ class DepfileService { // The tool doesn't write duplicates to these lists. This call is an attempt to // be resilient to the outputs of other tools which write or user edits to depfiles. .toSet() - .map(_fileSystem.file) + // Normalize the path before creating a file object. + .map((String path) => _fileSystem.file(_fileSystem.path.normalize(path))) .toList(); } } diff --git a/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart b/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart index f0efccaf5e3ef..3855c40141ee8 100644 --- a/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/depfile_test.dart @@ -17,6 +17,7 @@ void main() { fileSystem = MemoryFileSystem.test(); depfileService = DepfileService(logger: BufferLogger.test(), fileSystem: fileSystem); }); + testWithoutContext('Can parse depfile from file', () { final File depfileSource = fileSystem.file('example.d')..writeAsStringSync(''' a.txt: b.txt @@ -48,22 +49,34 @@ a.txt c.txt d.txt: b.txt }); testWithoutContext('Can parse depfile with windows file paths', () { - fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); - depfileService = DepfileService(logger: BufferLogger.test(), fileSystem: fileSystem); + final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + final DepfileService depfileService = DepfileService( + logger: BufferLogger.test(), + fileSystem: fileSystem, + ); final File depfileSource = fileSystem.file('example.d')..writeAsStringSync(r''' -C:\\a.txt: C:\\b.txt +C:\\a1.txt C:\\a2/a3.txt: C:\\b1.txt C:\\b2/b3.txt '''); final Depfile depfile = depfileService.parse(depfileSource); - expect(depfile.inputs.single.path, r'C:\b.txt'); - expect(depfile.outputs.single.path, r'C:\a.txt'); + expect(depfile.inputs.map((File e) => e.path).toList(), [ + r'C:\b1.txt', + r'C:\b2\b3.txt', + ]); + expect(depfile.outputs.map((File e) => e.path).toList(), [ + r'C:\a1.txt', + r'C:\a2\a3.txt', + ]); }); testWithoutContext( 'Can escape depfile with windows file paths and spaces in directory names', () { - fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); - depfileService = DepfileService(logger: BufferLogger.test(), fileSystem: fileSystem); + final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + final DepfileService depfileService = DepfileService( + logger: BufferLogger.test(), + fileSystem: fileSystem, + ); final File inputFile = fileSystem.directory(r'Hello Flutter').childFile('a.txt').absolute ..createSync(recursive: true); @@ -73,8 +86,9 @@ C:\\a.txt: C:\\b.txt final File outputDepfile = fileSystem.file('depfile'); depfileService.writeToFile(depfile, outputDepfile); - expect(outputDepfile.readAsStringSync(), contains(r'C:\\Hello\ Flutter\\a.txt')); - expect(outputDepfile.readAsStringSync(), contains(r'C:\\Hello\ Flutter\\b.txt')); + final String output = outputDepfile.readAsStringSync(); + expect(output, contains(r'C:\\Hello\ Flutter\\a.txt')); + expect(output, contains(r'C:\\Hello\ Flutter\\b.txt')); }, ); @@ -88,8 +102,46 @@ C:\\a.txt: C:\\b.txt final File outputDepfile = fileSystem.file('depfile'); depfileService.writeToFile(depfile, outputDepfile); - expect(outputDepfile.readAsStringSync(), contains(r'/Hello\ Flutter/a.txt')); - expect(outputDepfile.readAsStringSync(), contains(r'/Hello\ Flutter/b.txt')); + final String output = outputDepfile.readAsStringSync(); + expect(output, contains(r'/Hello\ Flutter/a.txt')); + expect(output, contains(r'/Hello\ Flutter/b.txt')); + }); + + testWithoutContext('Can produce normalized paths', () { + final List<(FileSystemStyle style, String input, String output, List expects)> pairs = + <(FileSystemStyle style, String input, String output, List expects)>[ + ( + FileSystemStyle.posix, + r'Hello Flutter\a.txt', + r'Hello Flutter\b.txt', + [r'/Hello\ Flutter/a.txt', r'/Hello\ Flutter/b.txt'], + ), + ( + FileSystemStyle.windows, + r'Hello Flutter/a.txt', + r'Hello Flutter/b.txt', + [r'\\Hello\ Flutter\\a.txt', r'\\Hello\ Flutter\\b.txt'], + ), + ]; + + for (final (FileSystemStyle style, String input, String output, List expects) + in pairs) { + final FileSystem fileSystem = MemoryFileSystem.test(style: style); + final DepfileService depfileService = DepfileService( + logger: BufferLogger.test(), + fileSystem: fileSystem, + ); + final File inputFile = fileSystem.file(input).absolute..createSync(recursive: true); + final File outputFile = fileSystem.file(output).absolute..createSync(); + final Depfile depfile = Depfile([inputFile], [outputFile]); + final File outputDepfile = fileSystem.file('depfile'); + depfileService.writeToFile(depfile, outputDepfile); + + final String outputString = outputDepfile.readAsStringSync(); + for (final String path in expects) { + expect(outputString, contains(path)); + } + } }); testWithoutContext('Resilient to weird whitespace', () { From d88a6751a87a8aa478bb6fa8e310b9a400e77479 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:08:00 -0700 Subject: [PATCH 45/53] [CP-stable] Split `Linux docs_publish` into `Linux docs_generate_release` (#169912) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/168913 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples N/A - This is an internal infrastructure change. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) No user impact, could negatively impact how API docs are published. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? View post-submits when a stable release is published and verify API docs were published. --- .ci.yaml | 101 ++++++++++++++++++++++++++++++++++------------------- TESTOWNERS | 2 +- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index ca1620d888f41..42142d0bccbbf 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -586,18 +586,45 @@ targets: # to GCS", and conditionally on the "master" channel will also deploy the docs # to Firebase (https://main-api.flutter.dev/). # - # See "Linux docs_deploy_stable" for how the docs are deployed to stable API. + # See "Linux docs_generate_release" for how docs are built (but not published) + # and "Linux docs_deploy_stable" for how docs deployed for the stable branch + # (we do not deploy docs for beta). - name: Linux docs_publish recipe: flutter/docs presubmit: false backfill: false - # This means "allow this to be scheduled by the release/release_builder" - # recipe. Normally we'd use "schedule: true", but that *also* means "do not - # run this in normal presubmit/postsubmit", and we do want it to run in - # postsubmit for the "master" channel. Sorry. - # - # See https://github.com/flutter/flutter/issues/168709 for details. - schedule_during_release_override: true + enabled_branches: + - master + timeout: 60 + dimensions: + os: "Linux" + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"}, + {"dependency": "firebase", "version": "v11.0.1"} + ] + tags: > + ["framework", "hostonly", "linux"] + validation: docs + validation_name: Docs + firebase_project: main-docs-flutter-prod + release_ref: refs/heads/master + drone_dimensions: + - os=Linux + + - name: Linux docs_generate_release + recipe: flutter/docs + # TODO(matanlurey): This has no effect, is used to allow creating a builder. + # Remove in the next PR. + # https://github.com/flutter/flutter/issues/168913 + bringup: true + scheduler: release + presubmit: false + postsubmit: false + enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ timeout: 60 dimensions: os: "Linux" @@ -612,11 +639,42 @@ targets: ["framework", "hostonly", "linux"] validation: docs validation_name: Docs + # TODO(matanlurey): Neither of these properties are actually used, since + # the branch name is not "master", but if they are removed, the recipe + # will fail. See https://github.com/flutter/flutter/issues/169108. firebase_project: main-docs-flutter-prod release_ref: refs/heads/master drone_dimensions: - os=Linux + + # This step runs on the release channel "stable", after the same commit SHA + # has been run and built by Linux flutter_release_builder as part of a release + # candidate branch (i.e. /flutter-\d+\.\d+-candidate\.\d+/) in the previous + # target, "Linux docs_generate_release". + - name: Linux docs_deploy_stable + recipe: flutter/docs + scheduler: release + presubmit: false + postsubmit: false + enabled_branches: + - stable + timeout: 60 + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"}, + {"dependency": "firebase", "version": "v11.0.1"} + ] + tags: > + ["framework", "hostonly", "linux"] + validation: docs_deploy + validation_name: Docs_deploy + firebase_project: docs-flutter-dev + drone_dimensions: + - os=Linux + - name: Linux docs_test recipe: flutter/flutter_drone timeout: 90 # https://github.com/flutter/flutter/issues/120901 @@ -6924,30 +6982,3 @@ targets: ["framework", "hostonly", "shard", "windows"] drone_dimensions: - os=Windows - - # This step runs on the release channel "stable", after the same commit SHA - # has been run and built by Linux flutter_release_builder as part of a release - # candidate branch (i.e. /flutter-\d+\.\d+-candidate\.\d+/) in the previous - # target, "Linux docs_publish". - - name: Linux docs_deploy_stable - recipe: flutter/docs - scheduler: release - bringup: true - enabled_branches: - - stable - presubmit: false - timeout: 60 - properties: - cores: "32" - dependencies: >- - [ - {"dependency": "dashing", "version": "0.4.0"}, - {"dependency": "firebase", "version": "v11.0.1"} - ] - tags: > - ["framework", "hostonly", "linux"] - validation: docs_deploy - validation_name: Docs_deploy - firebase_project: docs-flutter-dev - drone_dimensions: - - os=Linux diff --git a/TESTOWNERS b/TESTOWNERS index 95b3c0e856070..2ff8f7a6bf938 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -304,8 +304,8 @@ /dev/devicelab/bin/tasks/android_display_cutout.dart @reidbaker @flutter/android ## Host only framework tests -# Linux docs_deploy_beta # Linux docs_deploy_stable +# Linux docs_generate_release # Linux docs_publish /dev/bots/docs.sh @Piinks @flutter/framework # Linux packages_autoroller From fa17ad1002215a1bff3f462e67f380fa3d887aab Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:16:03 -0700 Subject: [PATCH 46/53] Update `engine.version` for 3.32.2 stable hotfix release (#169952) Updates `engine.version` for 3.32.2 stable hotfix release to the last cherry-pick SHA: https://github.com/flutter/flutter/commit/d88a6751a87a8aa478bb6fa8e310b9a400e77479 I think this is required because there were engine changes in a previous cherry-pick (https://github.com/flutter/flutter/pull/169772) not included in the 3.32.1 stable hotfix release. --- CHANGELOG.md | 6 ++++++ bin/internal/engine.version | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a31bd06675e..3688d8cc9f3d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,12 @@ INTERNAL NOTE ## Flutter 3.32 Changes +### [3.32.2](https://github.com/flutter/flutter/releases/tag/3.32.2) + +- [flutter/169772](https://github.com/flutter/flutter/pull/169772) - Configuration changes for Flutter's CI to run tests on Linux instead of Windows when not otherwise required. +- [flutter/169630](https://github.com/flutter/flutter/pull/169630) - Fixes issue where flavored Android packages may not successfully build on Windows repeatedly until the next clean. +- [flutter/169912](https://github.com/flutter/flutter/pull/169912) - Splits Flutter CI task for publishing API docs into one build step and one deploy step. + ### [3.32.1](https://github.com/flutter/flutter/releases/tag/3.32.1) - [flutter/156793](https://github.com/flutter/flutter/issues/156793) - Fixes flaky crash when targeting web applications via IDEs using the DAP. diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 722ab7cd4271b..03cd86b1ee801 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -1425e5e9ec5eeb4f225c401d8db69b860e0fde9a +d88a6751a87a8aa478bb6fa8e310b9a400e77479 From 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Wed, 4 Jun 2025 11:02:51 -0700 Subject: [PATCH 47/53] Re-update `engine.version` for 3.32.2 stable hotfix release to the last SHA with engine change (#170005) Corrects `engine.version` for 3.32.2 stable hotfix release to the last cherry-pick SHA with an engine change: https://github.com/flutter/flutter/commit/109150893958777c8f2215f6cfd3e89e984e8dea --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 03cd86b1ee801..7abd7d2413e45 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -d88a6751a87a8aa478bb6fa8e310b9a400e77479 +109150893958777c8f2215f6cfd3e89e984e8dea From 46a4c05aceaa636a07c5b1d89175ce51169e7778 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:39:08 -0700 Subject: [PATCH 48/53] [CP-stable]Revert "Fix NavigationBar indicator overlay color (#164484)" (#170052) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/169249 https://github.com/flutter/flutter/issues/169436 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples NavigationBar active indicator animation gets stucked. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Visual glitch visible by users. In several scenarios, the active indicator for NavigationBar and NavigationDrawer is not properly painted (animation stucked half way or active color not reflecting the current state). ### Workaround: Is there a workaround for this issue? No ### Risk: What is the risk level of this cherry-pick? - [ x ] Low ### Test Coverage: Are you confident that your fix is well-tested by automated tests? - [ x ] Yes ### Validation Steps: What are the steps to validate that this fix works? Run the code sample from https://github.com/flutter/flutter/issues/169249. This fix is a revert to a PR which landed in the current stable. --- .../lib/src/material/navigation_bar.dart | 3 +- .../test/material/navigation_bar_test.dart | 4 +- .../material/navigation_bar_theme_test.dart | 4 +- .../test/material/navigation_drawer_test.dart | 10 ++-- .../navigation_drawer_theme_test.dart | 4 +- .../test/material/navigation_rail_test.dart | 58 +++++++++++++------ .../material/navigation_rail_theme_test.dart | 4 +- 7 files changed, 52 insertions(+), 35 deletions(-) diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 83217782071fd..9465e7ef96f37 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -16,7 +16,6 @@ import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'elevation_overlay.dart'; -import 'ink_decoration.dart'; import 'ink_well.dart'; import 'material.dart'; import 'material_localizations.dart'; @@ -869,7 +868,7 @@ class NavigationIndicator extends StatelessWidget { builder: (BuildContext context, Animation fadeAnimation) { return FadeTransition( opacity: fadeAnimation, - child: Ink( + child: Container( width: width, height: height, decoration: ShapeDecoration( diff --git a/packages/flutter/test/material/navigation_bar_test.dart b/packages/flutter/test/material/navigation_bar_test.dart index f7d16d5f56fd4..121c1ad8b0c68 100644 --- a/packages/flutter/test/material/navigation_bar_test.dart +++ b/packages/flutter/test/material/navigation_bar_test.dart @@ -1716,8 +1716,8 @@ Material _getMaterial(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_bar_theme_test.dart b/packages/flutter/test/material/navigation_bar_theme_test.dart index e47a3ff752f53..9784a929aa071 100644 --- a/packages/flutter/test/material/navigation_bar_theme_test.dart +++ b/packages/flutter/test/material/navigation_bar_theme_test.dart @@ -367,8 +367,8 @@ Material _barMaterial(WidgetTester tester) { ShapeDecoration? _indicator(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_drawer_test.dart b/packages/flutter/test/material/navigation_drawer_test.dart index fde25e676a604..78b67436a51f5 100644 --- a/packages/flutter/test/material/navigation_drawer_test.dart +++ b/packages/flutter/test/material/navigation_drawer_test.dart @@ -123,16 +123,14 @@ void main() { await tester.tap(find.text('AC')); await tester.pump(); - // When no background color is set, only the non-visible indicator Ink is expected. - expect(findDestinationInk('AC'), findsOne); + expect(findDestinationInk('AC'), findsNothing); // Destination with a custom background color. await tester.tap(find.byIcon(Icons.access_alarm)); await tester.pump(); // A Material is added with the custom color. - expect(findDestinationInk('Alarm'), findsNWidgets(2)); - // The drawer destination Ink is the first one, the second is the indicator's one. + expect(findDestinationInk('Alarm'), findsOne); final BoxDecoration destinationDecoration = tester.firstWidget(findDestinationInk('Alarm')).decoration! as BoxDecoration; expect(destinationDecoration.color, color); @@ -511,8 +509,8 @@ InkWell? _getInkWell(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_drawer_theme_test.dart b/packages/flutter/test/material/navigation_drawer_theme_test.dart index 4a30bc8cf3b76..9ad94a8e8f3f3 100644 --- a/packages/flutter/test/material/navigation_drawer_theme_test.dart +++ b/packages/flutter/test/material/navigation_drawer_theme_test.dart @@ -285,8 +285,8 @@ Material _getMaterial(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_rail_test.dart b/packages/flutter/test/material/navigation_rail_test.dart index 5e0e10631a138..b60893aa3ac82 100644 --- a/packages/flutter/test/material/navigation_rail_test.dart +++ b/packages/flutter/test/material/navigation_rail_test.dart @@ -3076,7 +3076,6 @@ void main() { final RenderObject inkFeatures = tester.allRenderObjects.firstWhere( (RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures', ); - const Rect indicatorRect = Rect.fromLTRB(12.0, 0.0, 68.0, 32.0); const Rect includedRect = indicatorRect; final Rect excludedRect = includedRect.inflate(10); @@ -3102,7 +3101,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(12.0, 0.0, 68.0, 32.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(12.0, 72.0, 68.0, 104.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3164,7 +3163,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(12.0, 6.0, 68.0, 38.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(12.0, 58.0, 68.0, 90.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3230,7 +3229,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(30.0, 24.0, 86.0, 56.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(30.0, 96.0, 86.0, 128.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3295,7 +3294,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(0.0, 6.0, 50.0, 38.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(0.0, 58.0, 50.0, 90.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3362,7 +3361,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(140.0, 24.0, 196.0, 56.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(140.0, 96.0, 196.0, 128.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3402,11 +3401,13 @@ void main() { ); // Default values from M3 specification. - const double railMinWidth = 80.0; const double indicatorHeight = 32.0; const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 + const double verticalSpacer = 8.0; + const double verticalIconLabelSpacing = 4.0; + const double verticalDestinationSpacing = 12.0; // The navigation rail width is larger than default because of the first destination long label. final double railWidth = tester.getSize(find.byType(NavigationRail)).width; @@ -3417,7 +3418,13 @@ void main() { final Rect indicatorRect = Rect.fromLTRB(indicatorLeft, 0.0, indicatorRight, indicatorHeight); final Rect includedRect = indicatorRect; final Rect excludedRect = includedRect.inflate(10); - const double indicatorHorizontalPadding = (railMinWidth - indicatorWidth) / 2; // 12.0 + + // Compute the vertical position for the selected destination (the one with 'bookmark' icon). + const double labelHeight = 16; // fontSize is 12 and height is 1.3. + const double destinationHeight = + indicatorHeight + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing; + const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; + const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset; expect( inkFeatures, @@ -3441,10 +3448,10 @@ void main() { ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( rrect: RRect.fromLTRBR( - indicatorHorizontalPadding, - 0.0, - indicatorHorizontalPadding + indicatorWidth, - indicatorHeight, + indicatorLeft, + secondIndicatorVerticalOffset, + indicatorRight, + secondIndicatorVerticalOffset + indicatorHeight, const Radius.circular(16), ), color: const Color(0xffe8def8), @@ -3497,6 +3504,9 @@ void main() { const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 + const double verticalSpacer = 8.0; + const double verticalIconLabelSpacing = 4.0; + const double verticalDestinationSpacing = 12.0; // The navigation rail width is the default one because labels are short. final double railWidth = tester.getSize(find.byType(NavigationRail)).width; @@ -3516,8 +3526,13 @@ void main() { final Rect includedRect = indicatorRect; final Rect excludedRect = includedRect.inflate(10); - // Icon height is greater than indicator height so the indicator has a vertical offset. - const double secondIndicatorVerticalOffset = (iconSize - indicatorHeight) / 2; + // Compute the vertical position for the selected destination (the one with 'bookmark' icon). + const double labelHeight = 16; // fontSize is 12 and height is 1.3. + const double destinationHeight = + iconSize + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing; + const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; + const double indicatorOffset = (iconSize - indicatorHeight) / 2; + const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset + indicatorOffset; expect( inkFeatures, @@ -3596,6 +3611,7 @@ void main() { const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 + const double verticalSpacer = 8.0; const double verticalDestinationSpacingM3 = 12.0; // The navigation rail width is the default one because labels are short. @@ -3615,7 +3631,11 @@ void main() { final Rect excludedRect = includedRect.inflate(10); // Compute the vertical position for the selected destination (the one with 'bookmark' icon). - const double secondIndicatorVerticalOffset = verticalDestinationSpacingM3 / 2; + const double destinationHeight = indicatorHeight + verticalDestinationSpacingM3; + const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; + const double secondIndicatorVerticalOffset = + secondDestinationVerticalOffset + verticalDestinationSpacingM3 / 2; + const double secondDestinationHorizontalOffset = 800 - railMinExtendedWidth; // RTL. expect( inkFeatures, @@ -3641,9 +3661,9 @@ void main() { // Indicator for the selected destination (the one with 'bookmark' icon). ..rrect( rrect: RRect.fromLTRBR( - indicatorLeft, + secondDestinationHorizontalOffset + indicatorLeft, secondIndicatorVerticalOffset, - indicatorRight, + secondDestinationHorizontalOffset + indicatorRight, secondIndicatorVerticalOffset + indicatorHeight, const Radius.circular(16), ), @@ -6150,8 +6170,8 @@ Widget _buildWidget(Widget child, {bool useMaterial3 = true, bool isRTL = false} ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_rail_theme_test.dart b/packages/flutter/test/material/navigation_rail_theme_test.dart index 07236c9e63347..49581b8ba90b3 100644 --- a/packages/flutter/test/material/navigation_rail_theme_test.dart +++ b/packages/flutter/test/material/navigation_rail_theme_test.dart @@ -327,8 +327,8 @@ Material _railMaterial(WidgetTester tester) { ShapeDecoration? _indicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Ink)), + .firstWidget( + find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Container)), ) .decoration as ShapeDecoration?; From 31c4875c7aa0079ae11a4850e2d06bd83d001960 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:39:10 -0700 Subject: [PATCH 49/53] [CP-stable][Impeller] Maintain a global map of each context's currently active thread-local command pools (#170013) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/169208 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixes a memory leak in the Impeller Vulkan back end. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) The memory usage of apps using Impeller/Vulkan will increase as frames are rendered. Memory consumption will grow until the Android activity enters the stopped state. ### Workaround: Is there a workaround for this issue? Disabling Impeller ### Risk: What is the risk level of this cherry-pick? - [ x ] Medium ### Test Coverage: Are you confident that your fix is well-tested by automated tests? - [ x ] Yes ### Validation Steps: What are the steps to validate that this fix works? Start an app using Impeller/Vulkan that renders frames nonstop (for example, video playback). Leave it running for several minutes. Check memory metrics with a tool like `adb shell dumpsys meminfo` and verify that memory usage is stable. --- .../golden_playground_test_mac.cc | 8 ++++ .../backend/vulkan/command_pool_vk.cc | 47 ++++++++++++------- .../renderer/backend/vulkan/command_pool_vk.h | 17 ++++--- .../vulkan/command_pool_vk_unittests.cc | 19 ++++++++ .../renderer/backend/vulkan/context_vk.cc | 6 ++- 5 files changed, 70 insertions(+), 27 deletions(-) diff --git a/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc b/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc index 12b5103283688..fd8445dbe2811 100644 --- a/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc +++ b/engine/src/flutter/impeller/golden_tests/golden_playground_test_mac.cc @@ -132,6 +132,11 @@ void GoldenPlaygroundTest::SetTypographerContext( void GoldenPlaygroundTest::TearDown() { ASSERT_FALSE(dlopen("/usr/local/lib/libMoltenVK.dylib", RTLD_NOLOAD)); + + auto context = GetContext(); + if (context) { + context->DisposeThreadLocalCachedResources(); + } } namespace { @@ -274,6 +279,9 @@ RuntimeStage::Map GoldenPlaygroundTest::OpenAssetAsRuntimeStage( } std::shared_ptr GoldenPlaygroundTest::GetContext() const { + if (!pimpl_->screenshotter) { + return nullptr; + } return pimpl_->screenshotter->GetPlayground().GetContext(); } diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc index d6176b2c3b808..dfa80c252b486 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc @@ -171,10 +171,23 @@ static thread_local std::unique_ptr tls_command_pool_map; // with that context. static Mutex g_all_pools_map_mutex; static std::unordered_map< - const ContextVK*, - std::vector>> g_all_pools_map + uint64_t, + std::unordered_map>> g_all_pools_map IPLR_GUARDED_BY(g_all_pools_map_mutex); +CommandPoolRecyclerVK::CommandPoolRecyclerVK( + const std::shared_ptr& context) + : context_(context), context_hash_(context->GetHash()) {} + +// Visible for testing. +// Returns the number of pools in g_all_pools_map for the given context. +int CommandPoolRecyclerVK::GetGlobalPoolCount(const ContextVK& context) { + Lock all_pools_lock(g_all_pools_map_mutex); + auto it = g_all_pools_map.find(context.GetHash()); + return it != g_all_pools_map.end() ? it->second.size() : 0; +} + // TODO(matanlurey): Return a status_or<> instead of nullptr when we have one. std::shared_ptr CommandPoolRecyclerVK::Get() { auto const strong_context = context_.lock(); @@ -187,8 +200,7 @@ std::shared_ptr CommandPoolRecyclerVK::Get() { tls_command_pool_map.reset(new CommandPoolMap()); } CommandPoolMap& pool_map = *tls_command_pool_map.get(); - auto const hash = strong_context->GetHash(); - auto const it = pool_map.find(hash); + auto const it = pool_map.find(context_hash_); if (it != pool_map.end()) { return it->second; } @@ -201,11 +213,11 @@ std::shared_ptr CommandPoolRecyclerVK::Get() { auto const resource = std::make_shared( std::move(data->pool), std::move(data->buffers), context_); - pool_map.emplace(hash, resource); + pool_map.emplace(context_hash_, resource); { Lock all_pools_lock(g_all_pools_map_mutex); - g_all_pools_map[strong_context.get()].push_back(resource); + g_all_pools_map[context_hash_][std::this_thread::get_id()] = resource; } return resource; @@ -275,30 +287,33 @@ void CommandPoolRecyclerVK::Reclaim( RecycledData{.pool = std::move(pool), .buffers = std::move(buffers)}); } -CommandPoolRecyclerVK::~CommandPoolRecyclerVK() { - // Ensure all recycled pools are reclaimed before this is destroyed. - Dispose(); -} - void CommandPoolRecyclerVK::Dispose() { CommandPoolMap* pool_map = tls_command_pool_map.get(); if (pool_map) { - pool_map->clear(); + pool_map->erase(context_hash_); + } + + { + Lock all_pools_lock(g_all_pools_map_mutex); + auto found = g_all_pools_map.find(context_hash_); + if (found != g_all_pools_map.end()) { + found->second.erase(std::this_thread::get_id()); + } } } -void CommandPoolRecyclerVK::DestroyThreadLocalPools(const ContextVK* context) { +void CommandPoolRecyclerVK::DestroyThreadLocalPools() { // Delete the context's entry in this thread's command pool map. if (tls_command_pool_map.get()) { - tls_command_pool_map.get()->erase(context->GetHash()); + tls_command_pool_map.get()->erase(context_hash_); } // Destroy all other thread-local CommandPoolVK instances associated with // this context. Lock all_pools_lock(g_all_pools_map_mutex); - auto found = g_all_pools_map.find(context); + auto found = g_all_pools_map.find(context_hash_); if (found != g_all_pools_map.end()) { - for (auto& weak_pool : found->second) { + for (auto& [thread_id, weak_pool] : found->second) { auto pool = weak_pool.lock(); if (!pool) { continue; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.h index 30f2b3c3688ae..1fc9d06b32fb4 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.h @@ -103,8 +103,6 @@ class CommandPoolVK final { class CommandPoolRecyclerVK final : public std::enable_shared_from_this { public: - ~CommandPoolRecyclerVK(); - /// A unique command pool and zero or more recycled command buffers. struct RecycledData { vk::UniqueCommandPool pool; @@ -112,16 +110,13 @@ class CommandPoolRecyclerVK final }; /// @brief Clean up resources held by all per-thread command pools - /// associated with the given context. - /// - /// @param[in] context The context. - static void DestroyThreadLocalPools(const ContextVK* context); + /// associated with the context. + void DestroyThreadLocalPools(); /// @brief Creates a recycler for the given |ContextVK|. /// /// @param[in] context The context to create the recycler for. - explicit CommandPoolRecyclerVK(std::weak_ptr context) - : context_(std::move(context)) {} + explicit CommandPoolRecyclerVK(const std::shared_ptr& context); /// @brief Gets a command pool for the current thread. /// @@ -137,11 +132,15 @@ class CommandPoolRecyclerVK final std::vector&& buffers, bool should_trim = false); - /// @brief Clears all recycled command pools to let them be reclaimed. + /// @brief Clears this context's thread-local command pool. void Dispose(); + // Visible for testing. + static int GetGlobalPoolCount(const ContextVK& context); + private: std::weak_ptr context_; + uint64_t context_hash_; Mutex recycled_mutex_; std::vector recycled_ IPLR_GUARDED_BY(recycled_mutex_); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc index 42da1b896e50f..69b4e06c4ea6c 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc @@ -228,5 +228,24 @@ TEST(CommandPoolRecyclerVKTest, ExtraCommandBufferAllocationsTriggerTrim) { context->Shutdown(); } +TEST(CommandPoolRecyclerVKTest, RecyclerGlobalPoolMapSize) { + auto context = MockVulkanContextBuilder().Build(); + auto const recycler = context->GetCommandPoolRecycler(); + + // The global pool list for this context should initially be empty. + EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0); + + // Creating a pool for this thread should insert the pool into the global map. + auto pool = recycler->Get(); + EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 1); + + // Disposing this thread's pool should remove it from the global map. + pool.reset(); + recycler->Dispose(); + EXPECT_EQ(CommandPoolRecyclerVK::GetGlobalPoolCount(*context), 0); + + context->Shutdown(); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc index 6066065ca2b34..a9a42ad35a560 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc @@ -134,7 +134,9 @@ ContextVK::~ContextVK() { if (device_holder_ && device_holder_->device) { [[maybe_unused]] auto result = device_holder_->device->waitIdle(); } - CommandPoolRecyclerVK::DestroyThreadLocalPools(this); + if (command_pool_recycler_) { + command_pool_recycler_->DestroyThreadLocalPools(); + } } Context::BackendType ContextVK::GetBackendType() const { @@ -421,7 +423,7 @@ void ContextVK::Setup(Settings settings) { } auto command_pool_recycler = - std::make_shared(weak_from_this()); + std::make_shared(shared_from_this()); if (!command_pool_recycler) { VALIDATION_LOG << "Could not create command pool recycler."; return; From b02643b7d92908cd87315ba43a1245307e7d9cb8 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:53:20 -0700 Subject: [PATCH 50/53] [CP-stable]fix: add the missing type of debug metadata (#170003) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? https://github.com/flutter/flutter/issues/169252 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples Fixes a build failure on Android for app bundles when setting debug symbol level to `FULL` and using release mode. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) Flutter fails to build an AAB in release mode. ### Workaround: Is there a workaround for this issue? No, besides not using this symbol level. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? This linked issue has easy to follow repro steps for the original issue, which also function as validation steps that the issue is fixed. --- .../flutter_tools/lib/src/android/gradle.dart | 7 +- .../android/android_gradle_builder_test.dart | 75 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index e0c644fd0cd22..c3baf95b8bbf1 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -698,14 +698,15 @@ class AndroidGradleBuilder implements AndroidBuilder { return false; } - // As long as libflutter.so.sym is present for at least one architecture, + // As long as libflutter.so.sym or libflutter.so.dbg is present for at least one architecture, // assume AGP succeeded in stripping. - if (result.stdout.contains('libflutter.so.sym')) { + if (result.stdout.contains('libflutter.so.sym') || + result.stdout.contains('libflutter.so.dbg')) { return true; } _logger.printTrace( - 'libflutter.so.sym not present when checking final appbundle for debug symbols.', + 'libflutter.so.sym or libflutter.so.dbg not present when checking final appbundle for debug symbols.', ); return false; } diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index e20c3d6e067fc..996fe8372c981 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -851,6 +851,16 @@ void main() { /BUNDLE-METADATA/com.android.tools.build.debugsymbols/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/ /BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libflutter.so.sym +'''; + + // Output from `/tools/bin/apkanalyzer files list ` + // on an aab containing the debug info and symbol tables. + const String apkanalyzerOutputWithDebugInfoAndSymFiles = + apkanalyzerOutputWithoutSymFiles + + r''' +/BUNDLE-METADATA/com.android.tools.build.debugsymbols/ +/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/ +/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libflutter.so.dbg '''; void createSharedGradleFiles() { @@ -942,6 +952,71 @@ void main() { overrides: {AndroidStudio: () => FakeAndroidStudio()}, ); + testUsingContext( + 'build succeeds when debug info and symbol tables present for at least one architecture', + () async { + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + analytics: fakeAnalytics, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(environment: {'HOME': '/home'}), + androidStudio: FakeAndroidStudio(), + ); + processManager.addCommand( + FakeCommand(command: List.of(commonCommandPortion)..add('bundleRelease')), + ); + + createSharedGradleFiles(); + final File aabFile = createAabFile(BuildMode.release); + final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!; + + processManager.addCommand( + FakeCommand( + command: [ + sdk.getCmdlineToolsPath(apkAnalyzerBinaryName)!, + 'files', + 'list', + aabFile.path, + ], + stdout: apkanalyzerOutputWithDebugInfoAndSymFiles, + ), + ); + + final FlutterProject project = FlutterProject.fromDirectoryTest( + fileSystem.currentDirectory, + ); + project.android.appManifestFile + ..createSync(recursive: true) + ..writeAsStringSync(minimalV2EmbeddingManifest); + + await builder.buildGradleApp( + project: project, + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + null, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + targetArchs: [ + AndroidArch.arm64_v8a, + AndroidArch.armeabi_v7a, + AndroidArch.x86_64, + ], + ), + target: 'lib/main.dart', + isBuildingBundle: true, + configOnly: false, + localGradleErrors: [], + ); + }, + overrides: {AndroidStudio: () => FakeAndroidStudio()}, + ); + testUsingContext( 'building a debug aab does not invoke apkanalyzer', () async { From 5c1433509f997e9059d230942bdf83f1b88a179e Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 11 Jun 2025 13:12:24 -0700 Subject: [PATCH 51/53] Prepare to publish 3.32.3, update changelog+engine.version. (#170470) --- CHANGELOG.md | 6 ++++++ bin/internal/engine.version | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3688d8cc9f3d6..8592ef1ea07fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,12 @@ INTERNAL NOTE ## Flutter 3.32 Changes +### [3.32.3](https://github.com/flutter/flutter/releases/tag/3.32.3) + +- [flutter/170052](https://github.com/flutter/flutter/pull/170052) - Fixes "active" indicator for `NavigationBar` and `NavigationDrawer` +- [flutter/170013](https://github.com/flutter/flutter/pull/170013) - Fixes a memory leak in the Impeller Vulkan back end. +- [flutter/169912](https://github.com/flutter/flutter/pull/170003) - Fixes failures to build an Android AAB in release mode. + ### [3.32.2](https://github.com/flutter/flutter/releases/tag/3.32.2) - [flutter/169772](https://github.com/flutter/flutter/pull/169772) - Configuration changes for Flutter's CI to run tests on Linux instead of Windows when not otherwise required. diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 7abd7d2413e45..a58857098d274 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -109150893958777c8f2215f6cfd3e89e984e8dea +31c4875c7aa0079ae11a4850e2d06bd83d001960 From 8cd19e509d6bece8ccd74aef027c4ca947363095 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 12 Jun 2025 16:30:12 -0700 Subject: [PATCH 52/53] Trigger a new engine build from HEAD in `3.32` (#170558) Workaround for https://github.com/flutter/flutter/issues/170536. --- engine/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/README.md b/engine/README.md index c01e1d6c440d5..f6c13417c869e 100644 --- a/engine/README.md +++ b/engine/README.md @@ -1,5 +1,7 @@ # Flutter Engine + + ## Setting up the Engine development environment See [here](https://github.com/flutter/flutter/blob/master/engine/src/flutter/docs/contributing/Setting-up-the-Engine-development-environment.md#getting-the-source) From 6fba2447e95c451518584c35e25f5433f14d888c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 12 Jun 2025 19:03:56 -0700 Subject: [PATCH 53/53] Update `engine.version` (#170560) Workaround for https://github.com/flutter/flutter/issues/170536. --- CHANGELOG.md | 4 ++++ bin/internal/engine.version | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8592ef1ea07fc..86d0eaecaa2fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ INTERNAL NOTE ## Flutter 3.32 Changes +### [3.32.4](https://github.com/flutter/flutter/releases/tag/3.32.4) + +- [flutter/170536](https://github.com/flutter/flutter/issues/170536) - Fixes a code-signing issue on Mac hosts when running `dart` tooling. + ### [3.32.3](https://github.com/flutter/flutter/releases/tag/3.32.3) - [flutter/170052](https://github.com/flutter/flutter/pull/170052) - Fixes "active" indicator for `NavigationBar` and `NavigationDrawer` diff --git a/bin/internal/engine.version b/bin/internal/engine.version index a58857098d274..05cb68b5e3663 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -31c4875c7aa0079ae11a4850e2d06bd83d001960 +8cd19e509d6bece8ccd74aef027c4ca947363095