diff --git a/DEPS b/DEPS index 26f76ef350e77..cf799245e4197 100644 --- a/DEPS +++ b/DEPS @@ -62,7 +62,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': 'bb65648e20e29abc47acf3dd984518d29fd625c3', + 'dart_revision': 'b0e7f171c7c9ab82944ffd6918b80a0df36e0549', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py diff --git a/ci/licenses_golden/licenses_dart b/ci/licenses_golden/licenses_dart index 7506325a7aff7..ece337df138dc 100644 --- a/ci/licenses_golden/licenses_dart +++ b/ci/licenses_golden/licenses_dart @@ -1,4 +1,4 @@ -Signature: 436d820942d64c56e10183515883c9b6 +Signature: 4f1220293023cdec5a6af5bd69bcb0ab ==================================================================================================== LIBRARY: dart diff --git a/common/settings.h b/common/settings.h index a62f1b44a82cb..0145f705b6aa1 100644 --- a/common/settings.h +++ b/common/settings.h @@ -229,6 +229,9 @@ struct Settings { bool enable_impeller = false; #endif + // Log a warning during shell initialization if Impeller is not enabled. + bool warn_on_impeller_opt_out = false; + // The selected Android rendering API. AndroidRenderingAPI android_rendering_api = AndroidRenderingAPI::kSkiaOpenGLES; diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index 665a6248217cb..afbf01da90f83 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -187,6 +187,9 @@ std::vector Tessellator::TessellateConvex(const Path& path, [this](Path::Polyline::PointBufferPtr point_buffer) { point_buffer_ = std::move(point_buffer); }); + if (polyline.points->size() == 0) { + return output; + } output.reserve(polyline.points->size() + (4 * (polyline.contours.size() - 1))); diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc index 772a794b85f87..fcc2849571571 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -480,6 +480,19 @@ TEST(TessellatorTest, FilledRoundRectTessellationVertices) { Rect::MakeXYWH(5000, 10000, 2000, 3000), {50, 70}); } +TEST(TessellatorTest, EarlyReturnEmptyConvexShape) { + // This path is not technically empty (it has a size in one dimension), + // but is otherwise completely flat. + auto tessellator = std::make_shared(); + PathBuilder builder; + builder.MoveTo({0, 0}); + builder.MoveTo({10, 10}, /*relative=*/true); + + auto points = tessellator->TessellateConvex(builder.TakePath(), 3.0); + + EXPECT_TRUE(points.empty()); +} + #if !NDEBUG TEST(TessellatorTest, ChecksConcurrentPolylineUsage) { auto tessellator = std::make_shared(); diff --git a/impeller/toolkit/android/BUILD.gn b/impeller/toolkit/android/BUILD.gn index e0f83d652b573..758a5a9e9911f 100644 --- a/impeller/toolkit/android/BUILD.gn +++ b/impeller/toolkit/android/BUILD.gn @@ -50,6 +50,8 @@ source_set("unittests_lib") { ":unittests_fixtures", "//flutter/testing", ] + + defines = [ "TESTING" ] } executable("unittests") { diff --git a/impeller/toolkit/android/hardware_buffer.cc b/impeller/toolkit/android/hardware_buffer.cc index b3e6e5afc485d..a9ec935e95900 100644 --- a/impeller/toolkit/android/hardware_buffer.cc +++ b/impeller/toolkit/android/hardware_buffer.cc @@ -108,7 +108,7 @@ std::optional HardwareBuffer::GetSystemUniqueID() const { std::optional HardwareBuffer::GetSystemUniqueID( AHardwareBuffer* buffer) { if (!GetProcTable().AHardwareBuffer_getId) { - return false; + return std::nullopt; } uint64_t out_id = 0u; if (GetProcTable().AHardwareBuffer_getId(buffer, &out_id) != 0) { diff --git a/impeller/toolkit/android/proc_table.cc b/impeller/toolkit/android/proc_table.cc index 71e6ce5c4d14d..24f9f99008a3d 100644 --- a/impeller/toolkit/android/proc_table.cc +++ b/impeller/toolkit/android/proc_table.cc @@ -14,6 +14,11 @@ const ProcTable& GetProcTable() { return gProcTable; } +// Only used by tests. +ProcTable& GetMutableProcTable() { + return const_cast(GetProcTable()); +} + template void ResolveAndroidProc( AndroidProc& proc, diff --git a/impeller/toolkit/android/proc_table.h b/impeller/toolkit/android/proc_table.h index 83b38a1293c79..af398fd396bf9 100644 --- a/impeller/toolkit/android/proc_table.h +++ b/impeller/toolkit/android/proc_table.h @@ -124,6 +124,10 @@ struct ProcTable { const ProcTable& GetProcTable(); +#ifdef TESTING +ProcTable& GetMutableProcTable(); +#endif + } // namespace impeller::android #endif // FLUTTER_IMPELLER_TOOLKIT_ANDROID_PROC_TABLE_H_ diff --git a/impeller/toolkit/android/toolkit_android_unittests.cc b/impeller/toolkit/android/toolkit_android_unittests.cc index 4d26b0bfb2a1e..c0086d4d264eb 100644 --- a/impeller/toolkit/android/toolkit_android_unittests.cc +++ b/impeller/toolkit/android/toolkit_android_unittests.cc @@ -12,6 +12,18 @@ namespace impeller::android::testing { +#define DISABLE_ANDROID_PROC(name) \ + struct Disable##name { \ + Disable##name() { \ + real_proc = GetMutableProcTable().name.proc; \ + GetMutableProcTable().name.proc = nullptr; \ + } \ + ~Disable##name() { \ + GetMutableProcTable().name.proc = real_proc; \ + } \ + decltype(name)* real_proc; \ + } disable##name; + TEST(ToolkitAndroidTest, CanCreateProcTable) { ProcTable proc_table; ASSERT_TRUE(proc_table.IsValid()); @@ -49,6 +61,11 @@ TEST(ToolkitAndroidTest, CanGetHardwareBufferIDs) { ASSERT_TRUE(buffer.GetSystemUniqueID().has_value()); } +TEST(ToolkitAndroidTest, HardwareBufferNullIDIfAPIUnavailable) { + DISABLE_ANDROID_PROC(AHardwareBuffer_getId); + ASSERT_FALSE(HardwareBuffer::GetSystemUniqueID(nullptr).has_value()); +} + TEST(ToolkitAndroidTest, CanDescribeHardwareBufferHandles) { if (!HardwareBuffer::IsAvailableOnPlatform()) { GTEST_SKIP() << "Hardware buffers are not supported on this platform."; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 0228f2744f72d..e53ca5f3e3ad0 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -450,6 +450,14 @@ Shell::Shell(DartVMRef vm, weak_factory_(this) { FML_CHECK(!settings.enable_software_rendering || !settings.enable_impeller) << "Software rendering is incompatible with Impeller."; + if (!settings.enable_impeller && settings.warn_on_impeller_opt_out) { + FML_LOG(IMPORTANT) + << "[Action Required] The application opted out of Impeller by either " + "using the --no-enable-impeller flag or FLTEnableImpeller=false " + "plist flag. This option is going to go away in an upcoming Flutter " + "release. Remove the explicit opt-out. If you need to opt-out, " + "report a bug describing the issue."; + } FML_CHECK(vm_) << "Must have access to VM to create a shell."; FML_DCHECK(task_runners_.IsValid()); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index c145f2b8b6da7..70dfdb4da95b2 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include #define FML_USED_ON_EMBEDDER #include @@ -10,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -4689,6 +4689,28 @@ TEST_F(ShellTest, RuntimeStageBackendWithImpeller) { } #endif // IMPELLER_SUPPORTS_RENDERING +TEST_F(ShellTest, WillLogWarningWhenImpellerIsOptedOut) { +#if !IMPELLER_SUPPORTS_RENDERING + GTEST_SKIP() << "This platform doesn't support Impeller."; +#endif + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + settings.enable_impeller = false; + settings.warn_on_impeller_opt_out = true; + // Log captures are thread specific. Just put the shell in single threaded + // configuration. + const auto& runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + TaskRunners task_runners("test", runner, runner, runner, runner); + std::ostringstream stream; + fml::LogMessage::CaptureNextLog(&stream); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(stream.str().find( + "[Action Required] The application opted out of Impeller") != + std::string::npos); + ASSERT_TRUE(shell); + DestroyShell(std::move(shell), task_runners); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 2f1156704b982..1f6118bfe56c4 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -583,6 +583,7 @@ void onPostResume() { ensureAlive(); if (flutterEngine != null) { updateSystemUiOverlays(); + flutterEngine.getPlatformViewsController().onResume(); } else { Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity."); } @@ -1020,6 +1021,7 @@ void onTrimMemory(int level) { flutterEngine.getSystemChannel().sendMemoryPressureWarning(); } flutterEngine.getRenderer().onTrimMemory(level); + flutterEngine.getPlatformViewsController().onTrimMemory(level); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 5e800e80f327a..b540c20e14caf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -4,15 +4,12 @@ package io.flutter.embedding.engine.loader; -import static io.flutter.Build.API_LEVELS; - import android.app.ActivityManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.hardware.display.DisplayManager; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -237,13 +234,6 @@ public InitResult call() { } } - private static boolean areValidationLayersOnByDefault() { - if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= API_LEVELS.API_26) { - return Build.SUPPORTED_ABIS[0].equals("arm64-v8a"); - } - return false; - } - /** * Blocks until initialization of the native system has completed. * @@ -353,8 +343,7 @@ public void ensureInitializationComplete( if (metaData.getBoolean(ENABLE_IMPELLER_META_DATA_KEY, false)) { shellArgs.add("--enable-impeller"); } - if (metaData.getBoolean( - ENABLE_VULKAN_VALIDATION_META_DATA_KEY, areValidationLayersOnByDefault())) { + if (metaData.getBoolean(ENABLE_VULKAN_VALIDATION_META_DATA_KEY, false)) { shellArgs.add("--enable-vulkan-validation"); } if (metaData.getBoolean(IMPELLER_OPENGL_GPU_TRACING_DATA_KEY, false)) { diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 3f920cf126b34..ee1f633275b46 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -7,6 +7,7 @@ import static io.flutter.Build.API_LEVELS; import android.annotation.TargetApi; +import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.Rect; @@ -17,6 +18,7 @@ import android.media.ImageReader; import android.os.Build; import android.os.Handler; +import android.os.Looper; import android.view.Surface; import androidx.annotation.Keep; import androidx.annotation.NonNull; @@ -28,11 +30,11 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -414,12 +416,18 @@ final class ImageReaderSurfaceProducer // Flip when debugging to see verbose logs. private static final boolean VERBOSE_LOGS = false; + // We must always cleanup on memory pressure on Android 14 due to a bug in Android. + // It is safe to do on all versions so we unconditionally have this set to true. + private static final boolean CLEANUP_ON_MEMORY_PRESSURE = true; + private final long id; private boolean released; // Will be true in tests and on Android API < 33. private boolean ignoringFence = false; + private boolean trimOnMemoryPressure = CLEANUP_ON_MEMORY_PRESSURE; + // The requested width and height are updated by setSize. private int requestedWidth = 1; private int requestedHeight = 1; @@ -433,10 +441,11 @@ final class ImageReaderSurfaceProducer private long lastDequeueTime = 0; private long lastQueueTime = 0; private long lastScheduleTime = 0; + private int numTrims = 0; private Object lock = new Object(); // REQUIRED: The following fields must only be accessed when lock is held. - private final LinkedList imageReaderQueue = new LinkedList(); + private final ArrayDeque imageReaderQueue = new ArrayDeque(); private final HashMap perImageReaders = new HashMap(); private PerImage lastDequeuedImage = null; @@ -456,7 +465,7 @@ public PerImage(Image image, long queuedTime) { /** Internal class: state held per ImageReader. */ private class PerImageReader { public final ImageReader reader; - private final LinkedList imageQueue = new LinkedList(); + private final ArrayDeque imageQueue = new ArrayDeque(); private boolean closed = false; private final ImageReader.OnImageAvailableListener onImageAvailableListener = @@ -479,7 +488,8 @@ private class PerImageReader { public PerImageReader(ImageReader reader) { this.reader = reader; - reader.setOnImageAvailableListener(onImageAvailableListener, new Handler()); + reader.setOnImageAvailableListener( + onImageAvailableListener, new Handler(Looper.getMainLooper())); } PerImage queueImage(Image image) { @@ -649,6 +659,15 @@ PerImage dequeueImage() { @Override public void onTrimMemory(int level) { + if (!trimOnMemoryPressure) { + return; + } + if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + return; + } + synchronized (lock) { + numTrims++; + } cleanup(); createNewReader = true; } @@ -865,6 +884,13 @@ public int numImageReaders() { } } + @VisibleForTesting + public int numTrims() { + synchronized (lock) { + return numTrims; + } + } + @VisibleForTesting public int numImages() { int r = 0; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 6e2b2ab250f82..093be1566cb4e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -9,6 +9,7 @@ import static io.flutter.Build.API_LEVELS; import android.annotation.TargetApi; +import android.content.ComponentCallbacks2; import android.content.Context; import android.content.MutableContextWrapper; import android.os.Build; @@ -1053,6 +1054,24 @@ private void diposeAllViews() { } } + // Invoked when the Android system is requesting we reduce memory usage. + public void onTrimMemory(int level) { + if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + return; + } + for (VirtualDisplayController vdc : vdControllers.values()) { + vdc.clearSurface(); + } + } + + // Called after the application has been resumed. + // This is where we undo whatever may have been done in onTrimMemory. + public void onResume() { + for (VirtualDisplayController vdc : vdControllers.values()) { + vdc.resetSurface(); + } + } + /** * Disposes a single * diff --git a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java index 5de42299e00c4..cdde0687a4a99 100644 --- a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +++ b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java @@ -284,6 +284,49 @@ public void dispatchTouchEvent(MotionEvent event) { presentation.dispatchTouchEvent(event); } + public void clearSurface() { + virtualDisplay.setSurface(null); + } + + public void resetSurface() { + final int width = getRenderTargetWidth(); + final int height = getRenderTargetHeight(); + final boolean isFocused = getView().isFocused(); + final SingleViewPresentation.PresentationState presentationState = presentation.detachState(); + + // We detach the surface to prevent it being destroyed when releasing the vd. + virtualDisplay.setSurface(null); + virtualDisplay.release(); + final DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + int flags = 0; + virtualDisplay = + displayManager.createVirtualDisplay( + "flutter-vd#" + viewId, + width, + height, + densityDpi, + renderTarget.getSurface(), + flags, + callback, + null /* handler */); + // Create a new SingleViewPresentation and show() it before we cancel() the existing + // presentation. Calling show() and cancel() in this order fixes + // https://github.com/flutter/flutter/issues/26345 and maintains seamless transition + // of the contents of the presentation. + SingleViewPresentation newPresentation = + new SingleViewPresentation( + context, + virtualDisplay.getDisplay(), + accessibilityEventsDelegate, + presentationState, + focusChangeListener, + isFocused); + newPresentation.show(); + presentation.cancel(); + presentation = newPresentation; + } + static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener { static void schedule(View view, Runnable runnable) { OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 50f83d5fb8e0a..f19da788d3759 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -647,12 +647,41 @@ public void ImageReaderSurfaceProducerTrimMemoryCallback() { assertEquals(1, texture.numImageReaders()); assertEquals(1, texture.numImages()); - // Invoke the onTrimMemory callback. + // Invoke the onTrimMemory callback with level 0. + // This should do nothing. texture.onTrimMemory(0); shadowOf(Looper.getMainLooper()).idle(); + assertEquals(1, texture.numImageReaders()); + assertEquals(1, texture.numImages()); + assertEquals(0, texture.numTrims()); + + // Invoke the onTrimMemory callback with level 40. + // This should result in a trim. + texture.onTrimMemory(40); + shadowOf(Looper.getMainLooper()).idle(); + assertEquals(0, texture.numImageReaders()); assertEquals(0, texture.numImages()); + assertEquals(1, texture.numTrims()); + + // Request the surface, this should result in a new image reader. + surface = texture.getSurface(); + assertEquals(1, texture.numImageReaders()); + assertEquals(0, texture.numImages()); + assertEquals(1, texture.numTrims()); + + // Render an image. + canvas = surface.lockHardwareCanvas(); + canvas.drawARGB(255, 255, 0, 0); + surface.unlockCanvasAndPost(canvas); + + // Let callbacks run, this will produce a single frame. + shadowOf(Looper.getMainLooper()).idle(); + + assertEquals(1, texture.numImageReaders()); + assertEquals(1, texture.numImages()); + assertEquals(1, texture.numTrims()); } // A 0x0 ImageReader is a runtime error. diff --git a/shell/platform/common/accessibility_bridge.cc b/shell/platform/common/accessibility_bridge.cc index 9a6e02065ee64..0987986ad3c88 100644 --- a/shell/platform/common/accessibility_bridge.cc +++ b/shell/platform/common/accessibility_bridge.cc @@ -280,6 +280,7 @@ void AccessibilityBridge::ConvertFlutterUpdate(const SemanticsNode& node, SetIntAttributesFromFlutterUpdate(node_data, node); SetIntListAttributesFromFlutterUpdate(node_data, node); SetStringListAttributesFromFlutterUpdate(node_data, node); + SetIdentifierFromFlutterUpdate(node_data, node); SetNameFromFlutterUpdate(node_data, node); SetValueFromFlutterUpdate(node_data, node); SetTooltipFromFlutterUpdate(node_data, node); @@ -519,6 +520,13 @@ void AccessibilityBridge::SetStringListAttributesFromFlutterUpdate( } } +void AccessibilityBridge::SetIdentifierFromFlutterUpdate( + ui::AXNodeData& node_data, + const SemanticsNode& node) { + node_data.AddStringAttribute(ax::mojom::StringAttribute::kAuthorUniqueId, + node.identifier); +} + void AccessibilityBridge::SetNameFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { node_data.SetName(node.label); @@ -587,6 +595,9 @@ AccessibilityBridge::FromFlutterSemanticsNode( result.scroll_extent_min = flutter_node.scroll_extent_min; result.elevation = flutter_node.elevation; result.thickness = flutter_node.thickness; + if (flutter_node.identifier) { + result.identifier = std::string(flutter_node.identifier); + } if (flutter_node.label) { result.label = std::string(flutter_node.label); } diff --git a/shell/platform/common/accessibility_bridge.h b/shell/platform/common/accessibility_bridge.h index 7e6698021b726..5877207ea5087 100644 --- a/shell/platform/common/accessibility_bridge.h +++ b/shell/platform/common/accessibility_bridge.h @@ -172,6 +172,7 @@ class AccessibilityBridge double scroll_extent_min; double elevation; double thickness; + std::string identifier; std::string label; std::string hint; std::string value; @@ -227,6 +228,8 @@ class AccessibilityBridge const SemanticsNode& node); void SetStringListAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); + void SetIdentifierFromFlutterUpdate(ui::AXNodeData& node_data, + const SemanticsNode& node); void SetNameFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetValueFromFlutterUpdate(ui::AXNodeData& node_data, diff --git a/shell/platform/common/flutter_platform_node_delegate.cc b/shell/platform/common/flutter_platform_node_delegate.cc index 9e4e8a4a2b111..32a0824fc31a2 100644 --- a/shell/platform/common/flutter_platform_node_delegate.cc +++ b/shell/platform/common/flutter_platform_node_delegate.cc @@ -6,6 +6,7 @@ #include +#include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/shell/platform/common/accessibility_bridge.h" #include "flutter/third_party/accessibility/ax/ax_action_data.h" #include "flutter/third_party/accessibility/ax/ax_tree_manager_map.h" @@ -61,6 +62,12 @@ const ui::AXNodeData& FlutterPlatformNodeDelegate::GetData() const { return ax_node_->data(); } +std::u16string FlutterPlatformNodeDelegate::GetAuthorUniqueId() const { + return fml::WideStringToUtf16( + fml::Utf8ToWideString(GetData().GetStringAttribute( + ax::mojom::StringAttribute::kAuthorUniqueId))); +} + gfx::NativeViewAccessible FlutterPlatformNodeDelegate::GetParent() { if (!ax_node_->parent()) { return nullptr; diff --git a/shell/platform/common/flutter_platform_node_delegate.h b/shell/platform/common/flutter_platform_node_delegate.h index 6172f4f86b9f2..bad838cb6ec5b 100644 --- a/shell/platform/common/flutter_platform_node_delegate.h +++ b/shell/platform/common/flutter_platform_node_delegate.h @@ -101,6 +101,9 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase { // |ui::AXPlatformNodeDelegateBase| const ui::AXUniqueId& GetUniqueId() const override { return unique_id_; } + // |ui::AXPlatformNodeDelegateBase| + std::u16string GetAuthorUniqueId() const override; + // |ui::AXPlatformNodeDelegateBase| const ui::AXNodeData& GetData() const override; diff --git a/shell/platform/common/flutter_platform_node_delegate_unittests.cc b/shell/platform/common/flutter_platform_node_delegate_unittests.cc index 00e3240fa4f6c..c158c5bbc342c 100644 --- a/shell/platform/common/flutter_platform_node_delegate_unittests.cc +++ b/shell/platform/common/flutter_platform_node_delegate_unittests.cc @@ -46,6 +46,7 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; + root.identifier = ""; root.label = "root"; root.hint = ""; root.value = ""; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 70c1ac7a4742d..2eaf9c359ce7d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -200,6 +200,8 @@ static BOOL DoesHardwareSupportWideGamut() { } } + settings.warn_on_impeller_opt_out = true; + NSNumber* enableTraceSystrace = [mainBundle objectForInfoDictionaryKey:@"FLTTraceSystrace"]; // Change the default only if the option is present. if (enableTraceSystrace != nil) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm index 7de95d4771ae8..c68018bbdeb5f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -235,6 +235,11 @@ - (void)testDisableImpellerSettingIsCorrectlyParsed { [mockMainBundle stopMocking]; } +- (void)testRequestsWarningWhenImpellerOptOut { + auto settings = FLTDefaultSettingsForBundle(); + XCTAssertEqual(settings.warn_on_impeller_opt_out, YES); +} + - (void)testEnableImpellerSettingIsCorrectlyParsed { id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"YES"); diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index d26e2215adf3a..95e2c04da0d43 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -1308,6 +1308,8 @@ typedef struct { double elevation; /// Describes how much space the semantics node takes up along the z-axis. double thickness; + /// A technical identifier for the node. + const char* identifier; /// A textual description of the node. const char* label; /// A brief description of the result of performing an action on the node. diff --git a/shell/platform/embedder/embedder_semantics_update.cc b/shell/platform/embedder/embedder_semantics_update.cc index 1670d7c7341e6..a47b679bf0194 100644 --- a/shell/platform/embedder/embedder_semantics_update.cc +++ b/shell/platform/embedder/embedder_semantics_update.cc @@ -153,6 +153,7 @@ void EmbedderSemanticsUpdate2::AddNode(const SemanticsNode& node) { node.scrollExtentMin, node.elevation, node.thickness, + node.identifier.c_str(), node.label.c_str(), node.hint.c_str(), node.value.c_str(), diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index 82e6cd9c486e5..3fb6121d68f9e 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -806,12 +806,9 @@ LRESULT FlutterWindow::OnGetObject(UINT const message, } gfx::NativeViewAccessible root_view = GetNativeViewAccessible(); - // TODO(schectman): UIA is currently disabled by default. - // https://github.com/flutter/flutter/issues/114547 if (root_view) { CreateAxFragmentRoot(); if (is_uia_request) { -#ifdef FLUTTER_ENGINE_USE_UIA // Retrieve UIA object for the root view. Microsoft::WRL::ComPtr root; if (SUCCEEDED( @@ -824,7 +821,6 @@ LRESULT FlutterWindow::OnGetObject(UINT const message, } else { FML_LOG(ERROR) << "Failed to query AX fragment root."; } -#endif // FLUTTER_ENGINE_USE_UIA } else if (is_msaa_request) { // Create the accessibility root if it does not already exist. // Return the IAccessible for the root view. diff --git a/shell/platform/windows/window_unittests.cc b/shell/platform/windows/window_unittests.cc index 97b3f70c55127..d1a77cfeb3334 100644 --- a/shell/platform/windows/window_unittests.cc +++ b/shell/platform/windows/window_unittests.cc @@ -366,9 +366,7 @@ TEST(MockWindow, DISABLED_GetObjectUia) { bool uia_called = false; ON_CALL(window, OnGetObject) .WillByDefault(Invoke([&uia_called](UINT msg, WPARAM wpar, LPARAM lpar) { -#ifdef FLUTTER_ENGINE_USE_UIA uia_called = true; -#endif // FLUTTER_ENGINE_USE_UIA return static_cast(0); })); EXPECT_CALL(window, OnGetObject).Times(1); diff --git a/third_party/accessibility/ax/ax_enum_util.cc b/third_party/accessibility/ax/ax_enum_util.cc index daa93b61bbc3e..b925f16300228 100644 --- a/third_party/accessibility/ax/ax_enum_util.cc +++ b/third_party/accessibility/ax/ax_enum_util.cc @@ -1461,6 +1461,8 @@ const char* ToString(ax::mojom::StringAttribute string_attribute) { return "url"; case ax::mojom::StringAttribute::kValue: return "value"; + case ax::mojom::StringAttribute::kAuthorUniqueId: + return "authorUniqueId"; } return ""; @@ -1521,6 +1523,8 @@ ax::mojom::StringAttribute ParseStringAttribute(const char* string_attribute) { return ax::mojom::StringAttribute::kUrl; if (0 == strcmp(string_attribute, "value")) return ax::mojom::StringAttribute::kValue; + if (0 == strcmp(string_attribute, "authorUniqueId")) + return ax::mojom::StringAttribute::kAuthorUniqueId; return ax::mojom::StringAttribute::kNone; } diff --git a/third_party/accessibility/ax/ax_enums.h b/third_party/accessibility/ax/ax_enums.h index 1a444cdeea54e..454c529cb1019 100644 --- a/third_party/accessibility/ax/ax_enums.h +++ b/third_party/accessibility/ax/ax_enums.h @@ -523,6 +523,7 @@ enum class StringAttribute { kAccessKey, // Only used when invalid_state == invalid_state_other. kAriaInvalidValue, + kAuthorUniqueId, kAutoComplete, kChildTreeId, kClassName, diff --git a/third_party/accessibility/ax/ax_node_data.cc b/third_party/accessibility/ax/ax_node_data.cc index f13be3d2e6ef9..e70ce3e68e2e4 100644 --- a/third_party/accessibility/ax/ax_node_data.cc +++ b/third_party/accessibility/ax/ax_node_data.cc @@ -1520,6 +1520,9 @@ std::string AXNodeData::ToString() const { break; case ax::mojom::StringAttribute::kNone: break; + case ax::mojom::StringAttribute::kAuthorUniqueId: + result += " author_unique_id=" + value; + break; } }