diff --git a/DEPS b/DEPS index 471822b61502d..f70bdd6c7257f 100644 --- a/DEPS +++ b/DEPS @@ -35,7 +35,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': '654db247514fd82fe4037af9c7ef419cab8484b5', + 'dart_revision': '63c2197b976931c6472d9dc9574f98ff2ae9408c', # 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/firebase_testlab.py b/ci/firebase_testlab.py index 36b5d419b6471..92069a7c1f584 100755 --- a/ci/firebase_testlab.py +++ b/ci/firebase_testlab.py @@ -11,7 +11,16 @@ import subprocess import sys -BUCKET = 'gs://flutter_firebase_testlab' +if 'STORAGE_BUCKET' not in os.environ: + print('The GCP storage bucket must be provided as an environment variable.') + sys.exit(1) +BUCKET = os.environ['STORAGE_BUCKET'] + +if 'GCP_PROJECT' not in os.environ: + print('The GCP project must be provided as an environment variable.') + sys.exit(1) +PROJECT = os.environ['GCP_PROJECT'] + script_dir = os.path.dirname(os.path.realpath(__file__)) buildroot_dir = os.path.abspath(os.path.join(script_dir, '..', '..')) out_dir = os.path.join(buildroot_dir, 'out') @@ -28,7 +37,7 @@ def run_firebase_test(apk, results_dir): [ 'gcloud', '--project', - 'flutter-infra', + PROJECT, 'firebase', 'test', 'android', diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5ce695d846cc3..677e78b464ba5 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2397,6 +2397,8 @@ FILE: ../../../flutter/shell/platform/windows/window_state.h FILE: ../../../flutter/shell/platform/windows/window_win32.cc FILE: ../../../flutter/shell/platform/windows/window_win32.h FILE: ../../../flutter/shell/platform/windows/window_win32_unittests.cc +FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc +FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h FILE: ../../../flutter/shell/profiling/sampling_profiler.cc FILE: ../../../flutter/shell/profiling/sampling_profiler.h FILE: ../../../flutter/shell/profiling/sampling_profiler_unittest.cc diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 93a2faecc7e70..e82de0511c027 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: af6717216660c358a8a89f8c8a9cf21b +Signature: afc25a44d546c441153d3dd3a92c0ddf UNUSED LICENSES: diff --git a/impeller/compiler/impellerc_main.cc b/impeller/compiler/impellerc_main.cc index 79ff437355df9..1b752ab120662 100644 --- a/impeller/compiler/impellerc_main.cc +++ b/impeller/compiler/impellerc_main.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include +#include #include "flutter/fml/backtrace.h" #include "flutter/fml/command_line.h" @@ -20,6 +21,21 @@ namespace impeller { namespace compiler { +// Sets the file access mode of the file at path 'p' to 0644. +static bool SetPermissiveAccess(const std::filesystem::path& p) { + auto permissions = + std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | + std::filesystem::perms::group_read | std::filesystem::perms::others_read; + std::error_code error; + std::filesystem::permissions(p, permissions, error); + if (error) { + std::cerr << "Failed to set access on file '" << p + << "': " << error.message() << std::endl; + return false; + } + return true; +} + bool Main(const fml::CommandLine& command_line) { fml::InstallCrashHandler(); if (command_line.HasOption("help")) { @@ -110,6 +126,11 @@ bool Main(const fml::CommandLine& command_line) { << std::endl; return false; } + // Tools that consume the runtime stage data expect the access mode to + // be 0644. + if (!SetPermissiveAccess(sl_file_name)) { + return false; + } } else { if (!fml::WriteAtomically(*switches.working_directory, sl_file_name.string().c_str(), diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 80b4f773f9bf2..6d6fef7278cbd 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -185,6 +185,7 @@ if (enable_unittests) { dart_main = "fixtures/ui_test.dart" fixtures = [ "fixtures/DashInNooglerHat.jpg", + "fixtures/DashInNooglerHat%20WithSpace.jpg", "fixtures/Horizontal.jpg", "fixtures/Horizontal.png", "fixtures/hello_loop_2.gif", diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index d98275d34f9c7..b3b51035edcb8 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -21,23 +21,6 @@ class Scene extends NativeFieldWrapperClass1 { @pragma('vm:entry-point') Scene._(); - /// Synchronously creates a handle to an image from this scene. - /// - /// {@macro dart.ui.painting.Picture.toImageSync} - Image toImageSync(int width, int height) { - if (width <= 0 || height <= 0) { - throw Exception('Invalid image dimensions.'); - } - - final _Image image = _Image._(); - final String? result = _toImageSync(width, height, image); - if (result != null) { - throw PictureRasterizationException._(result); - } - return Image._(image, image.width, image.height); - } - String? _toImageSync(int width, int height, _Image outImage) native 'Scene_toImageSync'; - /// Creates a raster image representation of the current state of the scene. /// This is a slow operation that is performed on a background thread. /// diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 48def9ddc2941..a6448970f662d 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -22,7 +22,6 @@ namespace flutter { IMPLEMENT_WRAPPERTYPEINFO(ui, Scene); #define FOR_EACH_BINDING(V) \ - V(Scene, toImageSync) \ V(Scene, toImage) \ V(Scene, dispose) @@ -67,24 +66,6 @@ void Scene::dispose() { ClearDartWrapper(); } -Dart_Handle Scene::toImageSync(uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle) { - TRACE_EVENT0("flutter", "Scene::toImageSync"); - - if (!layer_tree_) { - return tonic::ToDart("Scene did not contain a layer tree."); - } - - auto picture = layer_tree_->Flatten(SkRect::MakeWH(width, height)); - if (!picture) { - return tonic::ToDart("Could not flatten scene into a layer tree."); - } - - Picture::RasterizeToImageSync(picture, width, height, raw_image_handle); - return Dart_Null(); -} - Dart_Handle Scene::toImage(uint32_t width, uint32_t height, Dart_Handle raw_image_callback) { diff --git a/lib/ui/compositing/scene.h b/lib/ui/compositing/scene.h index d550eac484ab6..f7a8d93711814 100644 --- a/lib/ui/compositing/scene.h +++ b/lib/ui/compositing/scene.h @@ -32,10 +32,6 @@ class Scene : public RefCountedDartWrappable { std::unique_ptr takeLayerTree(); - Dart_Handle toImageSync(uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle); - Dart_Handle toImage(uint32_t width, uint32_t height, Dart_Handle image_callback); diff --git a/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg b/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg new file mode 100644 index 0000000000000..488fdb4d5215c Binary files /dev/null and b/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg differ diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 92fa4967a15b5..79b027ac996c2 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -5375,35 +5375,6 @@ class Picture extends NativeFieldWrapperClass1 { } String? _toImage(int width, int height, _Callback<_Image?> callback) native 'Picture_toImage'; - /// Synchronously creates a handle to an image of this picture. - /// - /// {@template dart.ui.painting.Picture.toImageSync} - /// The returned image will be `width` pixels wide and `height` pixels high. - /// The picture is rasterized within the 0 (left), 0 (top), `width` (right), - /// `height` (bottom) bounds. Content outside these bounds is clipped. - /// - /// The image object is created and returned synchronously, but is rasterized - /// asynchronously. If the rasterization fails, an exception will be thrown - /// when the image is drawn to a [Canvas]. - /// - /// If a GPU context is available, this image will be created as GPU resident - /// and not copied back to the host. This means the image will be more - /// efficient to draw. - /// - /// If no GPU context is availalbe, the image will be rasterized on the CPU. - /// {@endtemplate} - Image toImageSync(int width, int height) { - assert(!_disposed); - if (width <= 0 || height <= 0) { - throw Exception('Invalid image dimensions.'); - } - - final _Image image = _Image._(); - _toImageSync(width, height, image); - return Image._(image, image.width, image.height); - } - void _toImageSync(int width, int height, _Image outImage) native 'Picture_toImageSync'; - /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose() { @@ -5703,9 +5674,14 @@ class ImmutableBuffer extends NativeFieldWrapperClass1 { /// /// Throws an [Exception] if the asset does not exist. static Future fromAsset(String assetKey) { + // The flutter tool converts all asset keys with spaces into URI + // encoded paths (replacing ' ' with '%20', for example). We perform + // the same encoding here so that users can load assets with the same + // key they have written in the pubspec. + final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; final ImmutableBuffer instance = ImmutableBuffer._(0); return _futurize((_Callback callback) { - return instance._initFromAsset(assetKey, callback); + return instance._initFromAsset(encodedKey, callback); }).then((int length) => instance.._length = length); } @@ -5912,7 +5888,7 @@ Future _futurize(_Callbacker callbacker) { } /// An exception thrown by [Canvas.drawImage] and related methods when drawing -/// an [Image] created via [Picture.toImageSync] that is in an invalid state. +/// an [Image] that is in an invalid state. /// /// This exception may be thrown if the requested image dimensions exceeded the /// maximum 2D texture size allowed by the GPU, or if no GPU surface or context diff --git a/lib/ui/painting/picture.cc b/lib/ui/painting/picture.cc index 4ec907e093cdd..95564670a381d 100644 --- a/lib/ui/painting/picture.cc +++ b/lib/ui/painting/picture.cc @@ -23,7 +23,6 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Picture); #define FOR_EACH_BINDING(V) \ V(Picture, toImage) \ - V(Picture, toImageSync) \ V(Picture, dispose) \ V(Picture, GetAllocationSize) @@ -53,46 +52,6 @@ Dart_Handle Picture::toImage(uint32_t width, raw_image_callback); } -void Picture::toImageSync(uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle) { - FML_DCHECK(display_list_.skia_object()); - RasterizeToImageSync(display_list_.skia_object(), width, height, - raw_image_handle); -} - -// static -void Picture::RasterizeToImageSync(sk_sp display_list, - uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle) { - auto* dart_state = UIDartState::Current(); - auto unref_queue = dart_state->GetSkiaUnrefQueue(); - auto snapshot_delegate = dart_state->GetSnapshotDelegate(); - auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner(); - - auto image = CanvasImage::Create(); - auto dl_image = DlDeferredImageGPU::Make(SkISize::Make(width, height)); - image->set_image(dl_image); - - fml::TaskRunner::RunNowOrPostTask( - raster_task_runner, - [snapshot_delegate, unref_queue, dl_image = std::move(dl_image), - display_list = std::move(display_list)]() { - sk_sp sk_image; - std::string error; - std::tie(sk_image, error) = snapshot_delegate->MakeGpuImage( - display_list, dl_image->dimensions()); - if (sk_image) { - dl_image->set_image(std::move(sk_image)); - } else { - dl_image->set_error(std::move(error)); - } - }); - - image->AssociateWithDartWrapper(raw_image_handle); -} - void Picture::dispose() { display_list_.reset(); ClearDartWrapper(); diff --git a/lib/ui/painting/picture.h b/lib/ui/painting/picture.h index 92653e9123cd6..33e5cb5772cf4 100644 --- a/lib/ui/painting/picture.h +++ b/lib/ui/painting/picture.h @@ -37,21 +37,12 @@ class Picture : public RefCountedDartWrappable { uint32_t height, Dart_Handle raw_image_callback); - void toImageSync(uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle); - void dispose(); size_t GetAllocationSize() const override; static void RegisterNatives(tonic::DartLibraryNatives* natives); - static void RasterizeToImageSync(sk_sp display_list, - uint32_t width, - uint32_t height, - Dart_Handle raw_image_handle); - static Dart_Handle RasterizeToImage(sk_sp display_list, uint32_t width, uint32_t height, diff --git a/lib/web_ui/lib/canvas.dart b/lib/web_ui/lib/canvas.dart index c9359b6930781..995bc66299617 100644 --- a/lib/web_ui/lib/canvas.dart +++ b/lib/web_ui/lib/canvas.dart @@ -155,7 +155,6 @@ abstract class Canvas { abstract class Picture { Future toImage(int width, int height); - Image toImageSync(int width, int height); void dispose(); bool get debugDisposed; int get approximateBytesUsed; diff --git a/lib/web_ui/lib/compositing.dart b/lib/web_ui/lib/compositing.dart index 96c51cbeeecf4..de1e6cdbee255 100644 --- a/lib/web_ui/lib/compositing.dart +++ b/lib/web_ui/lib/compositing.dart @@ -6,7 +6,6 @@ part of ui; abstract class Scene { Future toImage(int width, int height); - Image toImageSync(int width, int height); void dispose(); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart index eb6d860a0e072..f0cbd4a12a545 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart @@ -25,12 +25,6 @@ class LayerScene implements ui.Scene { final ui.Picture picture = layerTree.flatten(); return picture.toImage(width, height); } - - @override - ui.Image toImageSync(int width, int height) { - final ui.Picture picture = layerTree.flatten(); - return picture.toImageSync(width, height); - } } class LayerSceneBuilder implements ui.SceneBuilder { diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture.dart b/lib/web_ui/lib/src/engine/canvaskit/picture.dart index c2af7eb844e09..0227246be5157 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture.dart @@ -92,11 +92,6 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { @override Future toImage(int width, int height) async { - return toImageSync(width, height); - } - - @override - ui.Image toImageSync(int width, int height) { assert(debugCheckNotDisposed('Cannot convert picture to image.')); final SkSurface skSurface = canvasKit.MakeSurface(width, height); final SkCanvas skCanvas = skSurface.getCanvas(); diff --git a/lib/web_ui/lib/src/engine/html/scene.dart b/lib/web_ui/lib/src/engine/html/scene.dart index 645390e28eaa0..7ae9a36d69096 100644 --- a/lib/web_ui/lib/src/engine/html/scene.dart +++ b/lib/web_ui/lib/src/engine/html/scene.dart @@ -24,11 +24,6 @@ class SurfaceScene implements ui.Scene { throw UnsupportedError('toImage is not supported on the Web'); } - @override - ui.Image toImageSync(int width, int height) { - throw UnsupportedError('toImageSync is not supported on the Web'); - } - /// Releases the resources used by this scene. /// /// After calling this function, the scene is cannot be used further. diff --git a/lib/web_ui/lib/src/engine/picture.dart b/lib/web_ui/lib/src/engine/picture.dart index dc432b2b9f34c..6cc68063c91bf 100644 --- a/lib/web_ui/lib/src/engine/picture.dart +++ b/lib/web_ui/lib/src/engine/picture.dart @@ -88,11 +88,6 @@ class EnginePicture implements ui.Picture { return onImageLoaded.future; } - @override - ui.Image toImageSync(int width, int height) { - throw UnsupportedError('toImageSync is not supported on the HTML backend. Use drawPicture instead, or toImage.'); - } - bool _disposed = false; @override diff --git a/lib/web_ui/test/canvaskit/picture_test.dart b/lib/web_ui/test/canvaskit/picture_test.dart index b082d13d6e260..59851bfc7c3bc 100644 --- a/lib/web_ui/test/canvaskit/picture_test.dart +++ b/lib/web_ui/test/canvaskit/picture_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:typed_data'; - import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; @@ -85,23 +83,6 @@ void testMain() { expect(picture.resurrect(), isNotNull); }); }); - - test('toImageSync', () async { - const ui.Color color = ui.Color(0xFFAAAAAA); - final ui.PictureRecorder recorder = ui.PictureRecorder(); - final ui.Canvas canvas = ui.Canvas(recorder); - canvas.drawPaint(ui.Paint()..color = color); - final ui.Picture picture = recorder.endRecording(); - final ui.Image image = picture.toImageSync(10, 15); - - expect(image.width, 10); - expect(image.height, 15); - - final ByteData? data = await image.toByteData(); - expect(data, isNotNull); - expect(data!.lengthInBytes, 10 * 15 * 4); - expect(data.buffer.asUint32List().first, color.value); - }); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 }, skip: isIosSafari); } diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index b8908bdd24563..f9d526ae6db45 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -321,26 +321,3 @@ void scene_with_red_box() { }; PlatformDispatcher.instance.scheduleFrame(); } - - -@pragma('vm:entry-point') -Future toImageSync() async { - final PictureRecorder recorder = PictureRecorder(); - final Canvas canvas = Canvas(recorder); - canvas.drawPaint(Paint()..color = const Color(0xFFAAAAAA)); - final Picture picture = recorder.endRecording(); - - final Image image = picture.toImageSync(20, 25); - void expect(Object? a, Object? b) { - if (a != b) { - throw 'Expected $a to == $b'; - } - } - expect(image.width, 20); - expect(image.height, 25); - - final ByteData data = (await image.toByteData())!; - expect(data.lengthInBytes, 20 * 25 * 4); - expect(data.buffer.asUint32List().every((int byte) => byte == 0xFFAAAAAA), true); - notifyNative(); -} diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 23f91cdbda900..5b97ac0c27b46 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -3754,40 +3754,6 @@ TEST_F(ShellTest, SpawnWorksWithOnError) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } -TEST_F(ShellTest, PictureToImageSync) { -#if !SHELL_ENABLE_GL - // GL emulation does not exist on Fuchsia. - GTEST_SKIP(); -#endif // !SHELL_ENABLE_GL - auto settings = CreateSettingsForFixture(); - std::unique_ptr shell = - CreateShell(settings, // - GetTaskRunnersForFixture(), // - false, // - nullptr, // - false, // - ShellTestPlatformView::BackendType::kGLBackend // - ); - - fml::AutoResetWaitableEvent latch; - AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) { - latch.Signal(); - })); - - ASSERT_NE(shell, nullptr); - ASSERT_TRUE(shell->IsSetup()); - auto configuration = RunConfiguration::InferFromSettings(settings); - PlatformViewNotifyCreated(shell.get()); - configuration.SetEntrypoint("toImageSync"); - RunEngine(shell.get(), std::move(configuration)); - PumpOneFrame(shell.get()); - - latch.Wait(); - - PlatformViewNotifyDestroyed(shell.get()); - DestroyShell(std::move(shell)); -} - } // namespace testing } // namespace flutter diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index c57d90f0162c1..d55b0a6b7ce7f 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -9,10 +9,10 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; -import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Build; @@ -169,11 +169,7 @@ public void setTexture(@Nullable SurfaceTexture newTx) { // to the user until the platform view draws its first frame. final Canvas canvas = surface.lockHardwareCanvas(); try { - if (Build.VERSION.SDK_INT >= 29) { - canvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); - } else { - canvas.drawColor(Color.TRANSPARENT); - } + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); onFrameProduced(); } finally { surface.unlockCanvasAndPost(canvas); @@ -305,11 +301,7 @@ public void draw(Canvas canvas) { try { // Clear the current pixels in the canvas. // This helps when a WebView renders an HTML document with transparent background. - if (Build.VERSION.SDK_INT >= 29) { - surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); - } else { - surfaceCanvas.drawColor(Color.TRANSPARENT); - } + surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); super.draw(surfaceCanvas); onFrameProduced(); } finally { diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index 188875d89d5af..aa17536df2db4 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -53,6 +53,7 @@ @Keep @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) class SingleViewPresentation extends Presentation { + private static final String TAG = "PlatformViewsController"; /* * When an embedded view is resized in Flutterverse we move the Android view to a new virtual display @@ -184,10 +185,20 @@ protected void onCreate(Bundle savedInstanceState) { MutableContextWrapper currentContext = (MutableContextWrapper) embeddedView.getContext(); currentContext.setBaseContext(baseContext); } else { - throw new IllegalStateException( - "Unexpected platform view context. " - + "When constructing a platform view in the factory, use the context from PlatformViewFactory#create, view id: " - + viewId); + // In some cases, such as when using LayoutInflator, the original context + // may not be preserved. For backward compatibility with previous + // implementations of Virtual Display, which didn't validate the context, + // continue, but log a warning indicating that some functionality may not + // work as expected. + // See https://github.com/flutter/flutter/issues/110146 for context. + Log.w( + TAG, + "Unexpected platform view context for view ID " + + viewId + + "; some functionality may not work correctly. When constructing a platform view " + + "in the factory, ensure that the view returned from PlatformViewFactory#create " + + "returns the provided context from getContext(). If you are unable to associate " + + "the view with that context, consider using Hybrid Composition instead."); } container.addView(embeddedView); diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java index 2893df22834f5..630504f9aaeef 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -7,9 +7,9 @@ import android.annotation.TargetApi; import android.content.Context; -import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.PorterDuff; import android.graphics.SurfaceTexture; import android.view.Surface; import android.view.View; @@ -65,7 +65,7 @@ protected Surface createSurface(@NonNull SurfaceTexture tx) { // Verify. verify(surface, times(1)).lockHardwareCanvas(); verify(surface, times(1)).unlockCanvasAndPost(canvas); - verify(canvas, times(1)).drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); verifyNoMoreInteractions(surface); verifyNoMoreInteractions(canvas); } @@ -112,7 +112,7 @@ public void draw(Canvas canvas) { wrapper.draw(new Canvas()); // Verify. - verify(canvas, times(1)).drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + verify(canvas, times(1)).drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); verify(surface, times(1)).isValid(); verify(surface, times(1)).lockHardwareCanvas(); verify(surface, times(1)).unlockCanvasAndPost(canvas); diff --git a/shell/platform/common/accessibility_bridge.cc b/shell/platform/common/accessibility_bridge.cc index ab2468e331cfa..9b392af208df6 100644 --- a/shell/platform/common/accessibility_bridge.cc +++ b/shell/platform/common/accessibility_bridge.cc @@ -63,7 +63,7 @@ void AccessibilityBridge::CommitUpdates() { for (size_t i = results.size(); i > 0; i--) { for (SemanticsNode node : results[i - 1]) { - ConvertFluterUpdate(node, update); + ConvertFlutterUpdate(node, update); } } @@ -191,8 +191,8 @@ void AccessibilityBridge::GetSubTreeList(SemanticsNode target, } } -void AccessibilityBridge::ConvertFluterUpdate(const SemanticsNode& node, - ui::AXTreeUpdate& tree_update) { +void AccessibilityBridge::ConvertFlutterUpdate(const SemanticsNode& node, + ui::AXTreeUpdate& tree_update) { ui::AXNodeData node_data; node_data.id = node.id; SetRoleFromFlutterUpdate(node_data, node); diff --git a/shell/platform/common/accessibility_bridge.h b/shell/platform/common/accessibility_bridge.h index 32b4fcb27112b..ad573beeb0764 100644 --- a/shell/platform/common/accessibility_bridge.h +++ b/shell/platform/common/accessibility_bridge.h @@ -223,8 +223,8 @@ class AccessibilityBridge void InitAXTree(const ui::AXTreeUpdate& initial_state); void GetSubTreeList(SemanticsNode target, std::vector& result); - void ConvertFluterUpdate(const SemanticsNode& node, - ui::AXTreeUpdate& tree_update); + void ConvertFlutterUpdate(const SemanticsNode& node, + ui::AXTreeUpdate& tree_update); void SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetStateFromFlutterUpdate(ui::AXNodeData& node_data, diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 9d606cfbbb1ea..bf489f363bf0b 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1245,88 +1245,110 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, settings.log_tag = SAFE_ACCESS(args, log_tag, nullptr); } - flutter::PlatformViewEmbedder::UpdateSemanticsNodesCallback - update_semantics_nodes_callback = nullptr; + FlutterUpdateSemanticsNodeCallback update_semantics_node_callback = nullptr; if (SAFE_ACCESS(args, update_semantics_node_callback, nullptr) != nullptr) { - update_semantics_nodes_callback = - [ptr = args->update_semantics_node_callback, - user_data](flutter::SemanticsNodeUpdates update) { - for (const auto& value : update) { - const auto& node = value.second; - SkMatrix transform = node.transform.asM33(); - FlutterTransformation flutter_transform{ - transform.get(SkMatrix::kMScaleX), - transform.get(SkMatrix::kMSkewX), - transform.get(SkMatrix::kMTransX), - transform.get(SkMatrix::kMSkewY), - transform.get(SkMatrix::kMScaleY), - transform.get(SkMatrix::kMTransY), - transform.get(SkMatrix::kMPersp0), - transform.get(SkMatrix::kMPersp1), - transform.get(SkMatrix::kMPersp2)}; - const FlutterSemanticsNode embedder_node{ - sizeof(FlutterSemanticsNode), - node.id, - static_cast(node.flags), - static_cast(node.actions), - node.textSelectionBase, - node.textSelectionExtent, - node.scrollChildren, - node.scrollIndex, - node.scrollPosition, - node.scrollExtentMax, - node.scrollExtentMin, - node.elevation, - node.thickness, - node.label.c_str(), - node.hint.c_str(), - node.value.c_str(), - node.increasedValue.c_str(), - node.decreasedValue.c_str(), - static_cast(node.textDirection), - FlutterRect{node.rect.fLeft, node.rect.fTop, node.rect.fRight, - node.rect.fBottom}, - flutter_transform, - node.childrenInTraversalOrder.size(), - node.childrenInTraversalOrder.data(), - node.childrenInHitTestOrder.data(), - node.customAccessibilityActions.size(), - node.customAccessibilityActions.data(), - node.platformViewId, - }; - ptr(&embedder_node, user_data); - } - const FlutterSemanticsNode batch_end_sentinel = { - sizeof(FlutterSemanticsNode), - kFlutterSemanticsNodeIdBatchEnd, - }; - ptr(&batch_end_sentinel, user_data); - }; + update_semantics_node_callback = args->update_semantics_node_callback; } - flutter::PlatformViewEmbedder::UpdateSemanticsCustomActionsCallback - update_semantics_custom_actions_callback = nullptr; + FlutterUpdateSemanticsCustomActionCallback + update_semantics_custom_action_callback = nullptr; if (SAFE_ACCESS(args, update_semantics_custom_action_callback, nullptr) != nullptr) { - update_semantics_custom_actions_callback = - [ptr = args->update_semantics_custom_action_callback, - user_data](flutter::CustomAccessibilityActionUpdates actions) { - for (const auto& value : actions) { - const auto& action = value.second; - const FlutterSemanticsCustomAction embedder_action = { + update_semantics_custom_action_callback = + args->update_semantics_custom_action_callback; + } + + flutter::PlatformViewEmbedder::UpdateSemanticsCallback + update_semantics_callback = nullptr; + if (update_semantics_node_callback != nullptr || + update_semantics_custom_action_callback != nullptr) { + update_semantics_callback = + [update_semantics_node_callback, + update_semantics_custom_action_callback, + user_data](flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions) { + // First, queue all node and custom action updates. + if (update_semantics_node_callback != nullptr) { + for (const auto& value : update) { + const auto& node = value.second; + SkMatrix transform = node.transform.asM33(); + FlutterTransformation flutter_transform{ + transform.get(SkMatrix::kMScaleX), + transform.get(SkMatrix::kMSkewX), + transform.get(SkMatrix::kMTransX), + transform.get(SkMatrix::kMSkewY), + transform.get(SkMatrix::kMScaleY), + transform.get(SkMatrix::kMTransY), + transform.get(SkMatrix::kMPersp0), + transform.get(SkMatrix::kMPersp1), + transform.get(SkMatrix::kMPersp2)}; + const FlutterSemanticsNode embedder_node{ + sizeof(FlutterSemanticsNode), + node.id, + static_cast(node.flags), + static_cast(node.actions), + node.textSelectionBase, + node.textSelectionExtent, + node.scrollChildren, + node.scrollIndex, + node.scrollPosition, + node.scrollExtentMax, + node.scrollExtentMin, + node.elevation, + node.thickness, + node.label.c_str(), + node.hint.c_str(), + node.value.c_str(), + node.increasedValue.c_str(), + node.decreasedValue.c_str(), + static_cast(node.textDirection), + FlutterRect{node.rect.fLeft, node.rect.fTop, node.rect.fRight, + node.rect.fBottom}, + flutter_transform, + node.childrenInTraversalOrder.size(), + node.childrenInTraversalOrder.data(), + node.childrenInHitTestOrder.data(), + node.customAccessibilityActions.size(), + node.customAccessibilityActions.data(), + node.platformViewId, + }; + update_semantics_node_callback(&embedder_node, user_data); + } + } + + if (update_semantics_custom_action_callback != nullptr) { + for (const auto& value : actions) { + const auto& action = value.second; + const FlutterSemanticsCustomAction embedder_action = { + sizeof(FlutterSemanticsCustomAction), + action.id, + static_cast(action.overrideId), + action.label.c_str(), + action.hint.c_str(), + }; + update_semantics_custom_action_callback(&embedder_action, + user_data); + } + } + + // Second, mark node and action batches completed now that all + // updates are queued. + if (update_semantics_node_callback != nullptr) { + const FlutterSemanticsNode batch_end_sentinel = { + sizeof(FlutterSemanticsNode), + kFlutterSemanticsNodeIdBatchEnd, + }; + update_semantics_node_callback(&batch_end_sentinel, user_data); + } + + if (update_semantics_custom_action_callback != nullptr) { + const FlutterSemanticsCustomAction batch_end_sentinel = { sizeof(FlutterSemanticsCustomAction), - action.id, - static_cast(action.overrideId), - action.label.c_str(), - action.hint.c_str(), + kFlutterSemanticsCustomActionIdBatchEnd, }; - ptr(&embedder_action, user_data); + update_semantics_custom_action_callback(&batch_end_sentinel, + user_data); } - const FlutterSemanticsCustomAction batch_end_sentinel = { - sizeof(FlutterSemanticsCustomAction), - kFlutterSemanticsCustomActionIdBatchEnd, - }; - ptr(&batch_end_sentinel, user_data); }; } @@ -1421,8 +1443,7 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, flutter::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = { - update_semantics_nodes_callback, // - update_semantics_custom_actions_callback, // + update_semantics_callback, // platform_message_response_callback, // vsync_callback, // compute_platform_resolved_locale_callback, // diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index a24e13e2636d0..60e8e912aa892 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -67,13 +67,9 @@ PlatformViewEmbedder::~PlatformViewEmbedder() = default; void PlatformViewEmbedder::UpdateSemantics( flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { - if (platform_dispatch_table_.update_semantics_nodes_callback != nullptr) { - platform_dispatch_table_.update_semantics_nodes_callback(std::move(update)); - } - if (platform_dispatch_table_.update_semantics_custom_actions_callback != - nullptr) { - platform_dispatch_table_.update_semantics_custom_actions_callback( - std::move(actions)); + if (platform_dispatch_table_.update_semantics_callback != nullptr) { + platform_dispatch_table_.update_semantics_callback(std::move(update), + std::move(actions)); } } diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 5c40ce1e16e79..1a18f4683fa36 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -31,10 +31,9 @@ namespace flutter { class PlatformViewEmbedder final : public PlatformView { public: - using UpdateSemanticsNodesCallback = - std::function; - using UpdateSemanticsCustomActionsCallback = - std::function; + using UpdateSemanticsCallback = + std::function; using PlatformMessageResponseCallback = std::function)>; using ComputePlatformResolvedLocaleCallback = @@ -43,9 +42,7 @@ class PlatformViewEmbedder final : public PlatformView { using OnPreEngineRestartCallback = std::function; struct PlatformDispatchTable { - UpdateSemanticsNodesCallback update_semantics_nodes_callback; // optional - UpdateSemanticsCustomActionsCallback - update_semantics_custom_actions_callback; // optional + UpdateSemanticsCallback update_semantics_callback; // optional PlatformMessageResponseCallback platform_message_response_callback; // optional VsyncWaiterEmbedder::VsyncCallback vsync_callback; // optional diff --git a/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/shell/platform/embedder/tests/embedder_a11y_unittests.cc index 656807605e86a..e8f340872c951 100644 --- a/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -120,40 +120,47 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistent) { latch.Wait(); // Wait for UpdateSemantics callback on platform (current) thread. - int node_count = 0; int node_batch_end_count = 0; - context.SetSemanticsNodeCallback( - [&node_count, &node_batch_end_count](const FlutterSemanticsNode* node) { - if (node->id == kFlutterSemanticsNodeIdBatchEnd) { - ++node_batch_end_count; - } else { - ++node_count; - ASSERT_EQ(1.0, node->transform.scaleX); - ASSERT_EQ(2.0, node->transform.skewX); - ASSERT_EQ(3.0, node->transform.transX); - ASSERT_EQ(4.0, node->transform.skewY); - ASSERT_EQ(5.0, node->transform.scaleY); - ASSERT_EQ(6.0, node->transform.transY); - ASSERT_EQ(7.0, node->transform.pers0); - ASSERT_EQ(8.0, node->transform.pers1); - ASSERT_EQ(9.0, node->transform.pers2); - - if (node->id == 128) { - ASSERT_EQ(0x3f3, node->platform_view_id); - } else { - ASSERT_EQ(0, node->platform_view_id); - } - } - }); + int action_batch_end_count = 0; + + int node_count = 0; + context.SetSemanticsNodeCallback([&](const FlutterSemanticsNode* node) { + if (node->id == kFlutterSemanticsNodeIdBatchEnd) { + ++node_batch_end_count; + } else { + // Batches should be completed after all nodes are received. + ASSERT_EQ(0, node_batch_end_count); + ASSERT_EQ(0, action_batch_end_count); + + ++node_count; + ASSERT_EQ(1.0, node->transform.scaleX); + ASSERT_EQ(2.0, node->transform.skewX); + ASSERT_EQ(3.0, node->transform.transX); + ASSERT_EQ(4.0, node->transform.skewY); + ASSERT_EQ(5.0, node->transform.scaleY); + ASSERT_EQ(6.0, node->transform.transY); + ASSERT_EQ(7.0, node->transform.pers0); + ASSERT_EQ(8.0, node->transform.pers1); + ASSERT_EQ(9.0, node->transform.pers2); + + if (node->id == 128) { + ASSERT_EQ(0x3f3, node->platform_view_id); + } else { + ASSERT_EQ(0, node->platform_view_id); + } + } + }); int action_count = 0; - int action_batch_end_count = 0; context.SetSemanticsCustomActionCallback( - [&action_count, - &action_batch_end_count](const FlutterSemanticsCustomAction* action) { + [&](const FlutterSemanticsCustomAction* action) { if (action->id == kFlutterSemanticsCustomActionIdBatchEnd) { ++action_batch_end_count; } else { + // Batches should be completed after all actions are received. + ASSERT_EQ(0, node_batch_end_count); + ASSERT_EQ(0, action_batch_end_count); + ++action_count; } }); diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index 79eb749e632a9..88f833a9fdfce 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -119,14 +119,14 @@ void EmbedderTestContext::AddNativeCallback(const char* name, } void EmbedderTestContext::SetSemanticsNodeCallback( - const SemanticsNodeCallback& update_semantics_node_callback) { - update_semantics_node_callback_ = update_semantics_node_callback; + SemanticsNodeCallback update_semantics_node_callback) { + update_semantics_node_callback_ = std::move(update_semantics_node_callback); } void EmbedderTestContext::SetSemanticsCustomActionCallback( - const SemanticsActionCallback& update_semantics_custom_action_callback) { + SemanticsActionCallback update_semantics_custom_action_callback) { update_semantics_custom_action_callback_ = - update_semantics_custom_action_callback; + std::move(update_semantics_custom_action_callback); } void EmbedderTestContext::SetPlatformMessageCallback( diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index f7df168122a3d..ade3333d95fc3 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -71,11 +71,10 @@ class EmbedderTestContext { void AddNativeCallback(const char* name, Dart_NativeFunction function); - void SetSemanticsNodeCallback( - const SemanticsNodeCallback& update_semantics_node); + void SetSemanticsNodeCallback(SemanticsNodeCallback update_semantics_node); void SetSemanticsCustomActionCallback( - const SemanticsActionCallback& semantics_custom_action); + SemanticsActionCallback semantics_custom_action); void SetPlatformMessageCallback( const std::function& callback); diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 8402f1471d4c5..13077b7c12481 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -110,6 +110,8 @@ source_set("flutter_windows_source") { "window_state.h", "window_win32.cc", "window_win32.h", + "windows_proc_table.cc", + "windows_proc_table.h", ] libs = [ @@ -195,6 +197,7 @@ executable("flutter_windows_unittests") { "testing/engine_modifier.h", "testing/flutter_window_win32_test.cc", "testing/flutter_window_win32_test.h", + "testing/mock_direct_manipulation.h", "testing/mock_gl_functions.h", "testing/mock_text_input_manager_win32.cc", "testing/mock_text_input_manager_win32.h", @@ -202,6 +205,7 @@ executable("flutter_windows_unittests") { "testing/mock_window_binding_handler.h", "testing/mock_window_win32.cc", "testing/mock_window_win32.h", + "testing/mock_windows_proc_table.h", "testing/test_keyboard.cc", "testing/test_keyboard.h", "testing/test_keyboard_unittests.cc", diff --git a/shell/platform/windows/direct_manipulation.h b/shell/platform/windows/direct_manipulation.h index bca747173e4f8..26d546d898347 100644 --- a/shell/platform/windows/direct_manipulation.h +++ b/shell/platform/windows/direct_manipulation.h @@ -22,6 +22,7 @@ class DirectManipulationEventHandler; class DirectManipulationOwner { public: explicit DirectManipulationOwner(WindowWin32* window); + virtual ~DirectManipulationOwner() = default; // Initialize a DirectManipulation viewport with specified width and height. // These should match the width and height of the application window. int Init(unsigned int width, unsigned int height); @@ -34,7 +35,7 @@ class DirectManipulationOwner { WindowBindingHandlerDelegate* binding_handler_delegate); // Called when DM_POINTERHITTEST occurs with an acceptable pointer type. Will // start DirectManipulation for that interaction. - void SetContact(UINT contactId); + virtual void SetContact(UINT contactId); // Called to get updates from DirectManipulation. Should be called frequently // to provide smooth updates. void Update(); @@ -57,6 +58,8 @@ class DirectManipulationOwner { Microsoft::WRL::ComPtr viewport_; // Child needed for operation of the DirectManipulation API. fml::RefPtr handler_; + + FML_DISALLOW_COPY_AND_ASSIGN(DirectManipulationOwner); }; // Implements DirectManipulation event handling interfaces, receives calls from diff --git a/shell/platform/windows/testing/mock_direct_manipulation.h b/shell/platform/windows/testing/mock_direct_manipulation.h new file mode 100644 index 0000000000000..1631a0875ae65 --- /dev/null +++ b/shell/platform/windows/testing/mock_direct_manipulation.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ + +#include "flutter/shell/platform/windows/direct_manipulation.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +/// Mock for the |DirectManipulationOwner| base class. +class MockDirectManipulationOwner : public DirectManipulationOwner { + public: + explicit MockDirectManipulationOwner(WindowWin32* window) + : DirectManipulationOwner(window){}; + virtual ~MockDirectManipulationOwner() = default; + + MOCK_METHOD1(SetContact, void(UINT contact_id)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockDirectManipulationOwner); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index a44bc0972a4c2..12e64e7f48a2e 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -8,8 +8,10 @@ namespace flutter { namespace testing { MockWin32Window::MockWin32Window() : WindowWin32(){}; MockWin32Window::MockWin32Window( + std::unique_ptr window_proc_table, std::unique_ptr text_input_manager) - : WindowWin32(std::move(text_input_manager)){}; + : WindowWin32(std::move(window_proc_table), + std::move(text_input_manager)){}; MockWin32Window::~MockWin32Window() = default; @@ -24,6 +26,11 @@ LRESULT MockWin32Window::Win32DefWindowProc(HWND hWnd, return kWmResultDefault; } +void MockWin32Window::SetDirectManipulationOwner( + std::unique_ptr owner) { + direct_manipulation_owner_ = std::move(owner); +} + LRESULT MockWin32Window::InjectWindowMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) { diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index cfd7bac65c3ff..0278e6d3e15ba 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -18,7 +18,8 @@ namespace testing { class MockWin32Window : public WindowWin32 { public: MockWin32Window(); - MockWin32Window(std::unique_ptr text_input_manager); + MockWin32Window(std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager); virtual ~MockWin32Window(); // Prevent copying. @@ -28,6 +29,10 @@ class MockWin32Window : public WindowWin32 { // Wrapper for GetCurrentDPI() which is a protected method. UINT GetDpi(); + // Set the Direct Manipulation owner for testing purposes. + void SetDirectManipulationOwner( + std::unique_ptr owner); + // Simulates a WindowProc message from the OS. LRESULT InjectWindowMessage(UINT const message, WPARAM const wparam, diff --git a/shell/platform/windows/testing/mock_windows_proc_table.h b/shell/platform/windows/testing/mock_windows_proc_table.h new file mode 100644 index 0000000000000..7c136def68654 --- /dev/null +++ b/shell/platform/windows/testing/mock_windows_proc_table.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ + +#include "flutter/shell/platform/windows/windows_proc_table.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +/// Mock for the |WindowsProcTable| base class. +class MockWindowsProcTable : public WindowsProcTable { + public: + MockWindowsProcTable() = default; + virtual ~MockWindowsProcTable() = default; + + MOCK_METHOD2(GetPointerType, + BOOL(UINT32 pointer_id, POINTER_INPUT_TYPE* pointer_type)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index e05063346135d..f45457ca72c10 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -51,16 +51,22 @@ static const int kMaxTouchDeviceId = 128; } // namespace -WindowWin32::WindowWin32() : WindowWin32(nullptr) {} +WindowWin32::WindowWin32() : WindowWin32(nullptr, nullptr) {} WindowWin32::WindowWin32( + std::unique_ptr windows_proc_table, std::unique_ptr text_input_manager) : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId), + windows_proc_table_(std::move(windows_proc_table)), text_input_manager_(std::move(text_input_manager)) { // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is // supported, |current_dpi_| should be updated in the // kWmDpiChangedBeforeParent message. current_dpi_ = GetDpiForHWND(nullptr); + + if (windows_proc_table_ == nullptr) { + windows_proc_table_ = std::make_unique(); + } if (text_input_manager_ == nullptr) { text_input_manager_ = std::make_unique(); } @@ -473,11 +479,11 @@ WindowWin32::HandleMessage(UINT const message, break; case DM_POINTERHITTEST: { if (direct_manipulation_owner_) { - UINT contactId = GET_POINTERID_WPARAM(wparam); - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(contactId, &pointerType) && - pointerType == PT_TOUCHPAD) { - direct_manipulation_owner_->SetContact(contactId); + UINT contact_id = GET_POINTERID_WPARAM(wparam); + POINTER_INPUT_TYPE pointer_type; + if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) && + pointer_type == PT_TOUCHPAD) { + direct_manipulation_owner_->SetContact(contact_id); } } break; diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index cc142e699468e..f86320722b212 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -18,6 +18,7 @@ #include "flutter/shell/platform/windows/keyboard_manager_win32.h" #include "flutter/shell/platform/windows/sequential_id_generator.h" #include "flutter/shell/platform/windows/text_input_manager_win32.h" +#include "flutter/shell/platform/windows/windows_proc_table.h" #include "flutter/third_party/accessibility/gfx/native_widget_types.h" namespace flutter { @@ -28,7 +29,8 @@ namespace flutter { class WindowWin32 : public KeyboardManagerWin32::WindowDelegate { public: WindowWin32(); - WindowWin32(std::unique_ptr text_input_manager); + WindowWin32(std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager); virtual ~WindowWin32(); // Initializes as a child window with size using |width| and |height| and @@ -254,6 +256,10 @@ class WindowWin32 : public KeyboardManagerWin32::WindowDelegate { double mouse_x_ = 0; double mouse_y_ = 0; + // Abstracts Windows APIs that may not be available on all supported versions + // of Windows. + std::unique_ptr windows_proc_table_; + // Manages IME state. std::unique_ptr text_input_manager_; diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index 9f8f010a33217..3bb59bc670837 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/shell/platform/windows/testing/mock_direct_manipulation.h" #include "flutter/shell/platform/windows/testing/mock_text_input_manager_win32.h" #include "flutter/shell/platform/windows/testing/mock_window_win32.h" +#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; +using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Return; @@ -37,11 +41,12 @@ TEST(MockWin32Window, VerticalScroll) { } TEST(MockWin32Window, OnImeCompositionCompose) { - MockTextInputManagerWin32* text_input_manager = - new MockTextInputManagerWin32(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManagerWin32(); std::unique_ptr text_input_manager_ptr( text_input_manager); - MockWin32Window window(std::move(text_input_manager_ptr)); + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(*text_input_manager, GetComposingString()) .WillRepeatedly( Return(std::optional(std::u16string(u"nihao")))); @@ -63,11 +68,12 @@ TEST(MockWin32Window, OnImeCompositionCompose) { } TEST(MockWin32Window, OnImeCompositionResult) { - MockTextInputManagerWin32* text_input_manager = - new MockTextInputManagerWin32(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManagerWin32(); std::unique_ptr text_input_manager_ptr( text_input_manager); - MockWin32Window window(std::move(text_input_manager_ptr)); + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(*text_input_manager, GetComposingString()) .WillRepeatedly( Return(std::optional(std::u16string(u"nihao")))); @@ -89,11 +95,12 @@ TEST(MockWin32Window, OnImeCompositionResult) { } TEST(MockWin32Window, OnImeCompositionResultAndCompose) { - MockTextInputManagerWin32* text_input_manager = - new MockTextInputManagerWin32(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManagerWin32(); std::unique_ptr text_input_manager_ptr( text_input_manager); - MockWin32Window window(std::move(text_input_manager_ptr)); + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); // This situation is that Google Japanese Input finished composing "今日" in // "今日は" but is still composing "は". @@ -127,11 +134,12 @@ TEST(MockWin32Window, OnImeCompositionResultAndCompose) { } TEST(MockWin32Window, OnImeCompositionClearChange) { - MockTextInputManagerWin32* text_input_manager = - new MockTextInputManagerWin32(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManagerWin32(); std::unique_ptr text_input_manager_ptr( text_input_manager); - MockWin32Window window(std::move(text_input_manager_ptr)); + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(window, OnComposeChange(std::u16string(u""), 0)).Times(1); EXPECT_CALL(window, OnComposeCommit()).Times(1); ON_CALL(window, OnImeComposition) @@ -273,5 +281,81 @@ TEST(MockWin32Window, KeyDownWithCtrlToggled) { SetKeyboardState(keyboard_state); } +// Verify direct manipulation isn't notified of pointer hit tests. +TEST(MockWin32Window, PointerHitTest) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { + *type = PT_POINTER; + return TRUE; + }); + + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact).Times(0); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + +// Verify direct manipulation is notified of touchpad hit tests. +TEST(MockWin32Window, TouchPadHitTest) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { + *type = PT_TOUCHPAD; + return TRUE; + }); + + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact(Eq(pointer_id))).Times(1); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + +// Verify direct manipulation isn't notified of unknown hit tests. +// This can happen if determining the pointer type fails, for example, +// if GetPointerType is unsupported by the current Windows version. +// See: https://github.com/flutter/flutter/issues/109412 +TEST(MockWin32Window, UnknownPointerTypeSkipsDirectManipulation) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce( + [](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { return FALSE; }); + + MockWin32Window window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact).Times(0); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.cc b/shell/platform/windows/windows_proc_table.cc new file mode 100644 index 0000000000000..d2b5bd99f4264 --- /dev/null +++ b/shell/platform/windows/windows_proc_table.cc @@ -0,0 +1,28 @@ +// 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. + +#include "flutter/shell/platform/windows/windows_proc_table.h" + +namespace flutter { + +WindowsProcTable::WindowsProcTable() { + user32_ = fml::NativeLibrary::Create("user32.dll"); + get_pointer_type_ = + user32_->ResolveFunction("GetPointerType"); +} + +WindowsProcTable::~WindowsProcTable() { + user32_ = nullptr; +} + +BOOL WindowsProcTable::GetPointerType(UINT32 pointer_id, + POINTER_INPUT_TYPE* pointer_type) { + if (!get_pointer_type_.has_value()) { + return FALSE; + } + + return get_pointer_type_.value()(pointer_id, pointer_type); +} + +} // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.h b/shell/platform/windows/windows_proc_table.h new file mode 100644 index 0000000000000..db6397d2e4781 --- /dev/null +++ b/shell/platform/windows/windows_proc_table.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ + +#include "flutter/fml/native_library.h" + +#include + +namespace flutter { + +// Lookup table for Windows APIs that aren't available on all versions of +// Windows. +class WindowsProcTable { + public: + WindowsProcTable(); + virtual ~WindowsProcTable(); + + // Retrieves the pointer type for a specified pointer. + // + // Used to react differently to touch or pen inputs. Returns false on failure. + // Available in Windows 8 and newer, otherwise returns false. + virtual BOOL GetPointerType(UINT32 pointer_id, + POINTER_INPUT_TYPE* pointer_type); + + private: + using GetPointerType_ = BOOL __stdcall(UINT32 pointerId, + POINTER_INPUT_TYPE* pointerType); + + // The User32.dll library, used to resolve functions at runtime. + fml::RefPtr user32_; + + std::optional get_pointer_type_; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowsProcTable); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ diff --git a/testing/dart/assets_test.dart b/testing/dart/assets_test.dart index 7a2522c399fc5..64943c2179a91 100644 --- a/testing/dart/assets_test.dart +++ b/testing/dart/assets_test.dart @@ -27,6 +27,13 @@ void main() { expect(buffer.length == 354679, true); }); + test('Can load an asset with a space in the key', () async { + // This assets actual path is "fixtures/DashInNooglerHat%20WithSpace.jpg" + final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat WithSpace.jpg'); + + expect(buffer.length == 354679, true); + }); + test('can dispose immutable buffer', () async { final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat.jpg'); diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index 4de620f9750cd..c0bae43fb5f0a 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -410,103 +410,6 @@ void main() { expect(areEqual, true); }, skip: !Platform.isLinux); // https://github.com/flutter/flutter/issues/53784 - test('toImageSync - too big', () async { - PictureRecorder recorder = PictureRecorder(); - Canvas canvas = Canvas(recorder); - canvas.drawPaint(Paint()..color = const Color(0xFF123456)); - final Picture picture = recorder.endRecording(); - final Image image = picture.toImageSync(300000, 4000000); - picture.dispose(); - - expect(image.width, 300000); - expect(image.height, 4000000); - - recorder = PictureRecorder(); - canvas = Canvas(recorder); - - // On a slower CI machine, the raster thread may get behind the UI thread - // here. However, once the image is in an error state it will immediately - // throw on subsequent attempts. - bool caughtException = false; - for (int iterations = 0; iterations < 1000; iterations += 1) { - try { - canvas.drawImage(image, Offset.zero, Paint()); - } on PictureRasterizationException catch (e) { - caughtException = true; - expect(e.message, contains('unable to create render target at specified size')); - break; - } - // Let the event loop turn. - await Future.delayed(const Duration(milliseconds: 1)); - } - expect(caughtException, true); - expect( - () => canvas.drawImageRect(image, Rect.zero, Rect.zero, Paint()), - throwsException, - ); - expect( - () => canvas.drawImageNine(image, Rect.zero, Rect.zero, Paint()), - throwsException, - ); - expect( - () => canvas.drawAtlas(image, [], [], null, null, null, Paint()), - throwsException, - ); - }); - - test('toImageSync - succeeds', () async { - PictureRecorder recorder = PictureRecorder(); - Canvas canvas = Canvas(recorder); - canvas.drawPaint(Paint()..color = const Color(0xFF123456)); - final Picture picture = recorder.endRecording(); - final Image image = picture.toImageSync(30, 40); - picture.dispose(); - - expect(image.width, 30); - expect(image.height, 40); - - recorder = PictureRecorder(); - canvas = Canvas(recorder); - expect( - () => canvas.drawImage(image, Offset.zero, Paint()), - returnsNormally, - ); - expect( - () => canvas.drawImageRect(image, Rect.zero, Rect.zero, Paint()), - returnsNormally, - ); - expect( - () => canvas.drawImageNine(image, Rect.zero, Rect.zero, Paint()), - returnsNormally, - ); - expect( - () => canvas.drawAtlas(image, [], [], null, null, null, Paint()), - returnsNormally, - ); - }); - - test('toImageSync - toByteData', () async { - const Color color = Color(0xFF123456); - final PictureRecorder recorder = PictureRecorder(); - final Canvas canvas = Canvas(recorder); - canvas.drawPaint(Paint()..color = color); - final Picture picture = recorder.endRecording(); - final Image image = picture.toImageSync(6, 8); - picture.dispose(); - - expect(image.width, 6); - expect(image.height, 8); - - final ByteData? data = await image.toByteData(format: ImageByteFormat.rawRgba); - - expect(data, isNotNull); - expect(data!.lengthInBytes, 6 * 8 * 4); - expect(data.buffer.asUint8List()[0], 0x12); - expect(data.buffer.asUint8List()[1], 0x34); - expect(data.buffer.asUint8List()[2], 0x56); - expect(data.buffer.asUint8List()[3], 0xFF); - }); - test('Canvas.drawParagraph throws when Paragraph.layout was not called', () async { // Regression test for https://github.com/flutter/flutter/issues/97172 bool assertsEnabled = false; diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 7815fa2aaaf57..69b147c2514ac 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -8,34 +8,6 @@ import 'dart:ui'; import 'package:litetest/litetest.dart'; void main() { - test('Scene.toImageSync succeeds', () async { - final PictureRecorder recorder = PictureRecorder(); - final Canvas canvas = Canvas(recorder); - const Color color = Color(0xFF123456); - canvas.drawPaint(Paint()..color = color); - final Picture picture = recorder.endRecording(); - final SceneBuilder builder = SceneBuilder(); - builder.pushOffset(10, 10); - builder.addPicture(const Offset(5, 5), picture); - final Scene scene = builder.build(); - - final Image image = scene.toImageSync(6, 8); - picture.dispose(); - scene.dispose(); - - expect(image.width, 6); - expect(image.height, 8); - - final ByteData? data = await image.toByteData(); - - expect(data, isNotNull); - expect(data!.lengthInBytes, 6 * 8 * 4); - expect(data.buffer.asUint8List()[0], 0x12); - expect(data.buffer.asUint8List()[1], 0x34); - expect(data.buffer.asUint8List()[2], 0x56); - expect(data.buffer.asUint8List()[3], 0xFF); - }); - test('addPicture with disposed picture does not crash', () { bool assertsEnabled = false; assert(() { diff --git a/testing/dart/image_shader_test.dart b/testing/dart/image_shader_test.dart index 56c502b132596..f34b91ae85315 100644 --- a/testing/dart/image_shader_test.dart +++ b/testing/dart/image_shader_test.dart @@ -16,20 +16,4 @@ void main() { const Rect rect = Rect.fromLTRB(0, 0, 100, 100); testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); }); - - test('Construct an ImageShader - GPU image', () async { - final PictureRecorder recorder = PictureRecorder(); - final Canvas canvas = Canvas(recorder); - canvas.drawPaint(Paint()..color = const Color(0xFFABCDEF)); - final Picture picture = recorder.endRecording(); - final Image image = picture.toImageSync(50, 50); - picture.dispose(); - - // TODO(dnfield): this should not throw once - // https://github.com/flutter/flutter/issues/105085 is fixed. - expect( - () => ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)), - throwsException, - ); - }); } diff --git a/testing/scenario_app/android/BUILD.gn b/testing/scenario_app/android/BUILD.gn index 9e3b401f61882..3be504a6f0fdb 100644 --- a/testing/scenario_app/android/BUILD.gn +++ b/testing/scenario_app/android/BUILD.gn @@ -13,6 +13,7 @@ _android_sources = [ "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", + "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java new file mode 100644 index 0000000000000..a036a90a73e02 --- /dev/null +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewBadContextUiTest.java @@ -0,0 +1,45 @@ +// 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. + +package dev.flutter.scenariosui; + +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; +import dev.flutter.scenarios.PlatformViewsActivity; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PlatformViewWithSurfaceViewBadContextUiTest { + Intent intent; + + @Rule @NonNull + public ActivityTestRule activityRule = + new ActivityTestRule<>( + PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); + + private static String goldName(String suffix) { + return "PlatformViewWithSurfaceViewBadContextUiTest_" + suffix; + } + + @Before + public void setUp() { + intent = new Intent(Intent.ACTION_MAIN); + // Render a texture. + intent.putExtra("use_android_view", false); + intent.putExtra("view_type", PlatformViewsActivity.SURFACE_VIEW_BAD_CONTEXT_PV); + } + + @Test + public void testPlatformView() throws Exception { + intent.putExtra("scenario_name", "platform_view"); + ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); + } +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java index a67be3ab2c76f..80cde94422e10 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java @@ -8,8 +8,12 @@ import io.flutter.embedding.engine.FlutterEngine; public class PlatformViewsActivity extends TestActivity { + // WARNING: These strings must all be exactly the same length to avoid + // breaking the 'create' method's manual encoding in the test. See the + // TODO(stuartmorgan) about encoding alignment in platform_view.dart public static final String TEXT_VIEW_PV = "scenarios/textPlatformView"; public static final String SURFACE_VIEW_PV = "scenarios/surfacePlatformV"; + public static final String SURFACE_VIEW_BAD_CONTEXT_PV = "scenarios/surfaceVBadCntxt"; public static final String TEXTURE_VIEW_PV = "scenarios/texturePlatformV"; @Override @@ -23,7 +27,12 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { flutterEngine .getPlatformViewsController() .getRegistry() - .registerViewFactory(SURFACE_VIEW_PV, new SurfacePlatformViewFactory()); + .registerViewFactory(SURFACE_VIEW_PV, new SurfacePlatformViewFactory(true)); + + flutterEngine + .getPlatformViewsController() + .getRegistry() + .registerViewFactory(SURFACE_VIEW_BAD_CONTEXT_PV, new SurfacePlatformViewFactory(false)); flutterEngine .getPlatformViewsController() diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java index bec46167f63c8..399afdf1be6b9 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java @@ -6,6 +6,7 @@ import android.annotation.TargetApi; import android.content.Context; +import android.content.ContextWrapper; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -24,7 +25,9 @@ @TargetApi(23) public final class SurfacePlatformViewFactory extends PlatformViewFactory { - SurfacePlatformViewFactory() { + private boolean preserveContext; + + SurfacePlatformViewFactory(boolean preserveContext) { super( new MessageCodec() { @Nullable @@ -42,13 +45,19 @@ public Object decodeMessage(@Nullable ByteBuffer byteBuffer) { return StringCodec.INSTANCE.decodeMessage(byteBuffer); } }); + this.preserveContext = preserveContext; } @SuppressWarnings("unchecked") @Override @NonNull public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { - return new SurfacePlatformView(context); + if (preserveContext) { + return new SurfacePlatformView(context); + } else { + final Context differentContext = new ContextWrapper(context); + return new SurfacePlatformView(differentContext); + } } private static class SurfacePlatformView implements PlatformView { diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index 8f1704e6ef6a2..0f6cf35c15516 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -1139,6 +1139,11 @@ void addPlatformView( _valueString, 'width'.length, ...utf8.encode('width'), + // This is missing the 64-bit boundary alignment, making the entire + // message encoding fragile to changes before this point. Do not add new + // variable-length values such as strings before this point. + // TODO(stuartmorgan): Fix this to use the actual encoding logic, + // including alignment: https://github.com/flutter/flutter/issues/111188 _valueFloat64, ..._to64(width), _valueString,