From ea227fa396956e301882d3220ba45ca14d2e9d03 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 30 Jul 2020 19:40:31 +0000 Subject: [PATCH 1/3] Migrate engine to use multi-window API The goal here is to convert from the window singleton to an API that has the concept of multiple windows. I'm not attempting to actually enable creating multiple windows here, just migrate to an API that has a concept of multiple windows. --- ci/licenses_golden/licenses_flutter | 3 + lib/ui/compositing.dart | 22 +- lib/ui/compositing/scene.cc | 3 +- lib/ui/dart_ui.gni | 1 + lib/ui/hooks.dart | 229 +-- lib/ui/natives.dart | 2 +- lib/ui/painting/canvas.cc | 2 +- lib/ui/platform_dispatcher.dart | 1556 +++++++++++++++++ lib/ui/semantics.dart | 17 +- lib/ui/text.dart | 2 +- lib/ui/ui.dart | 1 + lib/ui/window.dart | 1248 ++++--------- lib/ui/window/platform_configuration.cc | 7 +- lib/ui/window/platform_configuration.h | 11 +- .../platform_configuration_unittests.cc | 29 +- lib/ui/window/viewport_metrics.cc | 18 + lib/ui/window/viewport_metrics.h | 12 + lib/ui/window/window.cc | 6 +- lib/ui/window/window.h | 5 +- lib/web_ui/lib/src/engine.dart | 9 +- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 8 +- lib/web_ui/lib/src/engine/canvas_pool.dart | 12 +- .../src/engine/canvaskit/embedded_views.dart | 2 +- .../engine/canvaskit/skia_object_cache.dart | 2 +- lib/web_ui/lib/src/engine/dom_renderer.dart | 8 +- .../lib/src/engine/html/render_vertices.dart | 4 +- .../lib/src/engine/html/surface_stats.dart | 8 +- lib/web_ui/lib/src/engine/keyboard.dart | 6 +- .../lib/src/engine/navigation/history.dart | 12 +- .../lib/src/engine/platform_dispatcher.dart | 923 ++++++++++ .../lib/src/engine/pointer_binding.dart | 4 +- lib/web_ui/lib/src/engine/profiler.dart | 4 +- .../src/engine/semantics/incrementable.dart | 4 +- .../lib/src/engine/semantics/scrollable.dart | 8 +- .../lib/src/engine/semantics/semantics.dart | 4 +- .../lib/src/engine/semantics/tappable.dart | 2 +- .../lib/src/engine/semantics/text_field.dart | 4 +- .../src/engine/text_editing/text_editing.dart | 18 +- lib/web_ui/lib/src/engine/util.dart | 4 +- lib/web_ui/lib/src/engine/window.dart | 853 ++------- .../lib/src/ui/platform_dispatcher.dart | 420 +++++ lib/web_ui/lib/src/ui/text.dart | 1 - lib/web_ui/lib/src/ui/window.dart | 371 +--- lib/web_ui/lib/ui.dart | 1 + .../canvaskit/skia_objects_cache_test.dart | 2 +- lib/web_ui/test/engine/history_test.dart | 12 +- .../engine/surface/platform_view_test.dart | 2 +- lib/web_ui/test/engine/window_test.dart | 28 +- .../engine/canvas_golden_test.dart | 4 +- .../engine/compositing_golden_test.dart | 4 +- lib/web_ui/test/window_test.dart | 14 +- runtime/runtime_controller.cc | 4 +- runtime/runtime_controller.h | 2 +- shell/common/engine.h | 19 +- shell/common/fixtures/shell_test.dart | 40 +- shell/common/shell_test.cc | 6 +- shell/common/shell_unittests.cc | 25 +- .../embedding/android/FlutterView.java | 1 - .../flutter/embedding/engine/FlutterJNI.java | 6 + .../engine/renderer/FlutterRenderer.java | 10 +- .../android/io/flutter/view/FlutterView.java | 4 + .../android/platform_view_android_jni_impl.cc | 24 +- .../platform/PlatformViewsControllerTest.java | 2 + .../framework/Source/FlutterViewController.mm | 22 +- .../macos/framework/Source/FlutterEngine.mm | 11 +- shell/platform/embedder/embedder.cc | 7 +- shell/platform/embedder/embedder.h | 4 + shell/platform/embedder/fixtures/main.dart | 180 +- .../platform/fuchsia/flutter/platform_view.cc | 2 + .../dart/window_hooks_integration_test.dart | 154 +- .../lib/src/animated_color_square.dart | 8 +- .../scenario_app/lib/src/channel_util.dart | 4 +- .../lib/src/initial_route_reply.dart | 8 +- .../lib/src/locale_initialization.dart | 9 +- .../scenario_app/lib/src/platform_view.dart | 160 +- .../scenario_app/lib/src/poppable_screen.dart | 10 +- testing/scenario_app/lib/src/scenario.dart | 20 +- testing/scenario_app/lib/src/scenarios.dart | 48 +- .../lib/src/send_text_focus_semantics.dart | 8 +- .../lib/src/touches_scenario.dart | 2 +- 80 files changed, 4165 insertions(+), 2567 deletions(-) create mode 100644 lib/ui/platform_dispatcher.dart create mode 100644 lib/web_ui/lib/src/engine/platform_dispatcher.dart create mode 100644 lib/web_ui/lib/src/ui/platform_dispatcher.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 72bfafac4c963..1211b443205b6 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -371,6 +371,7 @@ FILE: ../../../flutter/lib/ui/painting/single_frame_codec.h FILE: ../../../flutter/lib/ui/painting/vertices.cc FILE: ../../../flutter/lib/ui/painting/vertices.h FILE: ../../../flutter/lib/ui/painting/vertices_unittests.cc +FILE: ../../../flutter/lib/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/ui/plugins.dart FILE: ../../../flutter/lib/ui/plugins/callback_cache.cc FILE: ../../../flutter/lib/ui/plugins/callback_cache.h @@ -498,6 +499,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -552,6 +554,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path_metrics.dart +FILE: ../../../flutter/lib/web_ui/lib/src/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/pointer.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/test_embedding.dart diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index f484f0e53c521..266c1b2ba7300 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -10,8 +10,8 @@ part of dart.ui; /// /// To create a Scene object, use a [SceneBuilder]. /// -/// Scene objects can be displayed on the screen using the -/// [Window.render] method. +/// Scene objects can be displayed on the screen using the [FlutterView.render] +/// method. @pragma('vm:entry-point') class Scene extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated @@ -186,7 +186,7 @@ class PhysicalShapeEngineLayer extends _EngineLayerWrapper { /// Builds a [Scene] containing the given visuals. /// -/// A [Scene] can then be rendered using [Window.render]. +/// A [Scene] can then be rendered using [FlutterView.render]. /// /// To draw graphical operations onto a [Scene], first create a /// [Picture] using a [PictureRecorder] and a [Canvas], and then add @@ -655,13 +655,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// - 0x08: visualizeEngineStatistics - graph UI thread frame times /// Set enabledOptions to 0x0F to enable all the currently defined features. /// - /// The "UI thread" is the thread that includes all the execution of - /// the main Dart isolate (the isolate that can call - /// [Window.render]). The UI thread frame time is the total time - /// spent executing the [Window.onBeginFrame] callback. The "raster - /// thread" is the thread (running on the CPU) that subsequently - /// processes the [Scene] provided by the Dart code to turn it into - /// GPU commands and send it to the GPU. + /// The "UI thread" is the thread that includes all the execution of the main + /// Dart isolate (the isolate that can call [FlutterView.render]). The UI + /// thread frame time is the total time spent executing the + /// [PlatformDispatcher.onBeginFrame] callback. The "raster thread" is the + /// thread (running on the CPU) that subsequently processes the [Scene] + /// provided by the Dart code to turn it into GPU commands and send it to the + /// GPU. /// /// See also the [PerformanceOverlayOption] enum in the rendering library. /// for more details. @@ -802,7 +802,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// Returns a [Scene] containing the objects that have been added to /// this scene builder. The [Scene] can then be displayed on the - /// screen with [Window.render]. + /// screen with [FlutterView.render]. /// /// After calling this function, the scene builder object is invalid and /// cannot be used further. diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 96eac5885d083..46ac6efa534ca 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -42,9 +42,10 @@ Scene::Scene(std::shared_ptr rootLayer, uint32_t rasterizerTracingThreshold, bool checkerboardRasterCacheImages, bool checkerboardOffscreenLayers) { + // Currently only supports a single window. auto viewport_metrics = UIDartState::Current() ->platform_configuration() - ->window() + ->get_window(0) ->viewport_metrics(); layer_tree_ = std::make_unique( diff --git a/lib/ui/dart_ui.gni b/lib/ui/dart_ui.gni index 28cb82e9cd125..11fa74da53f84 100644 --- a/lib/ui/dart_ui.gni +++ b/lib/ui/dart_ui.gni @@ -13,6 +13,7 @@ dart_ui_files = [ "//flutter/lib/ui/lerp.dart", "//flutter/lib/ui/natives.dart", "//flutter/lib/ui/painting.dart", + "//flutter/lib/ui/platform_dispatcher.dart", "//flutter/lib/ui/plugins.dart", "//flutter/lib/ui/pointer.dart", "//flutter/lib/ui/semantics.dart", diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index a911d79f63a8d..3b8d66b8a46b4 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -11,7 +11,10 @@ part of dart.ui; @pragma('vm:entry-point') // ignore: unused_element void _updateWindowMetrics( + Object id, double devicePixelRatio, + double left, + double top, double width, double height, double viewPaddingTop, @@ -27,186 +30,98 @@ void _updateWindowMetrics( double systemGestureInsetBottom, double systemGestureInsetLeft, ) { - window - .._devicePixelRatio = devicePixelRatio - .._physicalSize = Size(width, height) - .._viewPadding = WindowPadding._( - top: viewPaddingTop, - right: viewPaddingRight, - bottom: viewPaddingBottom, - left: viewPaddingLeft) - .._viewInsets = WindowPadding._( - top: viewInsetTop, - right: viewInsetRight, - bottom: viewInsetBottom, - left: viewInsetLeft) - .._padding = WindowPadding._( - top: math.max(0.0, viewPaddingTop - viewInsetTop), - right: math.max(0.0, viewPaddingRight - viewInsetRight), - bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), - left: math.max(0.0, viewPaddingLeft - viewInsetLeft)) - .._systemGestureInsets = WindowPadding._( - top: math.max(0.0, systemGestureInsetTop), - right: math.max(0.0, systemGestureInsetRight), - bottom: math.max(0.0, systemGestureInsetBottom), - left: math.max(0.0, systemGestureInsetLeft)); - _invoke(window.onMetricsChanged, window._onMetricsChangedZone); + PlatformDispatcher.instance._updateWindowMetrics( + id, + devicePixelRatio, + left, + top, + width, + height, + viewPaddingTop, + viewPaddingRight, + viewPaddingBottom, + viewPaddingLeft, + viewInsetTop, + viewInsetRight, + viewInsetBottom, + viewInsetLeft, + systemGestureInsetTop, + systemGestureInsetRight, + systemGestureInsetBottom, + systemGestureInsetLeft, + ); } typedef _LocaleClosure = String? Function(); -String? _localeClosure() { - if (window.locale == null) { - return null; - } - return window.locale.toString(); -} - @pragma('vm:entry-point') // ignore: unused_element -_LocaleClosure? _getLocaleClosure() => _localeClosure; +_LocaleClosure? _getLocaleClosure() => PlatformDispatcher.instance._localeClosure; @pragma('vm:entry-point') // ignore: unused_element void _updateLocales(List locales) { - const int stringsPerLocale = 4; - final int numLocales = locales.length ~/ stringsPerLocale; - final List newLocales = []; - for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { - final String countryCode = locales[localeIndex * stringsPerLocale + 1]; - final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; - - newLocales.add(Locale.fromSubtags( - languageCode: locales[localeIndex * stringsPerLocale], - countryCode: countryCode.isEmpty ? null : countryCode, - scriptCode: scriptCode.isEmpty ? null : scriptCode, - )); - } - window._locales = newLocales; - _invoke(window.onLocaleChanged, window._onLocaleChangedZone); + PlatformDispatcher.instance._updateLocales(locales); } @pragma('vm:entry-point') // ignore: unused_element void _updateUserSettingsData(String jsonData) { - final Map data = json.decode(jsonData) as Map; - if (data.isEmpty) { - return; - } - _updateTextScaleFactor((data['textScaleFactor'] as num).toDouble()); - _updateAlwaysUse24HourFormat(data['alwaysUse24HourFormat'] as bool); - _updatePlatformBrightness(data['platformBrightness'] as String); + PlatformDispatcher.instance._updateUserSettingsData(jsonData); } @pragma('vm:entry-point') // ignore: unused_element void _updateLifecycleState(String state) { - // We do not update the state if the state has already been used to initialize - // the lifecycleState. - if (!window._initialLifecycleStateAccessed) - window._initialLifecycleState = state; -} - - -void _updateTextScaleFactor(double textScaleFactor) { - window._textScaleFactor = textScaleFactor; - _invoke(window.onTextScaleFactorChanged, window._onTextScaleFactorChangedZone); -} - -void _updateAlwaysUse24HourFormat(bool alwaysUse24HourFormat) { - window._alwaysUse24HourFormat = alwaysUse24HourFormat; -} - -void _updatePlatformBrightness(String brightnessName) { - window._platformBrightness = brightnessName == 'dark' ? Brightness.dark : Brightness.light; - _invoke(window.onPlatformBrightnessChanged, window._onPlatformBrightnessChangedZone); + PlatformDispatcher.instance._updateLifecycleState(state); } @pragma('vm:entry-point') // ignore: unused_element void _updateSemanticsEnabled(bool enabled) { - window._semanticsEnabled = enabled; - _invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone); + PlatformDispatcher.instance._updateSemanticsEnabled(enabled); } @pragma('vm:entry-point') // ignore: unused_element void _updateAccessibilityFeatures(int values) { - final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); - if (newFeatures == window._accessibilityFeatures) - return; - window._accessibilityFeatures = newFeatures; - _invoke(window.onAccessibilityFeaturesChanged, window._onAccessibilityFeaturesChangedZone); + PlatformDispatcher.instance._updateAccessibilityFeatures(values); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { - if (name == ChannelBuffers.kControlChannelName) { - try { - channelBuffers.handleMessage(data!); - } catch (ex) { - _printDebug('Message to "$name" caused exception $ex'); - } finally { - window._respondToPlatformMessage(responseId, null); - } - } else if (window.onPlatformMessage != null) { - _invoke3( - window.onPlatformMessage, - window._onPlatformMessageZone, - name, - data, - (ByteData? responseData) { - window._respondToPlatformMessage(responseId, responseData); - }, - ); - } else { - channelBuffers.push(name, data, (ByteData? responseData) { - window._respondToPlatformMessage(responseId, responseData); - }); - } + PlatformDispatcher.instance._dispatchPlatformMessage(name, data, responseId); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPointerDataPacket(ByteData packet) { - if (window.onPointerDataPacket != null) - _invoke1(window.onPointerDataPacket, window._onPointerDataPacketZone, _unpackPointerDataPacket(packet)); + PlatformDispatcher.instance._dispatchPointerDataPacket(packet); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchSemanticsAction(int id, int action, ByteData? args) { - _invoke3( - window.onSemanticsAction, - window._onSemanticsActionZone, - id, - SemanticsAction.values[action]!, - args, - ); + PlatformDispatcher.instance._dispatchSemanticsAction(id, action, args); } @pragma('vm:entry-point') // ignore: unused_element void _beginFrame(int microseconds) { - _invoke1(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds)); + PlatformDispatcher.instance._beginFrame(microseconds); } @pragma('vm:entry-point') // ignore: unused_element void _reportTimings(List timings) { - assert(timings.length % FramePhase.values.length == 0); - final List frameTimings = []; - for (int i = 0; i < timings.length; i += FramePhase.values.length) { - frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); - } - _invoke1(window.onReportTimings, window._onReportTimingsZone, frameTimings); + PlatformDispatcher.instance._reportTimings(timings); } @pragma('vm:entry-point') // ignore: unused_element void _drawFrame() { - _invoke(window.onDrawFrame, window._onDrawFrameZone); + PlatformDispatcher.instance._drawFrame(); } // ignore: always_declare_return_types, prefer_generic_function_type_aliases @@ -219,7 +134,7 @@ typedef _BinaryFunction(Null args, Null message); void _runMainZoned(Function startMainIsolateFunction, Function userMainFunction, List args) { - startMainIsolateFunction((){ + startMainIsolateFunction(() { runZonedGuarded(() { if (userMainFunction is _BinaryFunction) { // This seems to be undocumented but supported by the command line VM. @@ -239,9 +154,10 @@ void _runMainZoned(Function startMainIsolateFunction, void _reportUnhandledException(String error, String stackTrace) native 'PlatformConfiguration_reportUnhandledException'; /// Invokes [callback] inside the given [zone]. -void _invoke(void callback()?, Zone zone) { - if (callback == null) +void _invoke(void Function()? callback, Zone zone) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -253,9 +169,10 @@ void _invoke(void callback()?, Zone zone) { } /// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1(void callback(A a)?, Zone zone, A arg) { - if (callback == null) +void _invoke1(void Function(A a)? callback, Zone zone, A arg) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -267,9 +184,16 @@ void _invoke1(void callback(A a)?, Zone zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg1, A2 arg2, A3 arg3) { - if (callback == null) +void _invoke3( + void Function(A1 a1, A2 a2, A3 a3)? callback, + Zone zone, + A1 arg1, + A2 arg2, + A3 arg3, +) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -281,54 +205,3 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg }); } } - -// If this value changes, update the encoding code in the following files: -// -// * pointer_data.cc -// * pointer.dart -// * AndroidTouchProcessor.java -const int _kPointerDataFieldCount = 29; - -PointerDataPacket _unpackPointerDataPacket(ByteData packet) { - const int kStride = Int64List.bytesPerElement; - const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; - final int length = packet.lengthInBytes ~/ kBytesPerPointerData; - assert(length * kBytesPerPointerData == packet.lengthInBytes); - final List data = []; - for (int i = 0; i < length; ++i) { - int offset = i * _kPointerDataFieldCount; - data.add(PointerData( - embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), - timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), - change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - device: packet.getInt64(kStride * offset++, _kFakeHostEndian), - pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), - physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), - obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), - scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian) - )); - assert(offset == (i + 1) * _kPointerDataFieldCount); - } - return PointerDataPacket(data: data); -} diff --git a/lib/ui/natives.dart b/lib/ui/natives.dart index 0f2939592add1..13838cb1a512b 100644 --- a/lib/ui/natives.dart +++ b/lib/ui/natives.dart @@ -34,7 +34,7 @@ Future _scheduleFrame( Map parameters ) async { // Schedule the frame. - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); // Always succeed. return developer.ServiceExtensionResponse.result(json.encode({ 'type': 'Success', diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 68ae4898c176a..575e752eeee24 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -468,7 +468,7 @@ void Canvas::drawShadow(const CanvasPath* path, } SkScalar dpr = UIDartState::Current() ->platform_configuration() - ->window() + ->get_window(0) ->viewport_metrics() .device_pixel_ratio; flutter::PhysicalShapeLayer::DrawShadow(canvas_, path->path(), color, diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart new file mode 100644 index 0000000000000..e829e9ca3b27d --- /dev/null +++ b/lib/ui/platform_dispatcher.dart @@ -0,0 +1,1556 @@ +// 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. + +// @dart = 2.10 +part of dart.ui; + +/// Signature of callbacks that have no arguments and return no data. +typedef VoidCallback = void Function(); + +/// Signature for [PlatformDispatcher.onBeginFrame]. +typedef FrameCallback = void Function(Duration duration); + +/// Signature for [PlatformDispatcher.onReportTimings]. +/// +/// {@template dart.ui.TimingsCallback.list} +/// The callback takes a list of [FrameTiming] because it may not be +/// immediately triggered after each frame. Instead, Flutter tries to batch +/// frames together and send all their timings at once to decrease the +/// overhead (as this is available in the release mode). The list is sorted in +/// ascending order of time (earliest frame first). The timing of any frame +/// will be sent within about 1 second (100ms if in the profile/debug mode) +/// even if there are no later frames to batch. The timing of the first frame +/// will be sent immediately without batching. +/// {@endtemplate} +typedef TimingsCallback = void Function(List timings); + +/// Signature for [PlatformDispatcher.onPointerDataPacket]. +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); + +/// Signature for [PlatformDispatcher.onSemanticsAction]. +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); + +/// Signature for responses to platform messages. +/// +/// Used as a parameter to [PlatformDispatcher.sendPlatformMessage] and +/// [PlatformDispatcher.onPlatformMessage]. +typedef PlatformMessageResponseCallback = void Function(ByteData? data); + +/// Signature for [PlatformDispatcher.onPlatformMessage]. +typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); + +// Signature for _setNeedsReportTimings. +typedef _SetNeedsReportTimingsFunc = void Function(bool value); + +/// Signature for [PlatformDispatcher.onConfigurationChanged]. +typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); + +/// Platform event dispatcher singleton. +/// +/// The most basic interface to the host operating system's interface. +/// +/// This is the central entry point for platform messages and configuration +/// events from the platform. +/// +/// It exposes the core scheduler API, the input event callback, the graphics +/// drawing API, and other such core services. +/// +/// It manages the list of the application's [views] and the [screens] attached +/// to the device, as well as the [configuration] of various platform +/// attributes. +/// +/// Consider avoiding static references to this singleton through +/// [PlatformDispatcher.instance] and instead prefer using a binding for +/// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. +/// See [PlatformDispatcher.instance] for more information about why this is +/// preferred. +class PlatformDispatcher { + /// Private constructor, since only dart:ui is supposed to create one of + /// these. Use [instance] to access the singleton. + PlatformDispatcher._() { + _setNeedsReportTimings = _nativeSetNeedsReportTimings; + } + + /// The [PlatformDispatcher] singleton. + /// + /// Consider avoiding static references to this singleton though + /// [PlatformDispatcher.instance] and instead prefer using a binding for + /// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. + /// + /// Static access of this object means that Flutter has few, if any options to + /// fake or mock the given object in tests. Even in cases where Dart offers + /// special language constructs to forcefully shadow such properties, those + /// mechanisms would only be reasonable for tests and they would not be + /// reasonable for a future of Flutter where we legitimately want to select an + /// appropriate implementation at runtime. + /// + /// The only place that `WidgetsBinding.instance.platformDispatcher` is + /// inappropriate is if access to these APIs is required before the binding is + /// initialized by invoking `runApp()` or + /// `WidgetsFlutterBinding.instance.ensureInitialized()`. In that case, it is + /// necessary (though unfortunate) to use the [PlatformDispatcher.instance] + /// object statically. + static PlatformDispatcher get instance => _instance; + static final PlatformDispatcher _instance = PlatformDispatcher._(); + + /// The current platform configuration. + /// + /// If values in this configuration change, [onPlatformConfigurationChanged] + /// will be called. + PlatformConfiguration get configuration => _configuration; + PlatformConfiguration _configuration = const PlatformConfiguration(); + + /// Called when the platform configuration changes. + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; + VoidCallback? _onPlatformConfigurationChanged; + Zone _onPlatformConfigurationChangedZone = Zone.root; + set onPlatformConfigurationChanged(VoidCallback? callback) { + _onPlatformConfigurationChanged = callback; + _onPlatformConfigurationChangedZone = Zone.current; + } + + /// The current list of views, including top level platform windows used by + /// the application. + /// + /// If any of their configurations change, [onMetricsChanged] will be called. + Iterable get views => _views.values; + Map _views = {}; + + // A map of opaque platform view identifiers to view configurations. + Map _viewConfigurations = {}; + + /// A callback that is invoked whenever the [ViewConfiguration] of any of the + /// [views] changes. + /// + /// For example when the device is rotated or when the application is resized + /// (e.g. when showing applications side-by-side on Android), + /// `onMetricsChanged` is called. + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + /// + /// The framework registers with this callback and updates the layout + /// appropriately. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// register for notifications when this is called. + /// * [MediaQuery.of], a simpler mechanism for the same. + VoidCallback? get onMetricsChanged => _onMetricsChanged; + VoidCallback? _onMetricsChanged; + Zone _onMetricsChangedZone = Zone.root; + set onMetricsChanged(VoidCallback? callback) { + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + // + // Updates the metrics of the window with the given id. + void _updateWindowMetrics( + Object id, + double devicePixelRatio, + double left, + double top, + double width, + double height, + double viewPaddingTop, + double viewPaddingRight, + double viewPaddingBottom, + double viewPaddingLeft, + double viewInsetTop, + double viewInsetRight, + double viewInsetBottom, + double viewInsetLeft, + double systemGestureInsetTop, + double systemGestureInsetRight, + double systemGestureInsetBottom, + double systemGestureInsetLeft, + ) { + final ViewConfiguration previousConfiguration = + _viewConfigurations[id] ?? const ViewConfiguration(); + if (!_views.containsKey(id)) { + _views[id] = FlutterWindow._(id, this); + } + _viewConfigurations[id] = previousConfiguration.copyWith( + window: _views[id], + devicePixelRatio: devicePixelRatio, + geometry: Rect.fromLTWH(left, top, width, height), + viewPadding: WindowPadding._( + top: viewPaddingTop, + right: viewPaddingRight, + bottom: viewPaddingBottom, + left: viewPaddingLeft, + ), + viewInsets: WindowPadding._( + top: viewInsetTop, + right: viewInsetRight, + bottom: viewInsetBottom, + left: viewInsetLeft, + ), + padding: WindowPadding._( + top: math.max(0.0, viewPaddingTop - viewInsetTop), + right: math.max(0.0, viewPaddingRight - viewInsetRight), + bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), + left: math.max(0.0, viewPaddingLeft - viewInsetLeft), + ), + systemGestureInsets: WindowPadding._( + top: math.max(0.0, systemGestureInsetTop), + right: math.max(0.0, systemGestureInsetRight), + bottom: math.max(0.0, systemGestureInsetBottom), + left: math.max(0.0, systemGestureInsetLeft), + ), + ); + _invoke(onMetricsChanged, _onMetricsChangedZone); + } + + /// A callback invoked when any view begins a frame. + /// + /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} + /// A callback that is invoked to notify the application that it is an + /// appropriate time to provide a scene using the [SceneBuilder] API and the + /// [FlutterView.render] method. + /// + /// When possible, this is driven by the hardware VSync signal of the attached + /// screen with the highest VSync rate. This is only called if + /// [PlatformDispatcher.scheduleFrame] has been called since the last time + /// this callback was invoked. + /// {@endtemplate} + FrameCallback? get onBeginFrame => _onBeginFrame; + FrameCallback? _onBeginFrame; + Zone _onBeginFrameZone = Zone.root; + set onBeginFrame(FrameCallback? callback) { + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _beginFrame(int microseconds) { + _invoke1( + onBeginFrame, + _onBeginFrameZone, + Duration(microseconds: microseconds), + ); + } + + /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} + /// A callback that is invoked for each frame after [onBeginFrame] has + /// completed and after the microtask queue has been drained. + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. + /// {@endtemplate} + VoidCallback? get onDrawFrame => _onDrawFrame; + VoidCallback? _onDrawFrame; + Zone _onDrawFrameZone = Zone.root; + set onDrawFrame(VoidCallback? callback) { + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _drawFrame() { + _invoke(onDrawFrame, _onDrawFrameZone); + } + + /// A callback that is invoked when pointer data is available. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [GestureBinding], the Flutter framework class which manages pointer + /// events. + PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + PointerDataPacketCallback? _onPointerDataPacket; + Zone _onPointerDataPacketZone = Zone.root; + set onPointerDataPacket(PointerDataPacketCallback? callback) { + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _dispatchPointerDataPacket(ByteData packet) { + if (onPointerDataPacket != null) { + _invoke1( + onPointerDataPacket, + _onPointerDataPacketZone, + _unpackPointerDataPacket(packet), + ); + } + } + + // If this value changes, update the encoding code in the following files: + // + // * pointer_data.cc + // * pointer.dart + // * AndroidTouchProcessor.java + static const int _kPointerDataFieldCount = 29; + + static PointerDataPacket _unpackPointerDataPacket(ByteData packet) { + const int kStride = Int64List.bytesPerElement; + const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; + final int length = packet.lengthInBytes ~/ kBytesPerPointerData; + assert(length * kBytesPerPointerData == packet.lengthInBytes); + final List data = []; + for (int i = 0; i < length; ++i) { + int offset = i * _kPointerDataFieldCount; + data.add(PointerData( + embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), + timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), + change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + device: packet.getInt64(kStride * offset++, _kFakeHostEndian), + pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), + physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), + obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), + scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + )); + assert(offset == (i + 1) * _kPointerDataFieldCount); + } + return PointerDataPacket(data: data); + } + + /// A callback that is invoked to report the [FrameTiming] of recently + /// rasterized frames. + /// + /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use + /// [onReportTimings] directly because [SchedulerBinding.addTimingsCallback] + /// allows multiple callbacks. + /// + /// This can be used to see if the application has missed frames (through + /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high + /// latencies (through [FrameTiming.totalSpan]). + /// + /// Unlike [Timeline], the timing information here is available in the release + /// mode (additional to the profile and the debug mode). Hence this can be + /// used to monitor the application's performance in the wild. + /// + /// {@macro dart.ui.TimingsCallback.list} + /// + /// If this is null, no additional work will be done. If this is not null, + /// Flutter spends less than 0.1ms every 1 second to report the timings + /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for + /// 60fps), or 0.01% CPU usage per second. + TimingsCallback? get onReportTimings => _onReportTimings; + TimingsCallback? _onReportTimings; + Zone _onReportTimingsZone = Zone.root; + set onReportTimings(TimingsCallback? callback) { + if ((callback == null) != (_onReportTimings == null)) { + _setNeedsReportTimings(callback != null); + } + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; + } + + late _SetNeedsReportTimingsFunc _setNeedsReportTimings; + void _nativeSetNeedsReportTimings(bool value) + native 'PlatformConfiguration_setNeedsReportTimings'; + + // Called from the engine, via hooks.dart + void _reportTimings(List timings) { + assert(timings.length % FramePhase.values.length == 0); + final List frameTimings = []; + for (int i = 0; i < timings.length; i += FramePhase.values.length) { + frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); + } + _invoke1(onReportTimings, _onReportTimingsZone, frameTimings); + } + + /// Sends a message to a platform-specific plugin. + /// + /// The `name` parameter determines which plugin receives the message. The + /// `data` parameter contains the message payload and is typically UTF-8 + /// encoded JSON but can be arbitrary data. If the plugin replies to the + /// message, `callback` will be called with the response. + /// + /// The framework invokes [callback] in the same zone in which this method was + /// called. + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { + final String? error = + _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); + if (error != null) + throw Exception(error); + } + + String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data) + native 'PlatformConfiguration_sendPlatformMessage'; + + /// Called whenever this platform dispatcher receives a message from a + /// platform-specific plugin. + /// + /// The `name` parameter determines which plugin sent the message. The `data` + /// parameter is the payload and is typically UTF-8 encoded JSON but can be + /// arbitrary data. + /// + /// Message handlers must call the function given in the `callback` parameter. + /// If the handler does not need to respond, the handler should pass null to + /// the callback. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + PlatformMessageCallback? _onPlatformMessage; + Zone _onPlatformMessageZone = Zone.root; + set onPlatformMessage(PlatformMessageCallback? callback) { + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; + } + + /// Called by [_dispatchPlatformMessage]. + void _respondToPlatformMessage(int responseId, ByteData? data) + native 'PlatformConfiguration_respondToPlatformMessage'; + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback( + PlatformMessageResponseCallback? callback, + ) { + if (callback == null) { + return null; + } + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + // Called from the engine, via hooks.dart + void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { + if (name == ChannelBuffers.kControlChannelName) { + try { + channelBuffers.handleMessage(data!); + } catch (ex) { + _printDebug('Message to "$name" caused exception $ex'); + } finally { + _respondToPlatformMessage(responseId, null); + } + } else if (onPlatformMessage != null) { + _invoke3( + onPlatformMessage, + _onPlatformMessageZone, + name, + data, + (ByteData? responseData) { + _respondToPlatformMessage(responseId, responseData); + }, + ); + } else { + channelBuffers.push(name, data, (ByteData? responseData) { + _respondToPlatformMessage(responseId, responseData); + }); + } + } + + /// Set the debug name associated with this platform dispatcher's root + /// isolate. + /// + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. + /// + /// This can be combined with flutter tools `--isolate-filter` flag to debug + /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. + /// Note that this does not rename any child isolates of the root. + void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; + + /// The embedder can specify data that the isolate can request synchronously + /// on launch. This accessor fetches that data. + /// + /// This data is persistent for the duration of the Flutter application and is + /// available even after isolate restarts. Because of this lifecycle, the size + /// of this data must be kept to a minimum. + /// + /// For asynchronous communication between the embedder and isolate, a + /// platform channel may be used. + ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and + /// [onDrawFrame] callbacks be invoked. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; + + /// Additional accessibility features that may be enabled by the platform. + AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; + + /// A callback that is invoked when the value of [accessibilityFeatures] + /// changes. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; + VoidCallback? _onAccessibilityFeaturesChanged; + Zone _onAccessibilityFeaturesChangedZone = Zone.root; + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateAccessibilityFeatures(int values) { + final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); + final PlatformConfiguration previousConfiguration = configuration; + if (newFeatures == previousConfiguration.accessibilityFeatures) { + return; + } + _configuration = previousConfiguration.copyWith( + accessibilityFeatures: newFeatures, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone,); + _invoke(onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone,); + } + + /// Change the retained semantics data about this platform dispatcher. + /// + /// If [semanticsEnabled] is true, the user has requested that this function + /// be called whenever the semantic content of this platform dispatcher + /// changes. + /// + /// In either case, this function disposes the given update, which means the + /// semantics update cannot be used further. + void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; + + /// The system-reported default locale of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// This is the first locale selected by the user and is the user's primary + /// locale (the locale the device UI is displayed in) + /// + /// This is equivalent to `locales.first` and will provide an empty non-null + /// locale if the [locales] list has not been set or is empty. + Locale? get locale { + if (locales != null && locales!.isNotEmpty) { + return locales!.first; + } + return null; + } + + /// The full system-reported supported locales of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// The list is ordered in order of priority, with lower-indexed locales being + /// preferred over higher-indexed ones. The first element is the primary + /// [locale]. + /// + /// The [onLocaleChanged] callback is called whenever this value changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + List? get locales => configuration.locales; + + /// Performs the platform-native locale resolution. + /// + /// Each platform may return different results. + /// + /// If the platform fails to resolve a locale, then this will return null. + /// + /// This method returns synchronously and is a direct call to + /// platform specific APIs without invoking method channels. + Locale? computePlatformResolvedLocale(List supportedLocales) { + final List supportedLocalesData = []; + for (Locale locale in supportedLocales) { + supportedLocalesData.add(locale.languageCode); + supportedLocalesData.add(locale.countryCode); + supportedLocalesData.add(locale.scriptCode); + } + + final List result = _computePlatformResolvedLocale(supportedLocalesData); + + if (result.isNotEmpty) { + return Locale.fromSubtags( + languageCode: result[0], + countryCode: result[1] == '' ? null : result[1], + scriptCode: result[2] == '' ? null : result[2]); + } + return null; + } + List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; + + /// A callback that is invoked whenever [locale] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onLocaleChanged => _onLocaleChanged; + VoidCallback? _onLocaleChanged; + Zone _onLocaleChangedZone = Zone.root; // ignore: unused_field + set onLocaleChanged(VoidCallback? callback) { + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateLocales(List locales) { + const int stringsPerLocale = 4; + final int numLocales = locales.length ~/ stringsPerLocale; + final PlatformConfiguration previousConfiguration = configuration; + final List newLocales = []; + bool localesDiffer = numLocales != previousConfiguration.locales.length; + for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { + final String countryCode = locales[localeIndex * stringsPerLocale + 1]; + final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; + + newLocales.add(Locale.fromSubtags( + languageCode: locales[localeIndex * stringsPerLocale], + countryCode: countryCode.isEmpty ? null : countryCode, + scriptCode: scriptCode.isEmpty ? null : scriptCode, + )); + if (!localesDiffer && newLocales[localeIndex] != previousConfiguration.locales[localeIndex]) { + localesDiffer = true; + } + } + if (!localesDiffer) { + return; + } + _configuration = previousConfiguration.copyWith(locales: newLocales); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + _invoke(onLocaleChanged, _onLocaleChangedZone); + } + + // Called from the engine, via hooks.dart + String? _localeClosure() { + if (locale == null) { + return null; + } + return locale.toString(); + } + + /// The lifecycle state immediately after dart isolate initialization. + /// + /// This property will not be updated as the lifecycle changes. + /// + /// It is used to initialize [SchedulerBinding.lifecycleState] at startup with + /// any buffered lifecycle state events. + String get initialLifecycleState { + _initialLifecycleStateAccessed = true; + return _initialLifecycleState; + } + + late String _initialLifecycleState; + + /// Tracks if the initial state has been accessed. Once accessed, we will stop + /// updating the [initialLifecycleState], as it is not the preferred way to + /// access the state. + bool _initialLifecycleStateAccessed = false; + + // Called from the engine, via hooks.dart + void _updateLifecycleState(String state) { + // We do not update the state if the state has already been used to initialize + // the lifecycleState. + if (!_initialLifecycleStateAccessed) + _initialLifecycleState = state; + } + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + /// + /// This option is used by [showTimePicker]. + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + /// The system-reported text scale. + /// + /// This establishes the text scaling factor to use when rendering text, + /// according to the user's platform preferences. + /// + /// The [onTextScaleFactorChanged] callback is called whenever this value + /// changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + double get textScaleFactor => configuration.textScaleFactor; + + /// A callback that is invoked whenever [textScaleFactor] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + VoidCallback? _onTextScaleFactorChanged; + Zone _onTextScaleFactorChangedZone = Zone.root; + set onTextScaleFactorChanged(VoidCallback? callback) { + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; + } + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + Brightness get platformBrightness => configuration.platformBrightness; + + /// A callback that is invoked whenever [platformBrightness] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; + VoidCallback? _onPlatformBrightnessChanged; + Zone _onPlatformBrightnessChangedZone = Zone.root; + set onPlatformBrightnessChanged(VoidCallback? callback) { + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateUserSettingsData(String jsonData) { + final Map data = json.decode(jsonData) as Map; + if (data.isEmpty) { + return; + } + + final double textScaleFactor = (data['textScaleFactor'] as num).toDouble(); + final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat'] as bool; + final Brightness platformBrightness = + data['platformBrightness'] as String == 'dark' ? Brightness.dark : Brightness.light; + final PlatformConfiguration previousConfiguration = configuration; + final bool platformBrightnessChanged = + previousConfiguration.platformBrightness != platformBrightness; + final bool textScaleFactorChanged = previousConfiguration.textScaleFactor != textScaleFactor; + final bool alwaysUse24HourFormatChanged = + previousConfiguration.alwaysUse24HourFormat != alwaysUse24HourFormat; + if (!platformBrightnessChanged && !textScaleFactorChanged && !alwaysUse24HourFormatChanged) { + return; + } + _configuration = previousConfiguration.copyWith( + textScaleFactor: textScaleFactor, + alwaysUse24HourFormat: alwaysUse24HourFormat, + platformBrightness: platformBrightness, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + if (textScaleFactorChanged) { + _invoke(onTextScaleFactorChanged, _onTextScaleFactorChangedZone); + } + if (platformBrightnessChanged) { + _invoke(onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); + } + } + + /// Whether the user has requested that [updateSemantics] be called when the + /// semantic contents of a view changes. + /// + /// The [onSemanticsEnabledChanged] callback is called whenever this value + /// changes. + bool get semanticsEnabled => configuration.semanticsEnabled; + + /// A callback that is invoked when the value of [semanticsEnabled] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + VoidCallback? _onSemanticsEnabledChanged; + Zone _onSemanticsEnabledChangedZone = Zone.root; + set onSemanticsEnabledChanged(VoidCallback? callback) { + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateSemanticsEnabled(bool enabled) { + final PlatformConfiguration previousConfiguration = configuration; + if (previousConfiguration.semanticsEnabled == enabled) { + return; + } + _configuration = previousConfiguration.copyWith( + semanticsEnabled: enabled, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + _invoke(onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); + } + + /// A callback that is invoked whenever the user requests an action to be + /// performed. + /// + /// This callback is used when the user expresses the action they wish to + /// perform based on the semantics supplied by [updateSemantics]. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + SemanticsActionCallback? _onSemanticsAction; + Zone _onSemanticsActionZone = Zone.root; + set onSemanticsAction(SemanticsActionCallback? callback) { + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _dispatchSemanticsAction(int id, int action, ByteData? args) { + _invoke3( + onSemanticsAction, + _onSemanticsActionZone, + id, + SemanticsAction.values[action]!, + args, + ); + } + + /// The route or path that the embedder requested when the application was + /// launched. + /// + /// This will be the string "`/`" if no particular route was requested. + /// + /// ## Android + /// + /// On Android, calling + /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `createFlutterView` method in your `FlutterActivity` + /// subclass is a suitable time to set the value. The application's + /// `AndroidManifest.xml` file must also be updated to have a suitable + /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). + /// + /// ## iOS + /// + /// On iOS, calling + /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `application:didFinishLaunchingWithOptions:` method is a + /// suitable time to set this value. + /// + /// See also: + /// + /// * [Navigator], a widget that handles routing. + /// * [SystemChannels.navigation], which handles subsequent navigation + /// requests from the embedder. + String get defaultRouteName => _defaultRouteName(); + String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; +} + +/// Configuration of the platform. +/// +/// Immutable class (but can't use @immutable in dart:ui) +class PlatformConfiguration { + /// Const constructor for [PlatformConfiguration]. + const PlatformConfiguration({ + this.accessibilityFeatures = const AccessibilityFeatures._(0), + this.alwaysUse24HourFormat = false, + this.semanticsEnabled = false, + this.platformBrightness = Brightness.light, + this.textScaleFactor = 1.0, + this.locales = const [], + this.defaultRouteName, + }); + + /// Copy a [PlatformConfiguration] with some fields replaced. + PlatformConfiguration copyWith({ + AccessibilityFeatures? accessibilityFeatures, + bool? alwaysUse24HourFormat, + bool? semanticsEnabled, + Brightness? platformBrightness, + double? textScaleFactor, + List? locales, + String? defaultRouteName, + }) { + return PlatformConfiguration( + accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, + alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, + semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, + platformBrightness: platformBrightness ?? this.platformBrightness, + textScaleFactor: textScaleFactor ?? this.textScaleFactor, + locales: locales ?? this.locales, + defaultRouteName: defaultRouteName ?? this.defaultRouteName, + ); + } + + /// Additional accessibility features that may be enabled by the platform. + final AccessibilityFeatures accessibilityFeatures; + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + final bool alwaysUse24HourFormat; + + /// Whether the user has requested that [updateSemantics] be called when the + /// semantic contents of a view changes. + final bool semanticsEnabled; + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + final Brightness platformBrightness; + + /// The system-reported text scale. + final double textScaleFactor; + + /// The full system-reported supported locales of the device. + final List locales; + + /// The route or path that the embedder requested when the application was + /// launched. + final String? defaultRouteName; +} + +/// An immutable view configuration. +class ViewConfiguration { + /// A const constructor for an immutable [ViewConfiguration]. + const ViewConfiguration({ + this.window, + this.devicePixelRatio = 1.0, + this.geometry = Rect.zero, + this.visible = false, + this.viewInsets = WindowPadding.zero, + this.viewPadding = WindowPadding.zero, + this.systemGestureInsets = WindowPadding.zero, + this.padding = WindowPadding.zero, + }); + + /// Copy this configuration with some fields replaced. + ViewConfiguration copyWith({ + FlutterView? window, + double? devicePixelRatio, + Rect? geometry, + bool? visible, + WindowPadding? viewInsets, + WindowPadding? viewPadding, + WindowPadding? systemGestureInsets, + WindowPadding? padding, + }) { + return ViewConfiguration( + window: window ?? this.window, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, + geometry: geometry ?? this.geometry, + visible: visible ?? this.visible, + viewInsets: viewInsets ?? this.viewInsets, + viewPadding: viewPadding ?? this.viewPadding, + systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, + padding: padding ?? this.padding, + ); + } + + /// The top level view into which the view is placed and its geometry is + /// relative to. + /// + /// If null, then this configuration represents a top level view itself. + final FlutterView? window; + + /// The pixel density of the output surface. + final double devicePixelRatio; + + /// The geometry requested for the view on the screen or within its parent + /// window, in logical pixels. + final Rect geometry; + + /// Whether or not the view is currently visible on the screen. + final bool visible; + + /// The view insets, as it intersects with [Screen.viewInsets] for the screen + /// it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.viewInsets] area, [viewInsets] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this view rectangle into + /// which the application can draw, but over which the operating system will + /// likely place system UI, such as the keyboard or system menus, that fully + /// obscures any content. + final WindowPadding viewInsets; + + /// The view insets, as it intersects with [ScreenConfiguration.viewPadding] + /// for the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.viewPadding] area, [viewPadding] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but which may be partially + /// obscured by system UI (such as the system notification area), or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). + final WindowPadding viewPadding; + + /// The view insets, as it intersects with + /// [ScreenConfiguration.systemGestureInsets] for the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.systemGestureInsets] area, [systemGestureInsets] will + /// be [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but where the operating system + /// will consume input gestures for the sake of system navigation. + final WindowPadding systemGestureInsets; + + /// The view insets, as it intersects with [ScreenConfiguration.padding] for + /// the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.padding] area, [padding] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but which may be partially + /// obscured by system UI (such as the system notification area), or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). + final WindowPadding padding; + + @override + String toString() { + return '$runtimeType[window: $window, geometry: $geometry]'; + } +} + +/// Various important time points in the lifetime of a frame. +/// +/// [FrameTiming] records a timestamp of each phase for performance analysis. +enum FramePhase { + /// The timestamp of the vsync signal given by the operating system. + /// + /// See also [FrameTiming.vsyncOverhead]. + vsyncStart, + + /// When the UI thread starts building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildStart, + + /// When the UI thread finishes building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildFinish, + + /// When the raster thread starts rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterStart, + + /// When the raster thread finishes rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterFinish, +} + +/// Time-related performance metrics of a frame. +/// +/// If you're using the whole Flutter framework, please use +/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using +/// [PlatformDispatcher.onReportTimings] directly because +/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If +/// [SchedulerBinding] is unavailable, then see [PlatformDispatcher.onReportTimings] +/// for how to get this. +/// +/// The metrics in debug mode (`flutter run` without any flags) may be very +/// different from those in profile and release modes due to the debug overhead. +/// Therefore it's recommended to only monitor and analyze performance metrics +/// in profile and release modes. +class FrameTiming { + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// This constructor is used for unit test only. Real [FrameTiming]s should + /// be retrieved from [PlatformDispatcher.onReportTimings]. + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// List [timestamps] must have the same number of elements as + /// [FramePhase.values]. + /// + /// This constructor is usually only called by the Flutter engine, or a test. + /// To get the [FrameTiming] of your app, see [PlatformDispatcher.onReportTimings]. + FrameTiming._(this._timestamps) + : assert(_timestamps.length == FramePhase.values.length); + + /// This is a raw timestamp in microseconds from some epoch. The epoch in all + /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + + /// The duration to build the frame on the UI thread. + /// + /// The build starts approximately when [PlatformDispatcher.onBeginFrame] is + /// called. The [Duration] in the [PlatformDispatcher.onBeginFrame] callback + /// is exactly the `Duration(microseconds: + /// timestampInMicroseconds(FramePhase.buildStart))`. + /// + /// The build finishes when [FlutterView.render] is called. + /// + /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// To ensure smooth animations of X fps, this should not exceed 1000/X + /// milliseconds. + /// {@endtemplate} + /// {@template dart.ui.FrameTiming.fps_milliseconds} + /// That's about 16ms for 60fps, and 8ms for 120fps. + /// {@endtemplate} + Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + + /// The duration to rasterize the frame on the raster thread. + /// + /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + + /// The duration between receiving the vsync signal and starting building the + /// frame. + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + + /// The timespan between vsync start and raster finish. + /// + /// To achieve the lowest latency on an X fps display, this should not exceed + /// 1000/X milliseconds. + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + /// + /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. + Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + +/// States that an application can be in. +/// +/// The values below describe notifications from the operating system. +/// Applications should not expect to always receive all possible +/// notifications. For example, if the users pulls out the battery from the +/// device, no notification will be sent before the application is suddenly +/// terminated, along with the rest of the operating system. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state +/// from the widgets layer. +enum AppLifecycleState { + /// The application is visible and responding to user input. + resumed, + + /// The application is in an inactive state and is not receiving user input. + /// + /// On iOS, this state corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when in + /// a phone call, responding to a TouchID request, when entering the app + /// switcher or the control center, or when the UIViewController hosting the + /// Flutter app is transitioning. + /// + /// On Android, this corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when + /// another activity is focused, such as a split-screen app, a phone call, + /// a picture-in-picture app, a system dialog, or another window. + /// + /// Apps in this state should assume that they may be [paused] at any time. + inactive, + + /// The application is not currently visible to the user, not responding to + /// user input, and running in the background. + /// + /// When the application is in this state, the engine will not call the + /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame] + /// callbacks. + paused, + + /// The application is still hosted on a flutter engine but is detached from + /// any host views. + /// + /// When the application is in this state, the engine is running without + /// a view. It can either be in the progress of attaching a view when engine + /// was first initializes, or after the view being destroyed due to a Navigator + /// pop. + detached, +} + +/// A representation of distances for each of the four edges of a rectangle, +/// used to encode the view insets and padding that applications should place +/// around their user interface, as exposed by [FlutterView.viewInsets] and +/// [FlutterView.padding]. View insets and padding are preferably read via +/// [MediaQuery.of]. +/// +/// For a generic class that represents distances around a rectangle, see the +/// [EdgeInsets] class. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive +/// notifications when the padding changes. +/// * [MediaQuery.of], for the preferred mechanism for accessing these values. +/// * [Scaffold], which automatically applies the padding in material design +/// applications. +class WindowPadding { + const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); + + /// The distance from the left edge to the first unpadded pixel, in physical pixels. + final double left; + + /// The distance from the top edge to the first unpadded pixel, in physical pixels. + final double top; + + /// The distance from the right edge to the first unpadded pixel, in physical pixels. + final double right; + + /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. + final double bottom; + + /// A window padding that has zeros for each edge. + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); + + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +/// An identifier used to select a user's language and formatting preferences. +/// +/// This represents a [Unicode Language +/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) +/// (i.e. without Locale extensions), except variants are not supported. +/// +/// Locales are canonicalized according to the "preferred value" entries in the +/// [IANA Language Subtag +/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). +/// For example, `const Locale('he')` and `const Locale('iw')` are equal and +/// both have the [languageCode] `he`, because `iw` is a deprecated language +/// subtag that was replaced by the subtag `he`. +/// +/// See also: +/// +/// * [PlatformDispatcher.locale], which specifies the system's currently selected +/// [Locale]. +class Locale { + /// Creates a new Locale object. The first argument is the + /// primary language subtag, the second is the region (also + /// referred to as 'country') subtag. + /// + /// For example: + /// + /// ```dart + /// const Locale swissFrench = Locale('fr', 'CH'); + /// const Locale canadianFrench = Locale('fr', 'CA'); + /// ``` + /// + /// The primary language subtag must not be null. The region subtag is + /// optional. When there is no region/country subtag, the parameter should + /// be omitted or passed `null` instead of an empty-string. + /// + /// The subtag values are _case sensitive_ and must be one of the valid + /// subtags according to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The + /// primary language subtag must be at least two and at most eight lowercase + /// letters, but not four letters. The region region subtag must be two + /// uppercase letters or three digits. See the [Unicode Language + /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) + /// specification. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which also allows a [scriptCode] to be + /// specified. + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + + /// Creates a new Locale object. + /// + /// The keyword arguments specify the subtags of the Locale. + /// + /// The subtag values are _case sensitive_ and must be valid subtags according + /// to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for + /// each of languageCode, scriptCode and countryCode respectively. + /// + /// The [countryCode] subtag is optional. When there is no country subtag, + /// the parameter should be omitted or passed `null` instead of an empty-string. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + + /// The primary language subtag for the locale. + /// + /// This must not be null. It may be 'und', representing 'undefined'. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "language". The string specified must match the case of the + /// string in the registry. + /// + /// Language subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const + /// Locale('he')` and `const Locale('iw')` are equal, and both have the + /// [languageCode] `he`, because `iw` is a deprecated language subtag that was + /// replaced by the subtag `he`. + /// + /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR + /// supplemental + /// data](http://unicode.org/cldr/latest/common/validity/language.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + + /// The script subtag for the locale. + /// + /// This may be null, indicating that there is no specified script subtag. + /// + /// This must be a valid Unicode Language Identifier script subtag as listed + /// in [Unicode CLDR supplemental + /// data](http://unicode.org/cldr/latest/common/validity/script.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + final String? scriptCode; + + /// The region subtag for the locale. + /// + /// This may be null, indicating that there is no specified region subtag. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "region". The string specified must match the case of the + /// string in the registry. + /// + /// Region subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const Locale('de', + /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the + /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was + /// replaced by the subtag `DE`. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other is! Locale) { + return false; + } + final String? countryCode = _countryCode; + final String? otherCountryCode = other.countryCode; + return other.languageCode == languageCode + && other.scriptCode == scriptCode // scriptCode cannot be '' + && (other.countryCode == countryCode // Treat '' as equal to null. + || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null + || countryCode != null && countryCode.isEmpty && other.countryCode == null); + } + + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); + + static Locale? _cachedLocale; + static String? _cachedLocaleString; + + /// Returns a string representing the locale. + /// + /// This identifier happens to be a valid Unicode Locale Identifier using + /// underscores as separator, however it is intended to be used for debugging + /// purposes only. For parsable results, use [toLanguageTag] instead. + @keepToString + @override + String toString() { + if (!identical(_cachedLocale, this)) { + _cachedLocale = this; + _cachedLocaleString = _rawToString('_'); + } + return _cachedLocaleString!; + } + + /// Returns a syntactically valid Unicode BCP47 Locale Identifier. + /// + /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and + /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical + /// details. + String toLanguageTag() => _rawToString('-'); + + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null && scriptCode!.isNotEmpty) + out.write('$separator$scriptCode'); + if (_countryCode != null && _countryCode!.isNotEmpty) + out.write('$separator$countryCode'); + return out.toString(); + } +} \ No newline at end of file diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index 30bce7cc91e47..5769905628fdf 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -625,7 +625,8 @@ class SemanticsFlag { /// An object that creates [SemanticsUpdate] objects. /// /// Once created, the [SemanticsUpdate] objects can be passed to -/// [Window.updateSemantics] to update the semantics conveyed to the user. +/// [PlatformDispatcher.updateSemantics] to update the semantics conveyed to the +/// user. @pragma('vm:entry-point') class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates an empty [SemanticsUpdateBuilder] object. @@ -653,10 +654,10 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// /// The `actions` are a bit field of [SemanticsAction]s that can be undertaken /// by this node. If the user wishes to undertake one of these actions on this - /// node, the [Window.onSemanticsAction] will be called with `id` and one of - /// the possible [SemanticsAction]s. Because the semantics tree is maintained - /// asynchronously, the [Window.onSemanticsAction] callback might be called - /// with an action that is no longer possible. + /// node, the [PlatformDispatcher.onSemanticsAction] will be called with `id` + /// and one of the possible [SemanticsAction]s. Because the semantics tree is + /// maintained asynchronously, the [PlatformDispatcher.onSemanticsAction] + /// callback might be called with an action that is no longer possible. /// /// The `label` is a string that describes this node. The `value` property /// describes the current value of the node as a string. The `increasedValue` @@ -832,8 +833,8 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates a [SemanticsUpdate] object that encapsulates the updates recorded /// by this object. /// - /// The returned object can be passed to [Window.updateSemantics] to actually - /// update the semantics retained by the system. + /// The returned object can be passed to [PlatformDispatcher.updateSemantics] + /// to actually update the semantics retained by the system. SemanticsUpdate build() { final SemanticsUpdate semanticsUpdate = SemanticsUpdate._(); _build(semanticsUpdate); @@ -847,7 +848,7 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// To create a SemanticsUpdate object, use a [SemanticsUpdateBuilder]. /// /// Semantics updates can be applied to the system's retained semantics tree -/// using the [Window.updateSemantics] method. +/// using the [PlatformDispatcher.updateSemantics] method. @pragma('vm:entry-point') class SemanticsUpdate extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 8a03b68c68fa1..96e78678f60a2 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -2273,7 +2273,7 @@ final ByteData _fontChangeMessage = utf8.encoder.convert( ).buffer.asByteData(); FutureOr _sendFontChangeMessage() async { - window.onPlatformMessage?.call( + PlatformDispatcher.instance.onPlatformMessage?.call( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart index fe0e4fa16bcd6..4603fb3ce7774 100644 --- a/lib/ui/ui.dart +++ b/lib/ui/ui.dart @@ -33,6 +33,7 @@ part 'isolate_name_server.dart'; part 'lerp.dart'; part 'natives.dart'; part 'painting.dart'; +part 'platform_dispatcher.dart'; part 'plugins.dart'; part 'pointer.dart'; part 'semantics.dart'; diff --git a/lib/ui/window.dart b/lib/ui/window.dart index a39c5407ad45e..910866379d814 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -5,614 +5,72 @@ // @dart = 2.10 part of dart.ui; -/// Signature of callbacks that have no arguments and return no data. -typedef VoidCallback = void Function(); - -/// Signature for [Window.onBeginFrame]. -typedef FrameCallback = void Function(Duration duration); - -/// Signature for [Window.onReportTimings]. +/// A view into which a Flutter [Scene] is drawn. /// -/// {@template dart.ui.TimingsCallback.list} -/// The callback takes a list of [FrameTiming] because it may not be -/// immediately triggered after each frame. Instead, Flutter tries to batch -/// frames together and send all their timings at once to decrease the -/// overhead (as this is available in the release mode). The list is sorted in -/// ascending order of time (earliest frame first). The timing of any frame -/// will be sent within about 1 second (100ms if in the profile/debug mode) -/// even if there are no later frames to batch. The timing of the first frame -/// will be sent immediately without batching. -/// {@endtemplate} -typedef TimingsCallback = void Function(List timings); - -/// Signature for [Window.onPointerDataPacket]. -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); - -/// Signature for [Window.onSemanticsAction]. -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); - -/// Signature for responses to platform messages. -/// -/// Used as a parameter to [Window.sendPlatformMessage] and -/// [Window.onPlatformMessage]. -typedef PlatformMessageResponseCallback = void Function(ByteData? data); - -/// Signature for [Window.onPlatformMessage]. -typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); - -// Signature for _setNeedsReportTimings. -typedef _SetNeedsReportTimingsFunc = void Function(bool value); - -/// Various important time points in the lifetime of a frame. -/// -/// [FrameTiming] records a timestamp of each phase for performance analysis. -enum FramePhase { - /// The timestamp of the vsync signal given by the operating system. - /// - /// See also [FrameTiming.vsyncOverhead]. - vsyncStart, - - /// When the UI thread starts building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildStart, - - /// When the UI thread finishes building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildFinish, - - /// When the raster thread starts rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterStart, - - /// When the raster thread finishes rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterFinish, -} - -/// Time-related performance metrics of a frame. -/// -/// If you're using the whole Flutter framework, please use -/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using -/// [Window.onReportTimings] directly because -/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If -/// [SchedulerBinding] is unavailable, then see [Window.onReportTimings] for how -/// to get this. -/// -/// The metrics in debug mode (`flutter run` without any flags) may be very -/// different from those in profile and release modes due to the debug overhead. -/// Therefore it's recommended to only monitor and analyze performance metrics -/// in profile and release modes. -class FrameTiming { - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// This constructor is used for unit test only. Real [FrameTiming]s should - /// be retrieved from [Window.onReportTimings]. - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// List [timestamps] must have the same number of elements as - /// [FramePhase.values]. - /// - /// This constructor is usually only called by the Flutter engine, or a test. - /// To get the [FrameTiming] of your app, see [Window.onReportTimings]. - FrameTiming._(List timestamps) - : assert(timestamps.length == FramePhase.values.length), _timestamps = timestamps; - - /// This is a raw timestamp in microseconds from some epoch. The epoch in all - /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - - /// The duration to build the frame on the UI thread. - /// - /// The build starts approximately when [Window.onBeginFrame] is called. The - /// [Duration] in the [Window.onBeginFrame] callback is exactly the - /// `Duration(microseconds: timestampInMicroseconds(FramePhase.buildStart))`. - /// - /// The build finishes when [Window.render] is called. - /// - /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// To ensure smooth animations of X fps, this should not exceed 1000/X - /// milliseconds. - /// {@endtemplate} - /// {@template dart.ui.FrameTiming.fps_milliseconds} - /// That's about 16ms for 60fps, and 8ms for 120fps. - /// {@endtemplate} - Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - - /// The duration to rasterize the frame on the raster thread. - /// - /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - - /// The duration between receiving the vsync signal and starting building the - /// frame. - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - - /// The timespan between vsync start and raster finish. - /// - /// To achieve the lowest latency on an X fps display, this should not exceed - /// 1000/X milliseconds. - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - /// - /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. - Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - -/// States that an application can be in. -/// -/// The values below describe notifications from the operating system. -/// Applications should not expect to always receive all possible -/// notifications. For example, if the users pulls out the battery from the -/// device, no notification will be sent before the application is suddenly -/// terminated, along with the rest of the operating system. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state -/// from the widgets layer. -enum AppLifecycleState { - /// The application is visible and responding to user input. - resumed, - - /// The application is in an inactive state and is not receiving user input. - /// - /// On iOS, this state corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when in - /// a phone call, responding to a TouchID request, when entering the app - /// switcher or the control center, or when the UIViewController hosting the - /// Flutter app is transitioning. - /// - /// On Android, this corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when - /// another activity is focused, such as a split-screen app, a phone call, - /// a picture-in-picture app, a system dialog, or another window. - /// - /// Apps in this state should assume that they may be [paused] at any time. - inactive, - - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. - /// - /// When the application is in this state, the engine will not call the - /// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks. - paused, - - /// The application is still hosted on a flutter engine but is detached from - /// any host views. - /// - /// When the application is in this state, the engine is running without - /// a view. It can either be in the progress of attaching a view when engine - /// was first initializes, or after the view being destroyed due to a Navigator - /// pop. - detached, -} - -/// A representation of distances for each of the four edges of a rectangle, -/// used to encode the view insets and padding that applications should place -/// around their user interface, as exposed by [Window.viewInsets] and -/// [Window.padding]. View insets and padding are preferably read via -/// [MediaQuery.of]. -/// -/// For a generic class that represents distances around a rectangle, see the -/// [EdgeInsets] class. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive -/// notifications when the padding changes. -/// * [MediaQuery.of], for the preferred mechanism for accessing these values. -/// * [Scaffold], which automatically applies the padding in material design -/// applications. -class WindowPadding { - const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); - - /// The distance from the left edge to the first unpadded pixel, in physical pixels. - final double left; - - /// The distance from the top edge to the first unpadded pixel, in physical pixels. - final double top; - - /// The distance from the right edge to the first unpadded pixel, in physical pixels. - final double right; - - /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. - final double bottom; - - /// A window padding that has zeros for each edge. - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); - - @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } -} - -/// An identifier used to select a user's language and formatting preferences. -/// -/// This represents a [Unicode Language -/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) -/// (i.e. without Locale extensions), except variants are not supported. -/// -/// Locales are canonicalized according to the "preferred value" entries in the -/// [IANA Language Subtag -/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). -/// For example, `const Locale('he')` and `const Locale('iw')` are equal and -/// both have the [languageCode] `he`, because `iw` is a deprecated language -/// subtag that was replaced by the subtag `he`. -/// -/// See also: -/// -/// * [Window.locale], which specifies the system's currently selected -/// [Locale]. -class Locale { - /// Creates a new Locale object. The first argument is the - /// primary language subtag, the second is the region (also - /// referred to as 'country') subtag. - /// - /// For example: - /// - /// ```dart - /// const Locale swissFrench = Locale('fr', 'CH'); - /// const Locale canadianFrench = Locale('fr', 'CA'); - /// ``` - /// - /// The primary language subtag must not be null. The region subtag is - /// optional. When there is no region/country subtag, the parameter should - /// be omitted or passed `null` instead of an empty-string. - /// - /// The subtag values are _case sensitive_ and must be one of the valid - /// subtags according to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The - /// primary language subtag must be at least two and at most eight lowercase - /// letters, but not four letters. The region region subtag must be two - /// uppercase letters or three digits. See the [Unicode Language - /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) - /// specification. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which also allows a [scriptCode] to be - /// specified. - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - - /// Creates a new Locale object. - /// - /// The keyword arguments specify the subtags of the Locale. - /// - /// The subtag values are _case sensitive_ and must be valid subtags according - /// to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for - /// each of languageCode, scriptCode and countryCode respectively. - /// - /// The [countryCode] subtag is optional. When there is no country subtag, - /// the parameter should be omitted or passed `null` instead of an empty-string. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - - /// The primary language subtag for the locale. - /// - /// This must not be null. It may be 'und', representing 'undefined'. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "language". The string specified must match the case of the - /// string in the registry. - /// - /// Language subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const - /// Locale('he')` and `const Locale('iw')` are equal, and both have the - /// [languageCode] `he`, because `iw` is a deprecated language subtag that was - /// replaced by the subtag `he`. - /// - /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR - /// supplemental - /// data](http://unicode.org/cldr/latest/common/validity/language.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - - /// The script subtag for the locale. - /// - /// This may be null, indicating that there is no specified script subtag. - /// - /// This must be a valid Unicode Language Identifier script subtag as listed - /// in [Unicode CLDR supplemental - /// data](http://unicode.org/cldr/latest/common/validity/script.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - final String? scriptCode; - - /// The region subtag for the locale. - /// - /// This may be null, indicating that there is no specified region subtag. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "region". The string specified must match the case of the - /// string in the registry. - /// - /// Region subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const Locale('de', - /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the - /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was - /// replaced by the subtag `DE`. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) - return true; - if (other is! Locale) { - return false; - } - final String? countryCode = _countryCode; - final String? otherCountryCode = other.countryCode; - return other.languageCode == languageCode - && other.scriptCode == scriptCode // scriptCode cannot be '' - && (other.countryCode == countryCode // Treat '' as equal to null. - || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null - || countryCode != null && countryCode.isEmpty && other.countryCode == null); - } - - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); - - static Locale? _cachedLocale; - static String? _cachedLocaleString; - - /// Returns a string representing the locale. - /// - /// This identifier happens to be a valid Unicode Locale Identifier using - /// underscores as separator, however it is intended to be used for debugging - /// purposes only. For parseable results, use [toLanguageTag] instead. - @keepToString - @override - String toString() { - if (!identical(_cachedLocale, this)) { - _cachedLocale = this; - _cachedLocaleString = _rawToString('_'); - } - return _cachedLocaleString!; - } - - /// Returns a syntactically valid Unicode BCP47 Locale Identifier. - /// - /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and - /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical - /// details. - String toLanguageTag() => _rawToString('-'); - - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null && scriptCode!.isNotEmpty) - out.write('$separator$scriptCode'); - if (_countryCode != null && _countryCode!.isNotEmpty) - out.write('$separator$countryCode'); - return out.toString(); - } -} - -/// The most basic interface to the host operating system's user interface. -/// -/// It exposes the size of the display, the core scheduler API, the input event -/// callback, the graphics drawing API, and other such core services. -/// -/// There is a single Window instance in the system, which you can -/// obtain from `WidgetsBinding.instance.window`. -/// -/// There is also a [window] singleton object in `dart:ui` if `WidgetsBinding` -/// is unavailable. But we strongly advise to avoid statically referencing it. -/// See the document of [window] for more details of why it should be avoided. +/// Each [FlutterView] has its own layer tree that is rendered into an area +/// inside of a [FlutterWindow] whenever [render] is called with a [Scene]. /// /// ## Insets and Padding /// /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4} /// -/// In this diagram, the black areas represent system UI that the app cannot -/// draw over. The red area represents view padding that the application may not +/// In this illustration, the black areas represent system UI that the app +/// cannot draw over. The red area represents view padding that the view may not /// be able to detect gestures in and may not want to draw in. The grey area -/// represents the system keyboard, which can cover over the bottom view -/// padding when visible. +/// represents the system keyboard, which can cover over the bottom view padding +/// when visible. /// -/// The [Window.viewInsets] are the physical pixels which the operating +/// The [viewInsets] are the physical pixels which the operating /// system reserves for system UI, such as the keyboard, which would fully /// obscure any content drawn in that area. /// -/// The [Window.viewPadding] are the physical pixels on each side of the display -/// that may be partially obscured by system UI or by physical intrusions into -/// the display, such as an overscan region on a television or a "notch" on a -/// phone. Unlike the insets, these areas may have portions that show the user -/// application painted pixels without being obscured, such as a notch at the -/// top of a phone that covers only a subset of the area. Insets, on the other -/// hand, either partially or fully obscure the window, such as an opaque -/// keyboard or a partially transluscent statusbar, which cover an area without -/// gaps. +/// The [viewPadding] are the physical pixels on each side of the +/// display that may be partially obscured by system UI or by physical +/// intrusions into the display, such as an overscan region on a television or a +/// "notch" on a phone. Unlike the insets, these areas may have portions that +/// show the user view-painted pixels without being obscured, such as a +/// notch at the top of a phone that covers only a subset of the area. Insets, +/// on the other hand, either partially or fully obscure the window, such as an +/// opaque keyboard or a partially translucent status bar, which cover an area +/// without gaps. /// -/// The [Window.padding] property is computed from both [Window.viewInsets] and -/// [Window.viewPadding]. It will allow a view inset to consume view padding -/// where appropriate, such as when a phone's keyboard is covering the bottom -/// view padding and so "absorbs" it. +/// The [padding] property is computed from both +/// [viewInsets] and [viewPadding]. It will allow a +/// view inset to consume view padding where appropriate, such as when a phone's +/// keyboard is covering the bottom view padding and so "absorbs" it. /// /// Clients that want to position elements relative to the view padding -/// regardless of the view insets should use the [Window.viewPadding] property, -/// e.g. if you wish to draw a widget at the center of the screen with respect -/// to the iPhone "safe area" regardless of whether the keyboard is showing. +/// regardless of the view insets should use the [viewPadding] +/// property, e.g. if you wish to draw a widget at the center of the screen with +/// respect to the iPhone "safe area" regardless of whether the keyboard is +/// showing. /// -/// [Window.padding] is useful for clients that want to know how much padding -/// should be accounted for without concern for the current inset(s) state, e.g. -/// determining whether a gesture should be considered for scrolling purposes. -/// This value varies based on the current state of the insets. For example, a -/// visible keyboard will consume all gestures in the bottom part of the -/// [Window.viewPadding] anyway, so there is no need to account for that in the -/// [Window.padding], which is always safe to use for such calculations. -class Window { - Window._() { - _setNeedsReportTimings = _nativeSetNeedsReportTimings; - } +/// [padding] is useful for clients that want to know how much +/// padding should be accounted for without concern for the current inset(s) +/// state, e.g. determining whether a gesture should be considered for scrolling +/// purposes. This value varies based on the current state of the insets. For +/// example, a visible keyboard will consume all gestures in the bottom part of +/// the [viewPadding] anyway, so there is no need to account for +/// that in the [padding], which is always safe to use for such +/// calculations. +/// +/// See also: +/// +/// * [FlutterWindow], a special case of a [FlutterView] that is represented on +/// the platform as a separate window which can host other [FlutterView]s. +abstract class FlutterView { + /// The platform dispatcher that this view is registered with, and gets its + /// information from. + PlatformDispatcher get platformDispatcher; - /// The number of device pixels for each logical pixel. This number might not - /// be a power of two. Indeed, it might not even be an integer. For example, - /// the Nexus 6 has a device pixel ratio of 3.5. + /// The configuration of this view. + ViewConfiguration get viewConfiguration; + + /// The number of device pixels for each logical pixel for the screen this + /// view is displayed on. + /// + /// This number might not be a power of two. Indeed, it might not even be an + /// integer. For example, the Nexus 6 has a device pixel ratio of 3.5. /// /// Device pixels are also referred to as physical pixels. Logical pixels are /// also referred to as device-independent or resolution-independent pixels. @@ -633,39 +91,59 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get devicePixelRatio => _devicePixelRatio; - double _devicePixelRatio = 1.0; + double get devicePixelRatio => viewConfiguration.devicePixelRatio; - /// The dimensions of the rectangle into which the application will be drawn, - /// in physical pixels. + /// The dimensions and location of the rectangle into which the scene rendered + /// in this view will be drawn on the screen, in physical pixels. /// /// When this changes, [onMetricsChanged] is called. /// - /// At startup, the size of the application window may not be known before Dart + /// At startup, the size and location of the view may not be known before Dart /// code runs. If this value is observed early in the application lifecycle, - /// it may report [Size.zero]. + /// it may report [Rect.zero]. /// /// This value does not take into account any on-screen keyboards or other /// system UI. The [padding] and [viewInsets] properties provide a view into - /// how much of each side of the application may be obscured by system UI. + /// how much of each side of the view may be obscured by system UI. /// /// See also: /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - Size get physicalSize => _physicalSize; - Size _physicalSize = Size.zero; + Rect get physicalGeometry => viewConfiguration.geometry; + + /// The dimensions of the rectangle into which the scene rendered in this view + /// will be drawn on the screen, in physical pixels. + /// + /// When this changes, [onMetricsChanged] is called. + /// + /// At startup, the size of the view may not be known before Dart code runs. + /// If this value is observed early in the application lifecycle, it may + /// report [Size.zero]. + /// + /// This value does not take into account any on-screen keyboards or other + /// system UI. The [padding] and [viewInsets] properties provide information + /// about how much of each side of the view may be obscured by system UI. + /// + /// This value is the same as [physicalGeometry.size]. + /// + /// See also: + /// + /// * [physicalGeometry], which reports the location of the view as well as + /// its size. + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + Size get physicalSize => viewConfiguration.geometry.size; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but over which the operating system - /// will likely place system UI, such as the keyboard, that fully obscures - /// any content. + /// which the view can render, but over which the operating system will likely + /// place system UI, such as the keyboard, that fully obscures any content. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], + /// [viewPadding], and [padding] are described in + /// more detail in the documentation for [FlutterView]. /// /// See also: /// @@ -674,25 +152,24 @@ class Window { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the view insets in material /// design applications. - WindowPadding get viewInsets => _viewInsets; - WindowPadding _viewInsets = WindowPadding.zero; + WindowPadding get viewInsets => viewConfiguration.viewInsets; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but which may be partially obscured by - /// system UI (such as the system notification area), or or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). + /// which the view can render, but which may be partially obscured by system + /// UI (such as the system notification area), or or physical intrusions in + /// the display (e.g. overscan regions on television screens or phone sensor + /// housings). /// - /// Unlike [Window.padding], this value does not change relative to - /// [Window.viewInsets]. For example, on an iPhone X, it will not change in - /// response to the soft keyboard being visible or hidden, whereas - /// [Window.padding] will. + /// Unlike [padding], this value does not change relative to + /// [viewInsets]. For example, on an iPhone X, it will not + /// change in response to the soft keyboard being visible or hidden, whereas + /// [padding] will. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], + /// [viewPadding], and [padding] are described in + /// more detail in the documentation for [FlutterView]. /// /// See also: /// @@ -701,12 +178,11 @@ class Window { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the padding in material design /// applications. - WindowPadding get viewPadding => _viewPadding; - WindowPadding _viewPadding = WindowPadding.zero; + WindowPadding get viewPadding => viewConfiguration.viewPadding; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but where the operating system will - /// consume input gestures for the sake of system navigation. + /// which the view can render, but where the operating system will consume + /// input gestures for the sake of system navigation. /// /// For example, an operating system might use the vertical edges of the /// screen, where swiping inwards from the edges takes users backward @@ -719,85 +195,158 @@ class Window { /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. /// * [MediaQuery.of], a simpler mechanism for the same. - WindowPadding get systemGestureInsets => _systemGestureInsets; - WindowPadding _systemGestureInsets = WindowPadding.zero; + WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but which may be partially obscured by - /// system UI (such as the system notification area), or or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). - /// - /// This value is calculated by taking - /// `max(0.0, Window.viewPadding - Window.viewInsets)`. This will treat a - /// system IME that increases the bottom inset as consuming that much of the - /// bottom padding. For example, on an iPhone X, [EdgeInsets.bottom] of - /// [Window.padding] is the same as [EdgeInsets.bottom] of - /// [Window.viewPadding] when the soft keyboard is not drawn (to account for - /// the bottom soft button area), but will be `0.0` when the soft keyboard is - /// visible. + /// which the view can render, but which may be partially obscured by system + /// UI (such as the system notification area), or or physical intrusions in + /// the display (e.g. overscan regions on television screens or phone sensor + /// housings). + /// + /// This value is calculated by taking `max(0.0, FlutterView.viewPadding - + /// FlutterView.viewInsets)`. This will treat a system IME that increases the + /// bottom inset as consuming that much of the bottom padding. For example, on + /// an iPhone X, [EdgeInsets.bottom] of [FlutterView.padding] is the same as + /// [EdgeInsets.bottom] of [FlutterView.viewPadding] when the soft keyboard is + /// not drawn (to account for the bottom soft button area), but will be `0.0` + /// when the soft keyboard is visible. /// /// When this changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], [viewPadding], and [padding] + /// are described in more detail in the documentation for [FlutterView]. /// /// See also: /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - /// * [MediaQuery.of], a simpler mechanism for the same. - /// * [Scaffold], which automatically applies the padding in material design - /// applications. - WindowPadding get padding => _padding; - WindowPadding _padding = WindowPadding.zero; + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + /// * [MediaQuery.of], a simpler mechanism for the same. + /// * [Scaffold], which automatically applies the padding in material design + /// applications. + WindowPadding get padding => viewConfiguration.padding; - /// A callback that is invoked whenever the [devicePixelRatio], - /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] - /// values change, for example when the device is rotated or when the - /// application is resized (e.g. when showing applications side-by-side - /// on Android). + /// Updates the view's rendering on the GPU with the newly provided [Scene]. /// - /// The engine invokes this callback in the same zone in which the callback - /// was set. + /// This function must be called within the scope of the + /// [PlatformDispatcher.onBeginFrame] or [PlatformDispatcher.onDrawFrame] + /// callbacks being invoked. /// - /// The framework registers with this callback and updates the layout - /// appropriately. + /// If this function is called a second time during a single + /// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame] + /// callback sequence or called outside the scope of those callbacks, the call + /// will be ignored. + /// + /// To record graphical operations, first create a [PictureRecorder], then + /// construct a [Canvas], passing that [PictureRecorder] to its constructor. + /// After issuing all the graphical operations, call the + /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain + /// the final [Picture] that represents the issued graphical operations. + /// + /// Next, create a [SceneBuilder], and add the [Picture] to it using + /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can + /// then obtain a [Scene] object, which you can display to the user via this + /// [render] function. /// /// See also: /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// register for notifications when this is called. - /// * [MediaQuery.of], a simpler mechanism for the same. - VoidCallback? get onMetricsChanged => _onMetricsChanged; - VoidCallback? _onMetricsChanged; - Zone _onMetricsChangedZone = Zone.root; + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + /// * [RendererBinding], the Flutter framework class which manages layout and + /// painting. + void render(Scene scene) => _render(scene, this); + void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render'; +} + +/// A top-level platform window displaying a Flutter layer tree drawn from a +/// [Scene]. +/// +/// The current list of all Flutter views for the application is available from +/// `WidgetsBinding.instance.platformDispatcher.views`. Only views that are of type +/// [FlutterWindow] are top level platform windows. +/// +/// There is also a [PlatformDispatcher.instance] singleton object in `dart:ui` +/// if `WidgetsBinding` is unavailable, but we strongly advise avoiding a static +/// reference to it. See the documentation for [PlatformDispatcher.instance] for +/// more details about why it should be avoided. +/// +/// See also: +/// +/// * [PlatformDispatcher], which manages the current list of [FlutterView] (and +/// thus [FlutterWindow]) instances. +class FlutterWindow extends FlutterView { + FlutterWindow._(this._windowId, this.platformDispatcher); + + /// The opaque ID for this view. + final Object _windowId; + + @override + final PlatformDispatcher platformDispatcher; + + @override + ViewConfiguration get viewConfiguration { + assert(platformDispatcher._viewConfigurations.containsKey(_windowId)); + return platformDispatcher._viewConfigurations[_windowId]!; + } +} + +/// A [FlutterWindow] that includes access to setting callbacks and retrieving +/// properties that reside on the [PlatformDispatcher]. +/// +/// It is the type of the global [window] singleton used by applications that +/// only have a single main window. +/// +/// In addition to the properties of [FlutterView], this class provides access +/// to platform-specific properties. To modify or retrieve these properties, +/// applications designed for more than one main window should prefer using +/// `WidgetsBinding.instance.platformDispatcher` instead. +/// +/// Prefer access through `WidgetsBinding.instance.window` or +/// `WidgetsBinding.instance.platformDispatcher` over a static reference to +/// [window], or [PlatformDispatcher.instance]. See the documentation for +/// [PlatformDispatcher.instance] for more details about this recommendation. +class SingletonFlutterWindow extends FlutterWindow { + SingletonFlutterWindow._(Object windowId, PlatformDispatcher platformDispatcher) + : super._(windowId, platformDispatcher); + + /// A callback that is invoked whenever the [devicePixelRatio], + /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], + /// [PlatformDispatcher.screens], or [systemGestureInsets] values change. + /// + /// {@template flutter.lib.ui.window.forwardWarning} + /// + /// See [PlatformDispatcher.onMetricsChanged] for more information. + VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; set onMetricsChanged(VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; + platformDispatcher.onMetricsChanged = callback; } /// The system-reported default locale of the device. /// - /// This establishes the language and formatting conventions that application + /// {@template flutter.lib.ui.window.accessorForwardWarning} + /// Accessing this value returns the value contained in the + /// [PlatformDispatcher] singleton, so instead of getting it from here, you + /// should consider getting it from `WidgetsBinding.instance.platformDispatcher` instead + /// (or, when `WidgetsBinding` isn't available, from + /// [PlatformDispatcher.instance]). The reason this value forwards to the + /// [PlatformDispatcher] is to provide convenience for applications that only + /// use a single main window. + /// {@endtemplate} + /// + /// This establishes the language and formatting conventions that window /// should, if possible, use to render their user interface. /// - /// This is the first locale selected by the user and is the user's - /// primary locale (the locale the device UI is displayed in) + /// This is the first locale selected by the user and is the user's primary + /// locale (the locale the device UI is displayed in) /// - /// This is equivalent to `locales.first` and will provide an empty non-null locale - /// if the [locales] list has not been set or is empty. - Locale? get locale { - if (_locales != null && _locales!.isNotEmpty) { - return _locales!.first; - } - return null; - } + /// This is equivalent to `locales.first` and will provide an empty non-null + /// locale if the [locales] list has not been set or is empty. + Locale? get locale => platformDispatcher.locale; /// The full system-reported supported locales of the device. /// - /// This establishes the language and formatting conventions that application + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// + /// This establishes the language and formatting conventions that window /// should, if possible, use to render their user interface. /// /// The list is ordered in order of priority, with lower-indexed locales being @@ -809,8 +358,7 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - List? get locales => _locales; - List? _locales; + List? get locales => platformDispatcher.locales; /// Performs the platform-native locale resolution. /// @@ -821,27 +369,13 @@ class Window { /// This method returns synchronously and is a direct call to /// platform specific APIs without invoking method channels. Locale? computePlatformResolvedLocale(List supportedLocales) { - final List supportedLocalesData = []; - for (Locale locale in supportedLocales) { - supportedLocalesData.add(locale.languageCode); - supportedLocalesData.add(locale.countryCode); - supportedLocalesData.add(locale.scriptCode); - } - - final List result = _computePlatformResolvedLocale(supportedLocalesData); - - if (result.isNotEmpty) { - return Locale.fromSubtags( - languageCode: result[0], - countryCode: result[1] == '' ? null : result[1], - scriptCode: result[2] == '' ? null : result[2]); - } - return null; + return platformDispatcher.computePlatformResolvedLocale(supportedLocales); } - List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; /// A callback that is invoked whenever [locale] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -849,32 +383,25 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onLocaleChanged => _onLocaleChanged; - VoidCallback? _onLocaleChanged; - Zone _onLocaleChangedZone = Zone.root; + VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; set onLocaleChanged(VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; + platformDispatcher.onLocaleChanged = callback; } /// The lifecycle state immediately after dart isolate initialization. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This property will not be updated as the lifecycle changes. /// /// It is used to initialize [SchedulerBinding.lifecycleState] at startup /// with any buffered lifecycle state events. - String get initialLifecycleState { - _initialLifecycleStateAccessed = true; - return _initialLifecycleState; - } - late String _initialLifecycleState; - /// Tracks if the initial state has been accessed. Once accessed, we - /// will stop updating the [initialLifecycleState], as it is not the - /// preferred way to access the state. - bool _initialLifecycleStateAccessed = false; + String get initialLifecycleState => platformDispatcher.initialLifecycleState; /// The system-reported text scale. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This establishes the text scaling factor to use when rendering text, /// according to the user's platform preferences. /// @@ -885,18 +412,20 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get textScaleFactor => _textScaleFactor; - double _textScaleFactor = 1.0; + double get textScaleFactor => platformDispatcher.textScaleFactor; /// The setting indicating whether time should always be shown in the 24-hour /// format. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This option is used by [showTimePicker]. - bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; - bool _alwaysUse24HourFormat = false; + bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; /// A callback that is invoked whenever [textScaleFactor] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -904,21 +433,23 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - VoidCallback? _onTextScaleFactorChanged; - Zone _onTextScaleFactorChangedZone = Zone.root; + VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; set onTextScaleFactorChanged(VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; + platformDispatcher.onTextScaleFactorChanged = callback; } /// The setting indicating the current brightness mode of the host platform. - /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. - Brightness get platformBrightness => _platformBrightness; - Brightness _platformBrightness = Brightness.light; + /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + Brightness get platformBrightness => platformDispatcher.platformBrightness; /// A callback that is invoked whenever [platformBrightness] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -926,19 +457,20 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; - VoidCallback? _onPlatformBrightnessChanged; - Zone _onPlatformBrightnessChangedZone = Zone.root; + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; set onPlatformBrightnessChanged(VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; + platformDispatcher.onPlatformBrightnessChanged = callback; } - /// A callback that is invoked to notify the application that it is an - /// appropriate time to provide a scene using the [SceneBuilder] API and the - /// [render] method. When possible, this is driven by the hardware VSync - /// signal. This is only called if [scheduleFrame] has been called since the - /// last time this callback was invoked. + /// A callback that is invoked to notify the window that it is an appropriate + /// time to provide a scene using the [SceneBuilder] API and the [render] + /// method. + /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// + /// When possible, this is driven by the hardware VSync signal. This is only + /// called if [scheduleFrame] has been called since the last time this + /// callback was invoked. /// /// The [onDrawFrame] callback is invoked immediately after [onBeginFrame], /// after draining any microtasks (e.g. completions of any [Future]s) queued @@ -953,18 +485,18 @@ class Window { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - FrameCallback? get onBeginFrame => _onBeginFrame; - FrameCallback? _onBeginFrame; - Zone _onBeginFrameZone = Zone.root; + FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; set onBeginFrame(FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; + platformDispatcher.onBeginFrame = callback; } /// A callback that is invoked for each frame after [onBeginFrame] has - /// completed and after the microtask queue has been drained. This can be - /// used to implement a second phase of frame rendering that happens - /// after any deferred work queued by the [onBeginFrame] phase. + /// completed and after the microtask queue has been drained. + /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -975,22 +507,21 @@ class Window { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - VoidCallback? get onDrawFrame => _onDrawFrame; - VoidCallback? _onDrawFrame; - Zone _onDrawFrameZone = Zone.root; + VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; set onDrawFrame(VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; + platformDispatcher.onDrawFrame = callback; } /// A callback that is invoked to report the [FrameTiming] of recently /// rasterized frames. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use - /// [Window.onReportTimings] directly because + /// [FlutterWindow.onReportTimings] directly because /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. /// - /// This can be used to see if the application has missed frames (through + /// This can be used to see if the window has missed frames (through /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high /// latencies (through [FrameTiming.totalSpan]). /// @@ -1004,22 +535,15 @@ class Window { /// Flutter spends less than 0.1ms every 1 second to report the timings /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for /// 60fps), or 0.01% CPU usage per second. - TimingsCallback? get onReportTimings => _onReportTimings; - TimingsCallback? _onReportTimings; - Zone _onReportTimingsZone = Zone.root; + TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; set onReportTimings(TimingsCallback? callback) { - if ((callback == null) != (_onReportTimings == null)) { - _setNeedsReportTimings(callback != null); - } - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; + platformDispatcher.onReportTimings = callback; } - late _SetNeedsReportTimingsFunc _setNeedsReportTimings; - void _nativeSetNeedsReportTimings(bool value) native 'PlatformConfiguration_setNeedsReportTimings'; - /// A callback that is invoked when pointer data is available. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -1027,17 +551,16 @@ class Window { /// /// * [GestureBinding], the Flutter framework class which manages pointer /// events. - PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - PointerDataPacketCallback? _onPointerDataPacket; - Zone _onPointerDataPacketZone = Zone.root; + PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; set onPointerDataPacket(PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; + platformDispatcher.onPointerDataPacket = callback; } /// The route or path that the embedder requested when the application was /// launched. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This will be the string "`/`" if no particular route was requested. /// /// ## Android @@ -1061,118 +584,90 @@ class Window { /// * [Navigator], a widget that handles routing. /// * [SystemChannels.navigation], which handles subsequent navigation /// requests from the embedder. - String get defaultRouteName => _defaultRouteName(); - String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; - - /// Requests that, at the next appropriate opportunity, the [onBeginFrame] - /// and [onDrawFrame] callbacks be invoked. - /// - /// See also: - /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; - - /// Updates the application's rendering on the GPU with the newly provided - /// [Scene]. This function must be called within the scope of the - /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function - /// is called a second time during a single [onBeginFrame]/[onDrawFrame] - /// callback sequence or called outside the scope of those callbacks, the call - /// will be ignored. - /// - /// To record graphical operations, first create a [PictureRecorder], then - /// construct a [Canvas], passing that [PictureRecorder] to its constructor. - /// After issuing all the graphical operations, call the - /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain - /// the final [Picture] that represents the issued graphical operations. - /// - /// Next, create a [SceneBuilder], and add the [Picture] to it using - /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can - /// then obtain a [Scene] object, which you can display to the user via this - /// [render] function. + String get defaultRouteName => platformDispatcher.defaultRouteName; + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and + /// [onDrawFrame] callbacks be invoked. + /// + /// {@template flutter.lib.ui.window.functionForwardWarning} + /// Calling this function forwards the call to the same function on the + /// [PlatformDispatcher] singleton, so instead of calling it here, you should + /// consider calling it on `WidgetsBinding.instance.platformDispatcher` instead (or, when + /// `WidgetsBinding` isn't available, on [PlatformDispatcher.instance]). The + /// reason this function forwards to the [PlatformDispatcher] is to provide + /// convenience for applications that only use a single main window. + /// {@endtemplate} /// /// See also: /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - /// * [RendererBinding], the Flutter framework class which manages layout and - /// painting. - void render(Scene scene) native 'PlatformConfiguration_render'; + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + void scheduleFrame() => platformDispatcher.scheduleFrame(); /// Whether the user has requested that [updateSemantics] be called when /// the semantic contents of window changes. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// The [onSemanticsEnabledChanged] callback is called whenever this value /// changes. - bool get semanticsEnabled => _semanticsEnabled; - bool _semanticsEnabled = false; + bool get semanticsEnabled => platformDispatcher.semanticsEnabled; /// A callback that is invoked when the value of [semanticsEnabled] changes. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - VoidCallback? _onSemanticsEnabledChanged; - Zone _onSemanticsEnabledChangedZone = Zone.root; + VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; set onSemanticsEnabledChanged(VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; + platformDispatcher.onSemanticsEnabledChanged = callback; } /// A callback that is invoked whenever the user requests an action to be /// performed. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// This callback is used when the user expresses the action they wish to /// perform based on the semantics supplied by [updateSemantics]. /// /// The framework invokes this callback in the same zone in which the /// callback was set. - SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - SemanticsActionCallback? _onSemanticsAction; - Zone _onSemanticsActionZone = Zone.root; + SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; set onSemanticsAction(SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; + platformDispatcher.onSemanticsAction = callback; } /// Additional accessibility features that may be enabled by the platform. - AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; - // The zero value matches the default value in `platform_data.h`. - AccessibilityFeatures _accessibilityFeatures = const AccessibilityFeatures._(0); + AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; /// A callback that is invoked when the value of [accessibilityFeatures] changes. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; - VoidCallback? _onAccessibilityFeaturesChanged; - Zone _onAccessibilityFeaturesChangedZone = Zone.root; + VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged; set onAccessibilityFeaturesChanged(VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; + platformDispatcher.onAccessibilityFeaturesChanged = callback; } /// Change the retained semantics data about this window. /// + /// {@macro flutter.lib.ui.window.functionForwardWarning} + /// /// If [semanticsEnabled] is true, the user has requested that this function /// be called whenever the semantic content of this window changes. /// /// In either case, this function disposes the given update, which means the /// semantics update cannot be used further. - void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; - - /// Set the debug name associated with this window's root isolate. - /// - /// Normally debug names are automatically generated from the Dart port, entry - /// point, and source file. For example: `main.dart$main-1234`. - /// - /// This can be combined with flutter tools `--isolate-filter` flag to debug - /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. - /// Note that this does not rename any child isolates of the root. - void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; + void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); /// Sends a message to a platform-specific plugin. /// + /// {@macro flutter.lib.ui.window.functionForwardWarning} + /// /// The `name` parameter determines which plugin receives the message. The /// `data` parameter contains the message payload and is typically UTF-8 /// encoded JSON but can be arbitrary data. If the plugin replies to the @@ -1181,20 +676,16 @@ class Window { /// The framework invokes [callback] in the same zone in which this method /// was called. void sendPlatformMessage(String name, - ByteData? data, - PlatformMessageResponseCallback? callback) { - final String? error = - _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); - if (error != null) - throw Exception(error); + ByteData? data, + PlatformMessageResponseCallback? callback) { + platformDispatcher.sendPlatformMessage(name, data, callback); } - String? _sendPlatformMessage(String name, - PlatformMessageResponseCallback? callback, - ByteData? data) native 'PlatformConfiguration_sendPlatformMessage'; /// Called whenever this window receives a message from a platform-specific /// plugin. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The `name` parameter determines which plugin sent the message. The `data` /// parameter is the payload and is typically UTF-8 encoded JSON but can be /// arbitrary data. @@ -1205,43 +696,23 @@ class Window { /// /// The framework invokes this callback in the same zone in which the /// callback was set. - PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - PlatformMessageCallback? _onPlatformMessage; - Zone _onPlatformMessageZone = Zone.root; + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; set onPlatformMessage(PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Called by [_dispatchPlatformMessage]. - void _respondToPlatformMessage(int responseId, ByteData? data) - native 'PlatformConfiguration_respondToPlatformMessage'; - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback? callback) { - if (callback == null) - return null; - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; + platformDispatcher.onPlatformMessage = callback; } - - /// The embedder can specify data that the isolate can request synchronously - /// on launch. This accessor fetches that data. + /// Set the debug name associated with this platform dispatcher's root + /// isolate. /// - /// This data is persistent for the duration of the Flutter application and is - /// available even after isolate restarts. Because of this lifecycle, the size - /// of this data must be kept to a minimum. + /// {@macro flutter.lib.ui.window.forwardWarning} /// - /// For asynchronous communication between the embedder and isolate, a - /// platform channel may be used. - ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. + /// + /// This can be combined with flutter tools `--isolate-filter` flag to debug + /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. + /// Note that this does not rename any child isolates of the root. + void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } /// Additional accessibility features that may be enabled by the platform. @@ -1323,6 +794,15 @@ class AccessibilityFeatures { int get hashCode => _index.hashCode; } +/// A soon-to-be deprecated class that is wholly replaced by +/// [SingletonFlutterWindow]. +/// +/// This class will be removed once the framework no longer refers to it. +class Window extends SingletonFlutterWindow { + Window._(Object windowId, PlatformDispatcher platformDispatcher) + : super._(windowId, platformDispatcher); +} + /// Describes the contrast of a theme or color palette. enum Brightness { /// The color is dark and will require a light text color to achieve readable @@ -1338,12 +818,18 @@ enum Brightness { light, } -/// The [Window] singleton. +/// The [SingletonFlutterWindow] representing the main window for applications +/// where there is only one window, such as applications designed for +/// single-display mobile devices. /// -/// Please try to avoid statically referencing this and instead use a -/// binding for dependency resolution such as `WidgetsBinding.instance.window`. +/// Applications that are designed to use more than one window should interact +/// with the `WidgetsBinding.instance.platformDispatcher` instead. /// -/// Static access of this "window" object means that Flutter has few, if any +/// Consider avoiding static references to this singleton through +/// [PlatformDispatcher.instance] and instead prefer using a binding for +/// dependency resolution such as `WidgetsBinding.instance.window`. +/// +/// Static access of this `window` object means that Flutter has few, if any /// options to fake or mock the given object in tests. Even in cases where Dart /// offers special language constructs to forcefully shadow such properties, /// those mechanisms would only be reasonable for tests and they would not be @@ -1351,6 +837,14 @@ enum Brightness { /// appropriate implementation at runtime. /// /// The only place that `WidgetsBinding.instance.window` is inappropriate is if -/// a `Window` is required before invoking `runApp()`. In that case, it is -/// acceptable (though unfortunate) to use this object statically. -final Window window = Window._(); +/// access to these APIs is required before the binding is initialized by +/// invoking `runApp()` or `WidgetsFlutterBinding.instance.ensureInitialized()`. +/// In that case, it is necessary (though unfortunate) to use the +/// [PlatformDispatcher.instance] object statically. +/// +/// See also: +/// +/// * [PlatformDispatcher.views], contains the current list of Flutter windows +/// belonging to the application, including top level application windows like +/// this one. +final Window window = Window._(0, PlatformDispatcher.instance); diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index e470e1dab79e6..f0df4efaf7a76 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -9,6 +9,7 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" +#include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" @@ -197,7 +198,9 @@ PlatformConfiguration::~PlatformConfiguration() {} void PlatformConfiguration::DidCreateIsolate() { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); - window_.reset(new Window({1.0, 0.0, 0.0})); + windows_.insert( + std::make_pair(0, std::unique_ptr(new Window{ + 0, ViewportMetrics{1.0, 0.0, 0.0, 0.0, 0.0}}))); } void PlatformConfiguration::UpdateLocales( @@ -421,7 +424,7 @@ void PlatformConfiguration::RegisterNatives( true}, {"PlatformConfiguration_respondToPlatformMessage", _RespondToPlatformMessage, 3, true}, - {"PlatformConfiguration_render", Render, 2, true}, + {"PlatformConfiguration_render", Render, 3, true}, {"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true}, {"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2, true}, diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 4652c7c5dcdbb..e4ce4bd5b0307 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -6,14 +6,12 @@ #define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_ #include -#include #include #include #include #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" -#include "flutter/lib/ui/window/platform_message.h" #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" @@ -378,11 +376,14 @@ class PlatformConfiguration final { static void RegisterNatives(tonic::DartLibraryNatives* natives); //---------------------------------------------------------------------------- - /// @brief Retrieves the Window managed by the PlatformConfiguration. + /// @brief Retrieves the Window with the given ID managed by the + /// `PlatformConfiguration`. + /// + /// @param[in] window_id The id of the window to find and return. /// /// @return a pointer to the Window. /// - Window* window() const { return window_.get(); } + Window* get_window(int window_id) { return windows_[window_id].get(); } //---------------------------------------------------------------------------- /// @brief Responds to a previous platform message to the engine from the @@ -408,7 +409,7 @@ class PlatformConfiguration final { PlatformConfigurationClient* client_; tonic::DartPersistentValue library_; - std::unique_ptr window_; + std::unordered_map> windows_; // We use id 0 to mean that no response is expected. int next_response_id_ = 1; diff --git a/lib/ui/window/platform_configuration_unittests.cc b/lib/ui/window/platform_configuration_unittests.cc index 1ff3ce01de52d..049f88920c94a 100644 --- a/lib/ui/window/platform_configuration_unittests.cc +++ b/lib/ui/window/platform_configuration_unittests.cc @@ -54,11 +54,14 @@ TEST_F(ShellTest, PlatformConfigurationInitialization) { Dart_NativeArguments args) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->window(), nullptr); - ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, - 1.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 0.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, 0.0); + ASSERT_NE(configuration->get_window(0), nullptr); + ASSERT_EQ( + configuration->get_window(0)->viewport_metrics().device_pixel_ratio, + 1.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, + 0.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, + 0.0); message_latch->Signal(); }; @@ -97,13 +100,15 @@ TEST_F(ShellTest, PlatformConfigurationWindowMetricsUpdate) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->window(), nullptr); - configuration->window()->UpdateWindowMetrics( - ViewportMetrics{2.0, 10.0, 20.0}); - ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, - 2.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 10.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, + ASSERT_NE(configuration->get_window(0), nullptr); + configuration->get_window(0)->UpdateWindowMetrics( + ViewportMetrics{2.0, 0.0, 0.0, 10.0, 20.0}); + ASSERT_EQ( + configuration->get_window(0)->viewport_metrics().device_pixel_ratio, + 2.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, + 10.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, 20.0); message_latch->Signal(); diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index d52bfb83756e4..9eb43d61811ac 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -18,6 +18,8 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_height(p_physical_height) {} ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, + double p_physical_left, + double p_physical_top, double p_physical_width, double p_physical_height, double p_physical_padding_top, @@ -33,6 +35,8 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left) : device_pixel_ratio(p_device_pixel_ratio), + physical_left(p_physical_left), + physical_top(p_physical_top), physical_width(p_physical_width), physical_height(p_physical_height), physical_padding_top(p_physical_padding_top), @@ -51,8 +55,21 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_system_gesture_inset_left(p_physical_system_gesture_inset_left) { } +ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, + double p_physical_left, + double p_physical_top, + double p_physical_width, + double p_physical_height) + : device_pixel_ratio(p_device_pixel_ratio), + physical_left(p_physical_left), + physical_top(p_physical_top), + physical_width(p_physical_width), + physical_height(p_physical_height) {} + bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { return a.device_pixel_ratio == b.device_pixel_ratio && + a.physical_left == b.physical_left && + a.physical_top == b.physical_top && a.physical_width == b.physical_width && a.physical_height == b.physical_height && a.physical_padding_top == b.physical_padding_top && @@ -75,6 +92,7 @@ bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { std::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) { os << "DPR: " << a.device_pixel_ratio << " " + << "Location: [" << a.physical_left << "L, " << a.physical_top << "T] " << "Size: [" << a.physical_width << "W " << a.physical_height << "H] " << "Padding: [" << a.physical_padding_top << "T " << a.physical_padding_right << "R " << a.physical_padding_bottom << "B " diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h index adce20a91cdc9..5b7d604bbc9bc 100644 --- a/lib/ui/window/viewport_metrics.h +++ b/lib/ui/window/viewport_metrics.h @@ -15,6 +15,8 @@ struct ViewportMetrics { double p_physical_width, double p_physical_height); ViewportMetrics(double p_device_pixel_ratio, + double p_physical_left, + double p_physical_top, double p_physical_width, double p_physical_height, double p_physical_padding_top, @@ -30,7 +32,17 @@ struct ViewportMetrics { double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left); + // Create a ViewportMetrics instance that doesn't include depth, padding, or + // insets. + ViewportMetrics(double p_device_pixel_ratio, + double p_physical_left, + double p_physical_top, + double p_physical_width, + double p_physical_height); + double device_pixel_ratio = 1.0; + double physical_left = 0; + double physical_top = 0; double physical_width = 0; double physical_height = 0; double physical_padding_top = 0; diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index 1779998945554..c2e0568d7b586 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -11,7 +11,8 @@ namespace flutter { -Window::Window(ViewportMetrics metrics) : viewport_metrics_(metrics) { +Window::Window(int64_t window_id, ViewportMetrics metrics) + : window_id_(window_id), viewport_metrics_(metrics) { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); } @@ -46,7 +47,10 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { tonic::LogIfError(tonic::DartInvokeField( library_.value(), "_updateWindowMetrics", { + tonic::ToDart(window_id_), tonic::ToDart(metrics.device_pixel_ratio), + tonic::ToDart(metrics.physical_top), + tonic::ToDart(metrics.physical_left), tonic::ToDart(metrics.physical_width), tonic::ToDart(metrics.physical_height), tonic::ToDart(metrics.physical_padding_top), diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index 172cf6b8c2ae5..bccad35e274b3 100644 --- a/lib/ui/window/window.h +++ b/lib/ui/window/window.h @@ -18,10 +18,12 @@ namespace flutter { class Window final { public: - explicit Window(ViewportMetrics metrics); + explicit Window(int64_t window_id, ViewportMetrics metrics); ~Window(); + int window_id() const { return window_id_; } + const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; } void DispatchPointerDataPacket(const PointerDataPacket& packet); @@ -29,6 +31,7 @@ class Window final { private: tonic::DartPersistentValue library_; + int64_t window_id_; ViewportMetrics viewport_metrics_; }; diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index dcb19c8a3165f..aa0aac6371ea9 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -98,6 +98,7 @@ part 'engine/keyboard.dart'; part 'engine/mouse_cursor.dart'; part 'engine/onscreen_logging.dart'; part 'engine/picture.dart'; +part 'engine/platform_dispatcher.dart'; part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; @@ -223,17 +224,17 @@ void initializeEngine() { // part of the rasterization process, particularly in the HTML // renderer, takes place in the `SceneBuilder.build()`. _frameTimingsOnBuildStart(); - if (window._onBeginFrame != null) { - window.invokeOnBeginFrame( + if (EnginePlatformDispatcher.instance._onBeginFrame != null) { + EnginePlatformDispatcher.instance.invokeOnBeginFrame( Duration(microseconds: highResTimeMicroseconds)); } - if (window._onDrawFrame != null) { + if (EnginePlatformDispatcher.instance._onDrawFrame != null) { // TODO(yjbanov): technically Flutter flushes microtasks between // onBeginFrame and onDrawFrame. We don't, which hasn't // been an issue yet, but eventually we'll have to // implement it properly. - window.invokeOnDrawFrame(); + EnginePlatformDispatcher.instance.invokeOnDrawFrame(); } }); } diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 0f054559b18a0..96f0c304950f8 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -71,7 +71,7 @@ class BitmapCanvas extends EngineCanvas { /// Keeps track of what device pixel ratio was used when this [BitmapCanvas] /// was created. - final double _devicePixelRatio = EngineWindow.browserDevicePixelRatio; + final double _devicePixelRatio = EnginePlatformDispatcher.browserDevicePixelRatio; // Compensation for [_initializeViewport] snapping canvas position to 1 pixel. int? _canvasPositionX, _canvasPositionY; @@ -150,13 +150,13 @@ class BitmapCanvas extends EngineCanvas { static int _widthToPhysical(double width) { final double boundsWidth = width + 1; - return (boundsWidth * EngineWindow.browserDevicePixelRatio).ceil() + + return (boundsWidth * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } static int _heightToPhysical(double height) { final double boundsHeight = height + 1; - return (boundsHeight * EngineWindow.browserDevicePixelRatio).ceil() + + return (boundsHeight * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } @@ -198,7 +198,7 @@ class BitmapCanvas extends EngineCanvas { /// * [PersistedPicture._recycleCanvas] which also uses this method /// for the same reason. bool isReusable() { - return _devicePixelRatio == EngineWindow.browserDevicePixelRatio; + return _devicePixelRatio == EnginePlatformDispatcher.browserDevicePixelRatio; } /// Returns a "data://" URI containing a representation of the image in this diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index d56351124eed6..b0c3a7d2f3388 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -99,9 +99,9 @@ class _CanvasPool extends _SaveStackTracking { // * To make sure that when we scale the canvas by devicePixelRatio (see // _initializeViewport below) the pixels line up. final double cssWidth = - _widthInBitmapPixels / EngineWindow.browserDevicePixelRatio; + _widthInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; final double cssHeight = - _heightInBitmapPixels / EngineWindow.browserDevicePixelRatio; + _heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; canvas = html.CanvasElement( width: _widthInBitmapPixels, height: _heightInBitmapPixels, @@ -194,7 +194,7 @@ class _CanvasPool extends _SaveStackTracking { clipTimeTransform[5] != prevTransform[5] || clipTimeTransform[12] != prevTransform[12] || clipTimeTransform[13] != prevTransform[13]) { - final double ratio = EngineWindow.browserDevicePixelRatio; + final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform( clipTimeTransform[0], @@ -223,7 +223,7 @@ class _CanvasPool extends _SaveStackTracking { transform[5] != prevTransform[5] || transform[12] != prevTransform[12] || transform[13] != prevTransform[13]) { - final double ratio = EngineWindow.browserDevicePixelRatio; + final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform(transform[0], transform[1], transform[4], transform[5], transform[12], transform[13]); @@ -307,8 +307,8 @@ class _CanvasPool extends _SaveStackTracking { // This scale makes sure that 1 CSS pixel is translated to the correct // number of bitmap pixels. - ctx.scale(EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio); + ctx.scale(EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio); } void resetTransform() { diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 5f8c4a9b8e73c..a7c8c2f6dfe2f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -292,7 +292,7 @@ class HtmlViewEmbedder { // // HTML elements use logical (CSS) pixels, but we have been using physical // pixels, so scale down the head element to match the logical resolution. - final double scale = EngineWindow.browserDevicePixelRatio; + final double scale = EnginePlatformDispatcher.browserDevicePixelRatio; final double inverseScale = 1 / scale; final Matrix4 scaleMatrix = Matrix4.diagonal3Values(inverseScale, inverseScale, 1); diff --git a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart index eaca5c43ca4b9..74f013ed0ebbb 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart @@ -348,7 +348,7 @@ class SkiaObjects { if (_addedCleanupCallback) { return; } - window.rasterizer!.addPostFrameCallback(postFrameCleanUp); + EnginePlatformDispatcher.instance.rasterizer!.addPostFrameCallback(postFrameCleanUp); _addedCleanupCallback = true; } diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 415ea66c86190..0a35dc3a11262 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -466,7 +466,7 @@ flt-glass-pane * { } _localeSubscription = languageChangeEvent.forTarget(html.window) .listen(_languageDidChange); - window._updateLocales(); + EnginePlatformDispatcher.instance._updateLocales(); } /// Called immediately after browser window metrics change. @@ -481,18 +481,18 @@ flt-glass-pane * { void _metricsDidChange(html.Event? event) { if(isMobile && !window.isRotation() && textEditing.isEditing) { window.computeOnScreenKeyboardInsets(); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); } else { window._computePhysicalSize(); // When physical size changes this value has to be recalculated. window.computeOnScreenKeyboardInsets(); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); } } /// Called immediately after browser window language change. void _languageDidChange(html.Event event) { - window._updateLocales(); + EnginePlatformDispatcher.instance._updateLocales(); if (ui.window.onLocaleChanged != null) { ui.window.onLocaleChanged!(); } diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 11d400bfc5f5b..5684c0f8404f9 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -657,8 +657,8 @@ class _OffscreenCanvas { height: heightInPixels, ); _glCanvas!.className = 'gl-canvas'; - final double cssWidth = widthInPixels / EngineWindow.browserDevicePixelRatio; - final double cssHeight = heightInPixels / EngineWindow.browserDevicePixelRatio; + final double cssWidth = widthInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; + final double cssHeight = heightInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; _glCanvas!.style ..position = 'absolute' ..width = '${cssWidth}px' diff --git a/lib/web_ui/lib/src/engine/html/surface_stats.dart b/lib/web_ui/lib/src/engine/html/surface_stats.dart index 911a825ad03ab..6e524a4f6e266 100644 --- a/lib/web_ui/lib/src/engine/html/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/html/surface_stats.dart @@ -125,9 +125,9 @@ void _debugRepaintSurfaceStatsOverlay(PersistedScene scene) { ..fill(); final double physicalScreenWidth = - html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; + html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; + html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; @@ -296,9 +296,9 @@ void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { return pixels; }).fold(0, (int total, int pixels) => total + pixels); final double physicalScreenWidth = - html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; + html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; + html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; final double screenPixelRatio = pixelCount / physicsScreenPixelCount; diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index 558e53f6f927a..ad9e11204dc43 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -81,7 +81,7 @@ class Keyboard { final html.KeyboardEvent keyboardEvent = event; - if (window._onPlatformMessage == null) { + if (EnginePlatformDispatcher.instance._onPlatformMessage == null) { return; } @@ -135,7 +135,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - window.invokeOnPlatformMessage('flutter/keyevent', + EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } @@ -157,7 +157,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - window.invokeOnPlatformMessage('flutter/keyevent', + EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } } diff --git a/lib/web_ui/lib/src/engine/navigation/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart index 0a578162a9096..5cfc6c6c0af8f 100644 --- a/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -151,8 +151,8 @@ class MultiEntriesBrowserHistory extends BrowserHistory { currentPath); } _lastSeenSerialCount = _currentSerialCount; - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRouteInformation', { @@ -272,8 +272,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _setupFlutterEntry(urlStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(_popRouteMethodCall), (_) {}, @@ -291,8 +291,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _userProvidedRouteName = null; // Send a 'pushRoute' platform message so the app handles it accordingly. - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRoute', newRouteName), diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart new file mode 100644 index 0000000000000..d1e72189df398 --- /dev/null +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -0,0 +1,923 @@ +// 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. + +// @dart = 2.10 +part of engine; + +/// Requests that the browser schedule a frame. +/// +/// This may be overridden in tests, for example, to pump fake frames. +ui.VoidCallback? scheduleFrameCallback; + +/// Platform event dispatcher. +/// +/// This is the central entry point for platform messages and configuration +/// events from the platform. +class EnginePlatformDispatcher extends ui.PlatformDispatcher { + /// Private constructor, since only dart:ui is supposed to create one of + /// these. + EnginePlatformDispatcher._() { + _addBrightnessMediaQueryListener(); + } + + /// The [EnginePlatformDispatcher] singleton. + static EnginePlatformDispatcher get instance => _instance; + static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher._(); + + /// The current platform configuration. + @override + ui.PlatformConfiguration get configuration => _configuration; + ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(locales: parseBrowserLanguages()); + + /// Receives all events related to platform configuration changes. + @override + ui.VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; + ui.VoidCallback? _onPlatformConfigurationChanged; + Zone? _onPlatformConfigurationChangedZone; + @override + set onPlatformConfigurationChanged(ui.VoidCallback? callback) { + _onPlatformConfigurationChanged = callback; + _onPlatformConfigurationChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformConfigurationChanged() { + _invoke(_onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + } + + /// The current list of windows, + Iterable get views => _windows.values; + Map _windows = {}; + + /// A map of opaque platform window identifiers to window configurations. + /// + /// This should be considered a protected member, only to be used by + /// [PlatformDispatcher] subclasses. + Map _windowConfigurations = {}; + + /// A callback that is invoked whenever the platform's [devicePixelRatio], + /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] + /// values change, for example when the device is rotated or when the + /// application is resized (e.g. when showing applications side-by-side + /// on Android). + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + /// + /// The framework registers with this callback and updates the layout + /// appropriately. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// register for notifications when this is called. + /// * [MediaQuery.of], a simpler mechanism for the same. + @override + ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; + ui.VoidCallback? _onMetricsChanged; + Zone? _onMetricsChangedZone; + @override + set onMetricsChanged(ui.VoidCallback? callback) { + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnMetricsChanged() { + if (_onMetricsChanged != null) { + _invoke(_onMetricsChanged, _onMetricsChangedZone); + } + } + + /// Returns device pixel ratio returned by browser. + static double get browserDevicePixelRatio { + double? ratio = html.window.devicePixelRatio as double?; + // Guard against WebOS returning 0 and other browsers returning null. + return (ratio == null || ratio == 0.0) ? 1.0 : ratio; + } + + /// A callback invoked when any window begins a frame. + /// + /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} + /// A callback that is invoked to notify the application that it is an + /// appropriate time to provide a scene using the [SceneBuilder] API and the + /// [PlatformWindow.render] method. + /// When possible, this is driven by the hardware VSync signal of the attached + /// screen with the highest VSync rate. This is only called if + /// [PlatformWindow.scheduleFrame] has been called since the last time this + /// callback was invoked. + /// {@endtemplate} + @override + ui.FrameCallback? get onBeginFrame => _onBeginFrame; + ui.FrameCallback? _onBeginFrame; + Zone? _onBeginFrameZone; + @override + set onBeginFrame(ui.FrameCallback? callback) { + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnBeginFrame(Duration duration) { + _invoke1(_onBeginFrame, _onBeginFrameZone, duration); + } + + /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} + /// A callback that is invoked for each frame after [onBeginFrame] has + /// completed and after the microtask queue has been drained. + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. + /// {@endtemplate} + @override + ui.VoidCallback? get onDrawFrame => _onDrawFrame; + ui.VoidCallback? _onDrawFrame; + Zone? _onDrawFrameZone; + @override + set onDrawFrame(ui.VoidCallback? callback) { + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnDrawFrame() { + _invoke(_onDrawFrame, _onDrawFrameZone); + } + + /// A callback that is invoked when pointer data is available. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [GestureBinding], the Flutter framework class which manages pointer + /// events. + @override + ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + ui.PointerDataPacketCallback? _onPointerDataPacket; + Zone? _onPointerDataPacketZone; + @override + set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPointerDataPacket(ui.PointerDataPacket dataPacket) { + _invoke1(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket); + } + + /// A callback that is invoked to report the [FrameTiming] of recently + /// rasterized frames. + /// + /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use + /// [Window.onReportTimings] directly because + /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. + /// + /// This can be used to see if the application has missed frames (through + /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high + /// latencies (through [FrameTiming.totalSpan]). + /// + /// Unlike [Timeline], the timing information here is available in the release + /// mode (additional to the profile and the debug mode). Hence this can be + /// used to monitor the application's performance in the wild. + /// + /// {@macro dart.ui.TimingsCallback.list} + /// + /// If this is null, no additional work will be done. If this is not null, + /// Flutter spends less than 0.1ms every 1 second to report the timings + /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for + /// 60fps), or 0.01% CPU usage per second. + @override + ui.TimingsCallback? get onReportTimings => _onReportTimings; + ui.TimingsCallback? _onReportTimings; + Zone? _onReportTimingsZone; + @override + set onReportTimings(ui.TimingsCallback? callback) { + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnReportTimings(List timings) { + _invoke1>(_onReportTimings, _onReportTimingsZone, timings); + } + + @override + void sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + _sendPlatformMessage(name, data, _zonedPlatformMessageResponseCallback(callback)); + } + + @override + ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + ui.PlatformMessageCallback? _onPlatformMessage; + Zone? _onPlatformMessageZone; + @override + set onPlatformMessage(ui.PlatformMessageCallback? callback) { + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformMessage(String name, ByteData? data, + ui.PlatformMessageResponseCallback callback) { + _invoke3( + _onPlatformMessage, + _onPlatformMessageZone, + name, + data, + callback, + ); + } + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static ui.PlatformMessageResponseCallback? + _zonedPlatformMessageResponseCallback( + ui.PlatformMessageResponseCallback? callback) { + if (callback == null) + return null; + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + void _sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + // In widget tests we want to bypass processing of platform messages. + if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { + return; + } + + if (_debugPrintPlatformMessages) { + print('Sent platform message on channel: "$name"'); + } + + if (assertionsEnabled && name == 'flutter/debug-echo') { + // Echoes back the data unchanged. Used for testing purposes. + _replyToPlatformMessage(callback, data); + return; + } + + switch (name) { + /// This should be in sync with shell/common/shell.cc + case 'flutter/skia': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'Skia.setResourceCacheMaxBytes': + if (decoded.arguments is int) { + rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); + } + break; + } + return; + + case 'flutter/assets': + assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison + final String url = utf8.decode(data!.buffer.asUint8List()); + ui.webOnlyAssetManager.load(url).then((ByteData assetData) { + _replyToPlatformMessage(callback, assetData); + }, onError: (dynamic error) { + html.window.console + .warn('Error while trying to load an asset: $error'); + _replyToPlatformMessage(callback, null); + }); + return; + + case 'flutter/platform': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'SystemNavigator.pop': + // TODO(gspencergoog): As multi-window support expands, the pop call + // will need to include the window ID. Right now only one window is + // supported. + (_windows[0] as EngineFlutterWindow).browserHistory.exit().then((_) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + }); + return; + case 'HapticFeedback.vibrate': + final String type = decoded.arguments; + domRenderer.vibrate(_getHapticFeedbackDuration(type)); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setApplicationSwitcherDescription': + final Map arguments = decoded.arguments; + domRenderer.setTitle(arguments['label']); + domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setPreferredOrientations': + final List arguments = decoded.arguments; + domRenderer.setPreferredOrientation(arguments).then((bool success) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(success)); + }); + return; + case 'SystemSound.play': + // There are no default system sounds on web. + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'Clipboard.setData': + ClipboardMessageHandler().setDataMethodCall(decoded, callback); + return; + case 'Clipboard.getData': + ClipboardMessageHandler().getDataMethodCall(callback); + return; + } + break; + + // Dispatched by the bindings to delay service worker initialization. + case 'flutter/service_worker': + html.window.dispatchEvent(html.Event('flutter-first-frame')); + return; + + case 'flutter/textinput': + textEditing.channel.handleTextInput(data, callback); + return; + + case 'flutter/mousecursor': + const MethodCodec codec = StandardMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map arguments = decoded.arguments; + switch (decoded.method) { + case 'activateSystemCursor': + MouseCursor.instance!.activateSystemCursor(arguments['kind']); + } + return; + + case 'flutter/web_test_e2e': + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage( + callback, + codec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(codec, data))); + return; + + case 'flutter/platform_views': + if (experimentalUseSkia) { + rasterizer!.surface.viewEmbedder + .handlePlatformViewCall(data, callback); + } else { + ui.handlePlatformViewCall(data!, callback!); + } + return; + + case 'flutter/accessibility': + // In widget tests we want to bypass processing of platform messages. + final StandardMessageCodec codec = StandardMessageCodec(); + accessibilityAnnouncements.handleMessage(codec, data); + _replyToPlatformMessage(callback, codec.encodeMessage(true)); + return; + + case 'flutter/navigation': + // TODO(gspencergoog): As multi-window support expands, the navigation call + // will need to include the window ID. Right now only one window is + // supported. + (_windows[0] as EngineFlutterWindow).handleNavigationMessage(data).then((bool handled) { + if (handled) { + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + } else { + callback?.call(null); + } + }); + + // As soon as Flutter starts taking control of the app navigation, we + // should reset _defaultRouteName to "/" so it doesn't have any + // further effect after this point. + _defaultRouteName = '/'; + return; + } + + if (pluginMessageCallHandler != null) { + pluginMessageCallHandler!(name, data, callback); + return; + } + + // Passing [null] to [callback] indicates that the platform message isn't + // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is + // handled. + _replyToPlatformMessage(callback, null); + } + + + + int _getHapticFeedbackDuration(String type) { + switch (type) { + case 'HapticFeedbackType.lightImpact': + return DomRenderer.vibrateLightImpact; + case 'HapticFeedbackType.mediumImpact': + return DomRenderer.vibrateMediumImpact; + case 'HapticFeedbackType.heavyImpact': + return DomRenderer.vibrateHeavyImpact; + case 'HapticFeedbackType.selectionClick': + return DomRenderer.vibrateSelectionClick; + default: + return DomRenderer.vibrateLongPress; + } + } + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] + /// and [onDrawFrame] callbacks be invoked. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + @override + void scheduleFrame() { + if (scheduleFrameCallback == null) { + throw new Exception( + 'scheduleFrameCallback must be initialized first.'); + } + scheduleFrameCallback!(); + } + + /// Updates the application's rendering on the GPU with the newly provided + /// [Scene]. This function must be called within the scope of the + /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function + /// is called a second time during a single [onBeginFrame]/[onDrawFrame] + /// callback sequence or called outside the scope of those callbacks, the call + /// will be ignored. + /// + /// To record graphical operations, first create a [PictureRecorder], then + /// construct a [Canvas], passing that [PictureRecorder] to its constructor. + /// After issuing all the graphical operations, call the + /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain + /// the final [Picture] that represents the issued graphical operations. + /// + /// Next, create a [SceneBuilder], and add the [Picture] to it using + /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can + /// then obtain a [Scene] object, which you can display to the user via this + /// [render] function. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + /// * [RendererBinding], the Flutter framework class which manages layout and + /// painting. + @override + void render(ui.Scene scene, [ui.FlutterView? view]) { + if (experimentalUseSkia) { + // "Build finish" and "raster start" happen back-to-back because we + // render on the same thread, so there's no overhead from hopping to + // another thread. + // + // CanvasKit works differently from the HTML renderer in that in HTML + // we update the DOM in SceneBuilder.build, which is these function calls + // here are CanvasKit-only. + _frameTimingsOnBuildFinish(); + _frameTimingsOnRasterStart(); + + final LayerScene layerScene = scene as LayerScene; + rasterizer!.draw(layerScene.layerTree); + } else { + final SurfaceScene surfaceScene = scene as SurfaceScene; + domRenderer.renderScene(surfaceScene.webOnlyRootElement); + } + _frameTimingsOnRasterFinish(); + } + + /// Additional accessibility features that may be enabled by the platform. + ui.AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; + + /// A callback that is invoked when the value of [accessibilityFeatures] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; + ui.VoidCallback? _onAccessibilityFeaturesChanged; + Zone? _onAccessibilityFeaturesChangedZone; + set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnAccessibilityFeaturesChanged() { + _invoke(_onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); + } + + /// Change the retained semantics data about this window. + /// + /// If [semanticsEnabled] is true, the user has requested that this function + /// be called whenever the semantic content of this window changes. + /// + /// In either case, this function disposes the given update, which means the + /// semantics update cannot be used further. + void updateSemantics(ui.SemanticsUpdate update) { + EngineSemanticsOwner.instance.updateSemantics(update); + } + + /// We use the first locale in the [locales] list instead of the browser's + /// built-in `navigator.language` because browsers do not agree on the + /// implementation. + /// + /// See also: + /// + /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, + /// which explains browser quirks in the implementation notes. + ui.Locale get locale => locales.first; + + /// The full system-reported supported locales of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// The list is ordered in order of priority, with lower-indexed locales being + /// preferred over higher-indexed ones. The first element is the primary [locale]. + /// + /// The [onLocaleChanged] callback is called whenever this value changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + List get locales => configuration.locales; + + /// Performs the platform-native locale resolution. + /// + /// Each platform may return different results. + /// + /// If the platform fails to resolve a locale, then this will return null. + /// + /// This method returns synchronously and is a direct call to + /// platform specific APIs without invoking method channels. + ui.Locale? computePlatformResolvedLocale(List supportedLocales) { + // TODO(garyq): Implement on web. + return null; + } + + /// A callback that is invoked whenever [locale] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; + ui.VoidCallback? _onLocaleChanged; + Zone? _onLocaleChangedZone; + set onLocaleChanged(ui.VoidCallback? callback) { + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; + } + + /// The locale used when we fail to get the list from the browser. + static const ui.Locale _defaultLocale = const ui.Locale('en', 'US'); + + /// Sets locales to an empty list. + /// + /// The empty list is not a valid value for locales. This is only used for + /// testing locale update logic. + void debugResetLocales() { + _configuration = _configuration.copyWith(locales: const []); + } + + // Called by DomRenderer when browser languages change. + void _updateLocales() { + _configuration = _configuration.copyWith(locales: parseBrowserLanguages()); + } + + static List parseBrowserLanguages() { + // TODO(yjbanov): find a solution for IE + var languages = html.window.navigator.languages; + if (languages == null || languages.isEmpty) { + // To make it easier for the app code, let's not leave the locales list + // empty. This way there's fewer corner cases for apps to handle. + return const [_defaultLocale]; + } + + final List locales = []; + for (final String language in languages) { + final List parts = language.split('-'); + if (parts.length > 1) { + locales.add(ui.Locale(parts.first, parts.last)); + } else { + locales.add(ui.Locale(language)); + } + } + + assert(locales.isNotEmpty); + return locales; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnLocaleChanged() { + _invoke(_onLocaleChanged, _onLocaleChangedZone); + } + + /// The system-reported text scale. + /// + /// This establishes the text scaling factor to use when rendering text, + /// according to the user's platform preferences. + /// + /// The [onTextScaleFactorChanged] callback is called whenever this value + /// changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + double get textScaleFactor => configuration.textScaleFactor; + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + /// + /// This option is used by [showTimePicker]. + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + /// A callback that is invoked whenever [textScaleFactor] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + ui.VoidCallback? _onTextScaleFactorChanged; + Zone? _onTextScaleFactorChangedZone; + set onTextScaleFactorChanged(ui.VoidCallback? callback) { + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnTextScaleFactorChanged() { + _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); + } + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. + ui.Brightness get platformBrightness => configuration.platformBrightness; + + /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] + /// callback if [_platformBrightness] changed. + void _updatePlatformBrightness(ui.Brightness value) { + if (configuration.platformBrightness != value) { + _configuration = configuration.copyWith(platformBrightness: value); + invokeOnPlatformConfigurationChanged(); + invokeOnPlatformBrightnessChanged(); + } + } + + /// Reference to css media query that indicates the user theme preference on the web. + final html.MediaQueryList _brightnessMediaQuery = + html.window.matchMedia('(prefers-color-scheme: dark)'); + + /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. + /// + /// Updates the [_platformBrightness] with the new user preference. + html.EventListener? _brightnessMediaQueryListener; + + /// Set the callback function for listening changes in [_brightnessMediaQuery] value. + void _addBrightnessMediaQueryListener() { + _updatePlatformBrightness(_brightnessMediaQuery.matches + ? ui.Brightness.dark + : ui.Brightness.light); + + _brightnessMediaQueryListener = (html.Event event) { + final html.MediaQueryListEvent mqEvent = + event as html.MediaQueryListEvent; + _updatePlatformBrightness( + mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); + }; + _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); + registerHotRestartListener(() { + _removeBrightnessMediaQueryListener(); + }); + } + + /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. + void _removeBrightnessMediaQueryListener() { + _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); + _brightnessMediaQueryListener = null; + } + + /// A callback that is invoked whenever [platformBrightness] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; + ui.VoidCallback? _onPlatformBrightnessChanged; + Zone? _onPlatformBrightnessChangedZone; + set onPlatformBrightnessChanged(ui.VoidCallback? callback) { + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformBrightnessChanged() { + _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); + } + + /// Whether the user has requested that [updateSemantics] be called when + /// the semantic contents of window changes. + /// + /// The [onSemanticsEnabledChanged] callback is called whenever this value + /// changes. + bool get semanticsEnabled => configuration.semanticsEnabled; + + /// A callback that is invoked when the value of [semanticsEnabled] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + ui.VoidCallback? _onSemanticsEnabledChanged; + Zone? _onSemanticsEnabledChangedZone; + set onSemanticsEnabledChanged(ui.VoidCallback? callback) { + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsEnabledChanged() { + _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); + } + + /// A callback that is invoked whenever the user requests an action to be + /// performed. + /// + /// This callback is used when the user expresses the action they wish to + /// perform based on the semantics supplied by [updateSemantics]. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + ui.SemanticsActionCallback? _onSemanticsAction; + Zone? _onSemanticsActionZone; + set onSemanticsAction(ui.SemanticsActionCallback? callback) { + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsAction( + int id, ui.SemanticsAction action, ByteData? args) { + _invoke3( + _onSemanticsAction, _onSemanticsActionZone, id, action, args); + } + + /// The route or path that the embedder requested when the application was + /// launched. + /// + /// This will be the string "`/`" if no particular route was requested. + /// + /// ## Android + /// + /// On Android, calling + /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `createFlutterView` method in your `FlutterActivity` + /// subclass is a suitable time to set the value. The application's + /// `AndroidManifest.xml` file must also be updated to have a suitable + /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). + /// + /// ## iOS + /// + /// On iOS, calling + /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `application:didFinishLaunchingWithOptions:` method is a + /// suitable time to set this value. + /// + /// See also: + /// + /// * [Navigator], a widget that handles routing. + /// * [SystemChannels.navigation], which handles subsequent navigation + /// requests from the embedder. + String get defaultRouteName { + return _defaultRouteName ??= (_windows[0]! as EngineFlutterWindow).browserHistory.currentPath; + } + + /// Lazily initialized when the `defaultRouteName` getter is invoked. + /// + /// The reason for the lazy initialization is to give enough time for the app + /// to set [locationStrategy] in `lib/src/ui/initialization.dart`. + String? _defaultRouteName; + + @visibleForTesting + late Rasterizer? rasterizer = + experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; + + /// In Flutter, platform messages are exchanged between threads so the + /// messages and responses have to be exchanged asynchronously. We simulate + /// that by adding a zero-length delay to the reply. + void _replyToPlatformMessage( + ui.PlatformMessageResponseCallback? callback, + ByteData? data, + ) { + Future.delayed(Duration.zero).then((_) { + if (callback != null) { + callback(data); + } + }); + } +} + +bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { + final MethodCall decoded = codec.decodeMethodCall(data); + double ratio = double.parse(decoded.arguments); + switch (decoded.method) { + case 'setDevicePixelRatio': + window.debugOverrideDevicePixelRatio(ratio); + EnginePlatformDispatcher.instance.onMetricsChanged!(); + return true; + } + return false; +} + +/// Invokes [callback] inside the given [zone]. +void _invoke(void callback()?, Zone? zone) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(); + } else { + zone!.runGuarded(callback); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg]. +void _invoke1(void callback(A a)?, Zone? zone, A arg) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(arg); + } else { + zone!.runUnaryGuarded(callback, arg); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. +void _invoke3( + void callback(A1 a1, A2 a2, A3 a3)?, + Zone? zone, + A1 arg1, + A2 arg2, + A3 arg3, + ) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone!, Zone.current)) { + callback(arg1, arg2, arg3); + } else { + zone.runGuarded(() { + callback(arg1, arg2, arg3); + }); + } +} + diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index f6bb53110491b..ef0eb4667d779 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -125,9 +125,7 @@ class PointerBinding { void _onPointerData(Iterable data) { final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList()); - if (window._onPointerDataPacket != null) { - window.invokeOnPointerDataPacket(packet); - } + EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(packet); } } diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart index 312938dbc57ef..eeeab3d576d1c 100644 --- a/lib/web_ui/lib/src/engine/profiler.dart +++ b/lib/web_ui/lib/src/engine/profiler.dart @@ -110,7 +110,7 @@ class Profiler { /// Whether we are collecting [ui.FrameTiming]s. bool get _frameTimingsEnabled { - return window._onReportTimings != null; + return EnginePlatformDispatcher.instance._onReportTimings != null; } /// Collects frame timings from frames. @@ -202,7 +202,7 @@ void _frameTimingsOnRasterFinish() { _rasterFinishMicros = -1; if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) { _frameTimingsLastSubmitTime = now; - window.invokeOnReportTimings(_frameTimings); + EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings); _frameTimings = []; } } diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index 77fbb6daf5e47..616663a25d9ac 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -53,11 +53,11 @@ class Incrementable extends RoleManager { final int newInputValue = int.parse(_element.value!); if (newInputValue > _currentSurrogateValue) { _currentSurrogateValue += 1; - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.increase, null); } else if (newInputValue < _currentSurrogateValue) { _currentSurrogateValue -= 1; - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.decrease, null); } }); diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 50624ddd4ba09..9cae3a9189c11 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -53,20 +53,20 @@ class Scrollable extends RoleManager { final int semanticsId = semanticsObject.id; if (doScrollForward) { if (semanticsObject.isVerticalScrollContainer) { - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollUp, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollLeft, null); } } else { if (semanticsObject.isVerticalScrollContainer) { - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollDown, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollRight, null); } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index 57c02b5bf22db..f3b626e9b0535 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1253,8 +1253,8 @@ class EngineSemanticsOwner { _gestureModeClock?.datetime = null; } - if (window._onSemanticsEnabledChanged != null) { - window.invokeOnSemanticsEnabledChanged(); + if (EnginePlatformDispatcher.instance._onSemanticsEnabledChanged != null) { + EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); } } diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 3f58195b1d46e..8439dd3f70f08 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -40,7 +40,7 @@ class Tappable extends RoleManager { GestureMode.browserGestures) { return; } - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); }; element.addEventListener('click', _clickListener); diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 243a154c708b6..91b5d8ba9dc5b 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -148,7 +148,7 @@ class TextField extends RoleManager { } textEditing.useCustomEditableElement(textEditingElement); - window + EnginePlatformDispatcher.instance .invokeOnSemanticsAction(semanticsObject.id, ui.SemanticsAction.tap, null); }); } @@ -186,7 +186,7 @@ class TextField extends RoleManager { if (offsetX * offsetX + offsetY * offsetY < kTouchSlop) { // Recognize it as a tap that requires a keyboard. - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); } } else { diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index ce8b31d5fd23a..baa4b5e506d77 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -276,8 +276,8 @@ class EngineAutofillForm { /// Sends the 'TextInputClient.updateEditingStateWithTag' message to the framework. void _sendAutofillEditingState(String? tag, EditingState editingState) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1363,7 +1363,7 @@ class TextEditingChannel { throw StateError( 'Unsupported method call on the flutter/textinput channel: ${call.method}'); } - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + EnginePlatformDispatcher.instance._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); } /// Used for submitting the forms attached on the DOM. @@ -1392,8 +1392,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.updateEditingState' message to the framework. void updateEditingState(int? clientId, EditingState? editingState) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall('TextInputClient.updateEditingState', [ @@ -1408,8 +1408,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.performAction' message to the framework. void performAction(int? clientId, String? inputAction) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1424,8 +1424,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.onConnectionClosed' message to the framework. void onConnectionClosed(int? clientId) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 277f5484e2f07..22a252c578179 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -482,13 +482,13 @@ final ByteData? _fontChangeMessage = JSONMessageCodec().encodeMessage( sendFontChangeMessage() async { - if (window._onPlatformMessage != null) + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) if (!_fontChangeScheduled) { _fontChangeScheduled = true; // Batch updates into next animationframe. html.window.requestAnimationFrame((num _) { _fontChangeScheduled = false; - window.invokeOnPlatformMessage( + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 19597f6ae32de..ea0d5743c3701 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -6,56 +6,103 @@ part of engine; /// When set to true, all platform messages will be printed to the console. -const bool _debugPrintPlatformMessages = false; +const bool/*!*/ _debugPrintPlatformMessages = false; -/// Requests that the browser schedule a frame. -/// -/// This may be overridden in tests, for example, to pump fake frames. -ui.VoidCallback? scheduleFrameCallback; +/// The Web implementation of [ui.Window]. +// TODO(gspencergoog): Once the framework no longer uses ui.Window, make this extend +// ui.SingletonFlutterWindow instead. +class EngineFlutterWindow extends ui.Window { + EngineFlutterWindow(this._windowId, this.platformDispatcher) { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + engineDispatcher._windows[_windowId] = this; + engineDispatcher._windowConfigurations[_windowId] = ui.ViewConfiguration(); + _addUrlStrategyListener(); + } -typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); + final Object _windowId; + final ui.PlatformDispatcher platformDispatcher; -/// A JavaScript hook to customize the URL strategy of a Flutter app. -// -// Keep this js name in sync with flutter_web_plugins. Find it at: -// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart -// -// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 -@JS('_flutter_web_set_location_strategy') -external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); + void _addUrlStrategyListener() { + _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { + assert( + _browserHistory == null, + 'Cannot set URL strategy more than once.', + ); + final UrlStrategy? strategy = + jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + }); + registerHotRestartListener(() { + _jsSetUrlStrategy = null; + }); + } -UrlStrategy? _createDefaultUrlStrategy() { - return ui.debugEmulateFlutterTesterEnvironment - ? null - : const HashUrlStrategy(); -} + /// Handles the browser history integration to allow users to use the back + /// button, etc. + @visibleForTesting + BrowserHistory get browserHistory { + return _browserHistory ??= + MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); + } -/// The Web implementation of [ui.Window]. -class EngineWindow extends ui.Window { - EngineWindow() { - _addBrightnessMediaQueryListener(); - _addUrlStrategyListener(); + BrowserHistory? _browserHistory; + + Future _useSingleEntryBrowserHistory() async { + if (_browserHistory is SingleEntryBrowserHistory) { + return; + } + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); } - @override - double get devicePixelRatio => - _debugDevicePixelRatio ?? browserDevicePixelRatio; - - /// Returns device pixel ratio returned by browser. - static double get browserDevicePixelRatio { - double? ratio = html.window.devicePixelRatio as double?; - // Guard against WebOS returning 0 and other browsers returning null. - return (ratio == null || ratio == 0.0) ? 1.0 : ratio; + @visibleForTesting + Future debugInitializeHistory( + UrlStrategy? strategy, { + required bool useSingle, + }) async { + await _browserHistory?.tearDown(); + if (useSingle) { + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } else { + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } } - /// Overrides the default device pixel ratio. - /// - /// This is useful in tests to emulate screens of different dimensions. - void debugOverrideDevicePixelRatio(double value) { - _debugDevicePixelRatio = value; + @visibleForTesting + Future debugResetHistory() async { + await _browserHistory?.tearDown(); + _browserHistory = null; } - double? _debugDevicePixelRatio; + Future handleNavigationMessage( + ByteData? data, + ) async { + final MethodCall decoded = JSONMethodCodec().decodeMethodCall(data); + final Map arguments = decoded.arguments; + + switch (decoded.method) { + case 'routeUpdated': + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + return true; + case 'routeInformationUpdated': + assert(browserHistory is MultiEntriesBrowserHistory); + browserHistory.setRouteName( + arguments['location'], + state: arguments['state'], + ); + return true; + } + return false; + } + + @override + ui.ViewConfiguration get viewConfiguration { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + assert(engineDispatcher._windowConfigurations.containsKey(_windowId)); + return engineDispatcher._windowConfigurations[_windowId] ?? ui.ViewConfiguration(); + } @override ui.Size get physicalSize { @@ -164,713 +211,55 @@ class EngineWindow extends ui.Window { /// Overrides the value of [physicalSize] in tests. ui.Size? webOnlyDebugPhysicalSizeOverride; +} - /// Handles the browser history integration to allow users to use the back - /// button, etc. - @visibleForTesting - BrowserHistory get browserHistory { - return _browserHistory ??= - MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); - } - - BrowserHistory? _browserHistory; - - Future _useSingleEntryBrowserHistory() async { - if (_browserHistory is SingleEntryBrowserHistory) { - return; - } - final UrlStrategy? strategy = _browserHistory?.urlStrategy; - await _browserHistory?.tearDown(); - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } - - /// Lazily initialized when the `defaultRouteName` getter is invoked. - /// - /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] - /// in `lib/src/ui/initialization.dart`. - String? _defaultRouteName; - - @override - String get defaultRouteName { - return _defaultRouteName ??= browserHistory.currentPath; - } - - @override - void scheduleFrame() { - if (scheduleFrameCallback == null) { - throw new Exception('scheduleFrameCallback must be initialized first.'); - } - scheduleFrameCallback!(); - } - - @override - ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - ui.VoidCallback? _onTextScaleFactorChanged; - Zone? _onTextScaleFactorChangedZone; - @override - set onTextScaleFactorChanged(ui.VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnTextScaleFactorChanged() { - _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); - } - - @override - ui.VoidCallback? get onPlatformBrightnessChanged => - _onPlatformBrightnessChanged; - ui.VoidCallback? _onPlatformBrightnessChanged; - Zone? _onPlatformBrightnessChangedZone; - @override - set onPlatformBrightnessChanged(ui.VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformBrightnessChanged() { - _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); - } - - @override - ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; - ui.VoidCallback? _onMetricsChanged; - Zone _onMetricsChangedZone = Zone.root; - @override - set onMetricsChanged(ui.VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnMetricsChanged() { - if (window._onMetricsChanged != null) { - _invoke(_onMetricsChanged, _onMetricsChangedZone); - } - } - - @override - ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; - ui.VoidCallback? _onLocaleChanged; - Zone? _onLocaleChangedZone; - @override - set onLocaleChanged(ui.VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; - } - - /// The locale used when we fail to get the list from the browser. - static const _defaultLocale = const ui.Locale('en', 'US'); - - /// We use the first locale in the [locales] list instead of the browser's - /// built-in `navigator.language` because browsers do not agree on the - /// implementation. - /// - /// See also: - /// - /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, - /// which explains browser quirks in the implementation notes. - @override - ui.Locale get locale => _locales!.first; - - @override - List? get locales => _locales; - List? _locales = parseBrowserLanguages(); - - /// Sets locales to `null`. - /// - /// `null` is not a valid value for locales. This is only used for testing - /// locale update logic. - void debugResetLocales() { - _locales = null; - } - - // Called by DomRenderer when browser languages change. - void _updateLocales() { - _locales = parseBrowserLanguages(); - } - - static List parseBrowserLanguages() { - // TODO(yjbanov): find a solution for IE - var languages = html.window.navigator.languages; - if (languages == null || languages.isEmpty) { - // To make it easier for the app code, let's not leave the locales list - // empty. This way there's fewer corner cases for apps to handle. - return const [_defaultLocale]; - } - - final List locales = []; - for (final String language in languages) { - final List parts = language.split('-'); - if (parts.length > 1) { - locales.add(ui.Locale(parts.first, parts.last)); - } else { - locales.add(ui.Locale(language)); - } - } - - assert(locales.isNotEmpty); - return locales; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnLocaleChanged() { - _invoke(_onLocaleChanged, _onLocaleChangedZone); - } - - @override - ui.FrameCallback? get onBeginFrame => _onBeginFrame; - ui.FrameCallback? _onBeginFrame; - Zone? _onBeginFrameZone; - @override - set onBeginFrame(ui.FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnBeginFrame(Duration duration) { - _invoke1(_onBeginFrame, _onBeginFrameZone, duration); - } - - @override - ui.TimingsCallback? get onReportTimings => _onReportTimings; - ui.TimingsCallback? _onReportTimings; - Zone? _onReportTimingsZone; - @override - set onReportTimings(ui.TimingsCallback? callback) { - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnReportTimings(List timings) { - _invoke1>( - _onReportTimings, _onReportTimingsZone, timings); - } - - @override - ui.VoidCallback? get onDrawFrame => _onDrawFrame; - ui.VoidCallback? _onDrawFrame; - Zone? _onDrawFrameZone; - @override - set onDrawFrame(ui.VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnDrawFrame() { - _invoke(_onDrawFrame, _onDrawFrameZone); - } - - @override - ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - ui.PointerDataPacketCallback? _onPointerDataPacket; - Zone? _onPointerDataPacketZone; - @override - set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPointerDataPacket(ui.PointerDataPacket packet) { - _invoke1( - _onPointerDataPacket, _onPointerDataPacketZone, packet); - } - - @override - ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - ui.VoidCallback? _onSemanticsEnabledChanged; - Zone? _onSemanticsEnabledChangedZone; - @override - set onSemanticsEnabledChanged(ui.VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsEnabledChanged() { - _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); - } - - @override - ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - ui.SemanticsActionCallback? _onSemanticsAction; - Zone? _onSemanticsActionZone; - @override - set onSemanticsAction(ui.SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsAction( - int id, ui.SemanticsAction action, ByteData? args) { - _invoke3( - _onSemanticsAction, _onSemanticsActionZone, id, action, args); - } - - @override - ui.VoidCallback? get onAccessibilityFeaturesChanged => - _onAccessibilityFeaturesChanged; - ui.VoidCallback? _onAccessibilityFeaturesChanged; - Zone? _onAccessibilityFeaturesChangedZone; - @override - set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnAccessibilityFeaturesChanged() { - _invoke( - _onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); - } - - @override - ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - ui.PlatformMessageCallback? _onPlatformMessage; - Zone? _onPlatformMessageZone; - @override - set onPlatformMessage(ui.PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformMessage(String name, ByteData? data, - ui.PlatformMessageResponseCallback callback) { - _invoke3( - _onPlatformMessage, - _onPlatformMessageZone, - name, - data, - callback, - ); - } - - @override - void sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - _sendPlatformMessage( - name, data, _zonedPlatformMessageResponseCallback(callback)); - } - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? - _zonedPlatformMessageResponseCallback( - ui.PlatformMessageResponseCallback? callback) { - if (callback == null) { - return null; - } - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; - } - - void _sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - // In widget tests we want to bypass processing of platform messages. - if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { - return; - } - - if (_debugPrintPlatformMessages) { - print('Sent platform message on channel: "$name"'); - } - - if (assertionsEnabled && name == 'flutter/debug-echo') { - // Echoes back the data unchanged. Used for testing purpopses. - _replyToPlatformMessage(callback, data); - return; - } - - switch (name) { - /// This should be in sync with shell/common/shell.cc - case 'flutter/skia': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'Skia.setResourceCacheMaxBytes': - if (decoded.arguments is int) { - rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); - } - break; - } - - return; - case 'flutter/assets': - assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison - final String url = utf8.decode(data!.buffer.asUint8List()); - ui.webOnlyAssetManager.load(url).then((ByteData assetData) { - _replyToPlatformMessage(callback, assetData); - }, onError: (dynamic error) { - html.window.console - .warn('Error while trying to load an asset: $error'); - _replyToPlatformMessage(callback, null); - }); - return; - - case 'flutter/platform': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'SystemNavigator.pop': - browserHistory.exit().then((_) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - }); - return; - case 'HapticFeedback.vibrate': - final String? type = decoded.arguments; - domRenderer.vibrate(_getHapticFeedbackDuration(type)); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setApplicationSwitcherDescription': - final Map arguments = decoded.arguments; - domRenderer.setTitle(arguments['label']); - domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setPreferredOrientations': - final List? arguments = decoded.arguments; - domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(success)); - }); - return; - case 'SystemSound.play': - // There are no default system sounds on web. - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'Clipboard.setData': - ClipboardMessageHandler().setDataMethodCall(decoded, callback); - return; - case 'Clipboard.getData': - ClipboardMessageHandler().getDataMethodCall(callback); - return; - } - break; - - // Dispatched by the bindings to delay service worker initialization. - case 'flutter/service_worker': - html.window.dispatchEvent(html.Event('flutter-first-frame')); - return; - - case 'flutter/textinput': - textEditing.channel.handleTextInput(data, callback); - return; - - case 'flutter/mousecursor': - const MethodCodec codec = StandardMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map? arguments = decoded.arguments; - switch (decoded.method) { - case 'activateSystemCursor': - MouseCursor.instance!.activateSystemCursor(arguments!['kind']); - } - return; - - case 'flutter/web_test_e2e': - const MethodCodec codec = JSONMethodCodec(); - _replyToPlatformMessage( - callback, - codec.encodeSuccessEnvelope( - _handleWebTestEnd2EndMessage(codec, data))); - return; - - case 'flutter/platform_views': - if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder - .handlePlatformViewCall(data, callback); - } else { - ui.handlePlatformViewCall(data!, callback!); - } - return; - - case 'flutter/accessibility': - // In widget tests we want to bypass processing of platform messages. - final StandardMessageCodec codec = StandardMessageCodec(); - accessibilityAnnouncements.handleMessage(codec, data); - _replyToPlatformMessage(callback, codec.encodeMessage(true)); - return; - - case 'flutter/navigation': - _handleNavigationMessage(data, callback).then((handled) { - if (!handled && callback != null) { - callback(null); - } - }); - // As soon as Flutter starts taking control of the app navigation, we - // should reset [_defaultRouteName] to "/" so it doesn't have any - // further effect after this point. - _defaultRouteName = '/'; - return; - } - - if (pluginMessageCallHandler != null) { - pluginMessageCallHandler!(name, data, callback); - return; - } - - // Passing [null] to [callback] indicates that the platform message isn't - // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is - // handled. - _replyToPlatformMessage(callback, null); - } - - @visibleForTesting - Future debugInitializeHistory( - UrlStrategy? strategy, { - required bool useSingle, - }) async { - await _browserHistory?.tearDown(); - if (useSingle) { - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } else { - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - } - } - - @visibleForTesting - Future debugResetHistory() async { - await _browserHistory?.tearDown(); - _browserHistory = null; - } - - Future _handleNavigationMessage( - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) async { - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map arguments = decoded.arguments; +typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); - switch (decoded.method) { - case 'routeUpdated': - await _useSingleEntryBrowserHistory(); - browserHistory.setRouteName(arguments['routeName']); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - case 'routeInformationUpdated': - assert(browserHistory is MultiEntriesBrowserHistory); - browserHistory.setRouteName( - arguments['location'], - state: arguments['state'], - ); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - } - return false; - } +/// A JavaScript hook to customize the URL strategy of a Flutter app. +// +// Keep this js name in sync with flutter_web_plugins. Find it at: +// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +// +// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 +@JS('_flutter_web_set_location_strategy') +external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); - int _getHapticFeedbackDuration(String? type) { - switch (type) { - case 'HapticFeedbackType.lightImpact': - return DomRenderer.vibrateLightImpact; - case 'HapticFeedbackType.mediumImpact': - return DomRenderer.vibrateMediumImpact; - case 'HapticFeedbackType.heavyImpact': - return DomRenderer.vibrateHeavyImpact; - case 'HapticFeedbackType.selectionClick': - return DomRenderer.vibrateSelectionClick; - default: - return DomRenderer.vibrateLongPress; - } - } +UrlStrategy? _createDefaultUrlStrategy() { + return ui.debugEmulateFlutterTesterEnvironment + ? null + : const HashUrlStrategy(); +} - /// In Flutter, platform messages are exchanged between threads so the - /// messages and responses have to be exchanged asynchronously. We simulate - /// that by adding a zero-length delay to the reply. - void _replyToPlatformMessage( - ui.PlatformMessageResponseCallback? callback, - ByteData? data, - ) { - Future.delayed(Duration.zero).then((_) { - if (callback != null) { - callback(data); - } - }); - } +/// The Web implementation of [ui.Window]. +class EngineSingletonFlutterWindow extends EngineFlutterWindow { + EngineSingletonFlutterWindow(Object windowId, ui.PlatformDispatcher platformDispatcher) : super(windowId, platformDispatcher); @override - ui.Brightness get platformBrightness => _platformBrightness; - ui.Brightness _platformBrightness = ui.Brightness.light; - - /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] - /// callback if [_platformBrightness] changed. - void _updatePlatformBrightness(ui.Brightness newPlatformBrightness) { - ui.Brightness previousPlatformBrightness = _platformBrightness; - _platformBrightness = newPlatformBrightness; - - if (previousPlatformBrightness != _platformBrightness && - onPlatformBrightnessChanged != null) { - invokeOnPlatformBrightnessChanged(); - } - } + double get devicePixelRatio => _debugDevicePixelRatio ?? EnginePlatformDispatcher.browserDevicePixelRatio; - /// Reference to css media query that indicates the user theme preference on the web. - final html.MediaQueryList _brightnessMediaQuery = - html.window.matchMedia('(prefers-color-scheme: dark)'); - - /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. + /// Overrides the default device pixel ratio. /// - /// Updates the [_platformBrightness] with the new user preference. - html.EventListener? _brightnessMediaQueryListener; - - /// Set the callback function for listening changes in [_brightnessMediaQuery] value. - void _addBrightnessMediaQueryListener() { - _updatePlatformBrightness(_brightnessMediaQuery.matches - ? ui.Brightness.dark - : ui.Brightness.light); - - _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = - event as html.MediaQueryListEvent; - _updatePlatformBrightness( - mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); - }; - _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); - registerHotRestartListener(() { - _removeBrightnessMediaQueryListener(); - }); - } - - void _addUrlStrategyListener() { - _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { - assert( - _browserHistory == null, - 'Cannot set URL strategy more than once.', - ); - final UrlStrategy? strategy = - jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - }); - registerHotRestartListener(() { - _jsSetUrlStrategy = null; - }); - } - - /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. - void _removeBrightnessMediaQueryListener() { - _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); - _brightnessMediaQueryListener = null; - } - - @override - void render(ui.Scene scene) { - if (experimentalUseSkia) { - // "Build finish" and "raster start" happen back-to-back because we - // render on the same thread, so there's no overhead from hopping to - // another thread. - // - // CanvasKit works differently from the HTML renderer in that in HTML - // we update the DOM in SceneBuilder.build, which is these function calls - // here are CanvasKit-only. - _frameTimingsOnBuildFinish(); - _frameTimingsOnRasterStart(); - - final LayerScene layerScene = scene as LayerScene; - rasterizer!.draw(layerScene.layerTree); - } else { - final SurfaceScene surfaceScene = scene as SurfaceScene; - domRenderer.renderScene(surfaceScene.webOnlyRootElement); - } - _frameTimingsOnRasterFinish(); - } - - @visibleForTesting - late Rasterizer? rasterizer = - experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; -} - -bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { - final MethodCall decoded = codec.decodeMethodCall(data); - double ratio = double.parse(decoded.arguments); - switch (decoded.method) { - case 'setDevicePixelRatio': - window.debugOverrideDevicePixelRatio(ratio); - window.onMetricsChanged!(); - return true; - } - return false; -} - -/// Invokes [callback] inside the given [zone]. -void _invoke(void callback()?, Zone? zone) { - if (callback == null) { - return; + /// This is useful in tests to emulate screens of different dimensions. + void debugOverrideDevicePixelRatio(double value) { + _debugDevicePixelRatio = value; } - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(); - } else { - zone!.runGuarded(callback); - } + double? _debugDevicePixelRatio; } -/// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1(void callback(A a)?, Zone? zone, A arg) { - if (callback == null) { - return; - } - - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(arg); - } else { - zone!.runUnaryGuarded(callback, arg); - } -} +/// A type of [FlutterView] that can be hosted inside of a [FlutterWindow]. +class EngineFlutterWindowView extends ui.FlutterWindow { + EngineFlutterWindowView._(this._viewId, this.platformDispatcher); -/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, - A1 arg1, A2 arg2, A3 arg3) { - if (callback == null) { - return; - } + final Object _viewId; - assert(zone != null); + final ui.PlatformDispatcher platformDispatcher; - if (identical(zone, Zone.current)) { - callback(arg1, arg2, arg3); - } else { - zone!.runGuarded(() { - callback(arg1, arg2, arg3); - }); + @override + ui.ViewConfiguration get viewConfiguration { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + assert(engineDispatcher._windowConfigurations.containsKey(_viewId)); + return engineDispatcher._windowConfigurations[_viewId] ?? ui.ViewConfiguration(); } } @@ -879,7 +268,7 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, /// `dart:ui` window delegates to this value. However, this value has a wider /// API surface, providing Web-specific functionality that the standard /// `dart:ui` version does not. -final EngineWindow window = EngineWindow(); +final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); /// The Web implementation of [ui.WindowPadding]. class WindowPadding implements ui.WindowPadding { diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart new file mode 100644 index 0000000000000..1ec3fdb09da82 --- /dev/null +++ b/lib/web_ui/lib/src/ui/platform_dispatcher.dart @@ -0,0 +1,420 @@ +// 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. + +// @dart = 2.10 +part of ui; + +typedef VoidCallback = void Function(); +typedef FrameCallback = void Function(Duration duration); +typedef TimingsCallback = void Function(List timings); +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); +typedef PlatformMessageResponseCallback = void Function(ByteData? data); +typedef PlatformMessageCallback = void Function( + String name, ByteData? data, PlatformMessageResponseCallback? callback); +typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); + +abstract class PlatformDispatcher { + static PlatformDispatcher get instance => engine.EnginePlatformDispatcher.instance; + + PlatformConfiguration get configuration; + VoidCallback? get onPlatformConfigurationChanged; + set onPlatformConfigurationChanged(VoidCallback? callback); + + Iterable get views; + + VoidCallback? get onMetricsChanged; + set onMetricsChanged(VoidCallback? callback); + + FrameCallback? get onBeginFrame; + set onBeginFrame(FrameCallback? callback); + + VoidCallback? get onDrawFrame; + set onDrawFrame(VoidCallback? callback); + + PointerDataPacketCallback? get onPointerDataPacket; + set onPointerDataPacket(PointerDataPacketCallback? callback); + + TimingsCallback? get onReportTimings; + set onReportTimings(TimingsCallback? callback); + + void sendPlatformMessage( + String name, + ByteData? data, + PlatformMessageResponseCallback? callback, + ); + + PlatformMessageCallback? get onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback); + + void setIsolateDebugName(String name) {} + + ByteData? getPersistentIsolateData() => null; + + void scheduleFrame(); + + void render(Scene scene, [FlutterView view]); + + AccessibilityFeatures get accessibilityFeatures; + + VoidCallback? get onAccessibilityFeaturesChanged; + set onAccessibilityFeaturesChanged(VoidCallback? callback); + + void updateSemantics(SemanticsUpdate update); + + Locale get locale; + + List get locales => configuration.locales; + + Locale? computePlatformResolvedLocale(List supportedLocales); + + VoidCallback? get onLocaleChanged; + set onLocaleChanged(VoidCallback? callback); + + String get initialLifecycleState => 'AppLifecycleState.resumed'; + + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + double get textScaleFactor => configuration.textScaleFactor; + + VoidCallback? get onTextScaleFactorChanged; + set onTextScaleFactorChanged(VoidCallback? callback); + + Brightness get platformBrightness => configuration.platformBrightness; + + VoidCallback? get onPlatformBrightnessChanged; + set onPlatformBrightnessChanged(VoidCallback? callback); + + bool get semanticsEnabled => configuration.semanticsEnabled; + + VoidCallback? get onSemanticsEnabledChanged; + set onSemanticsEnabledChanged(VoidCallback? callback); + + SemanticsActionCallback? get onSemanticsAction; + set onSemanticsAction(SemanticsActionCallback? callback); + + String get defaultRouteName; +} + +class PlatformConfiguration { + const PlatformConfiguration({ + this.accessibilityFeatures = const AccessibilityFeatures._(0), + this.alwaysUse24HourFormat = false, + this.semanticsEnabled = false, + this.platformBrightness = Brightness.light, + this.textScaleFactor = 1.0, + this.locales = const [], + this.defaultRouteName = '/', + }); + + PlatformConfiguration copyWith({ + AccessibilityFeatures? accessibilityFeatures, + bool? alwaysUse24HourFormat, + bool? semanticsEnabled, + Brightness? platformBrightness, + double? textScaleFactor, + List? locales, + String? defaultRouteName, + }) { + return PlatformConfiguration( + accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, + alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, + semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, + platformBrightness: platformBrightness ?? this.platformBrightness, + textScaleFactor: textScaleFactor ?? this.textScaleFactor, + locales: locales ?? this.locales, + defaultRouteName: defaultRouteName ?? this.defaultRouteName, + ); + } + + final AccessibilityFeatures accessibilityFeatures; + final bool alwaysUse24HourFormat; + final bool semanticsEnabled; + final Brightness platformBrightness; + final double textScaleFactor; + final List locales; + final String defaultRouteName; +} + +class ViewConfiguration { + const ViewConfiguration({ + this.window, + this.devicePixelRatio = 1.0, + this.geometry = Rect.zero, + this.visible = false, + this.viewInsets = WindowPadding.zero, + this.viewPadding = WindowPadding.zero, + this.systemGestureInsets = WindowPadding.zero, + this.padding = WindowPadding.zero, + }); + + ViewConfiguration copyWith({ + FlutterWindow? window, + double? devicePixelRatio, + Rect? geometry, + bool? visible, + WindowPadding? viewInsets, + WindowPadding? viewPadding, + WindowPadding? systemGestureInsets, + WindowPadding? padding, + }) { + return ViewConfiguration( + window: window ?? this.window, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, + geometry: geometry ?? this.geometry, + visible: visible ?? this.visible, + viewInsets: viewInsets ?? this.viewInsets, + viewPadding: viewPadding ?? this.viewPadding, + systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, + padding: padding ?? this.padding, + ); + } + + final FlutterWindow? window; + final double devicePixelRatio; + final Rect geometry; + final bool visible; + final WindowPadding viewInsets; + final WindowPadding viewPadding; + final WindowPadding systemGestureInsets; + final WindowPadding padding; + + @override + String toString() { + return '$runtimeType[window: $window, geometry: $geometry]'; + } +} + +enum FramePhase { + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish, +} + +class FrameTiming { + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + + FrameTiming._(this._timestamps) + : assert(_timestamps.length == FramePhase.values.length); + + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + + Duration get buildDuration => + _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + + Duration get rasterDuration => + _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + + Duration get totalSpan => + _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + +enum AppLifecycleState { + resumed, + inactive, + paused, + detached, +} + +abstract class WindowPadding { + const factory WindowPadding._( + {required double left, + required double top, + required double right, + required double bottom}) = engine.WindowPadding; + + double get left; + double get top; + double get right; + double get bottom; + + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); + + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +class Locale { + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + + final String? scriptCode; + + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + return other is Locale + && other.languageCode == languageCode + && other.scriptCode == scriptCode + && other.countryCode == countryCode; + } + + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode); + + @override + String toString() => _rawToString('_'); + + // TODO(yjbanov): implement to match flutter native. + String toLanguageTag() => _rawToString('-'); + + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null) { + out.write('$separator$scriptCode'); + } + if (_countryCode != null) { + out.write('$separator$countryCode'); + } + return out.toString(); + } +} \ No newline at end of file diff --git a/lib/web_ui/lib/src/ui/text.dart b/lib/web_ui/lib/src/ui/text.dart index abf00f154674b..eaa9f449db7a4 100644 --- a/lib/web_ui/lib/src/ui/text.dart +++ b/lib/web_ui/lib/src/ui/text.dart @@ -1,7 +1,6 @@ // 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. -// Synced 2019-05-30T14:20:57.833907. // @dart = 2.10 part of ui; diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index fa58ff01e5537..cc7a45dcc65db 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -1,264 +1,131 @@ // 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. -// Synced 2019-05-30T14:20:57.841444. // @dart = 2.10 part of ui; -typedef VoidCallback = void Function(); -typedef FrameCallback = void Function(Duration duration); -typedef TimingsCallback = void Function(List timings); -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); -typedef PlatformMessageResponseCallback = void Function(ByteData? data); -typedef PlatformMessageCallback = void Function( - String name, ByteData? data, PlatformMessageResponseCallback? callback); - -enum AppLifecycleState { - resumed, - inactive, - paused, - detached, +abstract class FlutterView { + PlatformDispatcher get platformDispatcher; + ViewConfiguration get viewConfiguration; + double get devicePixelRatio => viewConfiguration.devicePixelRatio; + Rect get physicalGeometry => viewConfiguration.geometry; + Size get physicalSize => viewConfiguration.geometry.size; + WindowPadding get viewInsets => viewConfiguration.viewInsets; + WindowPadding get viewPadding => viewConfiguration.viewPadding; + WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; + WindowPadding get padding => viewConfiguration.padding; + void render(Scene scene) => platformDispatcher.render(scene, this); } -abstract class WindowPadding { - const factory WindowPadding._({ - required double left, - required double top, - required double right, - required double bottom, - }) = engine.WindowPadding; - - double get left; - double get top; - double get right; - double get bottom; - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); +abstract class FlutterWindow extends FlutterView { + @override + PlatformDispatcher get platformDispatcher; @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } + ViewConfiguration get viewConfiguration; } -class Locale { - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - final String? scriptCode; - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; +abstract class SingletonFlutterWindow extends FlutterWindow { + VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; + set onMetricsChanged(VoidCallback? callback) { + platformDispatcher.onMetricsChanged = callback; + } - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is Locale - && other.languageCode == languageCode - && other.scriptCode == scriptCode - && other.countryCode == countryCode; + Locale? get locale => platformDispatcher.locale; + List? get locales => platformDispatcher.locales; + + Locale? computePlatformResolvedLocale(List supportedLocales) { + return platformDispatcher.computePlatformResolvedLocale(supportedLocales); } - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode); + VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; + set onLocaleChanged(VoidCallback? callback) { + platformDispatcher.onLocaleChanged = callback; + } - @override - String toString() => _rawToString('_'); + String get initialLifecycleState => platformDispatcher.initialLifecycleState; - // TODO(yjbanov): implement to match flutter native. - String toLanguageTag() => _rawToString('-'); + double get textScaleFactor => platformDispatcher.textScaleFactor; + bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null) { - out.write('$separator$scriptCode'); - } - if (_countryCode != null) { - out.write('$separator$countryCode'); - } - return out.toString(); + VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; + set onTextScaleFactorChanged(VoidCallback? callback) { + platformDispatcher.onTextScaleFactorChanged = callback; } -} -abstract class Window { - double get devicePixelRatio; - Size get physicalSize; - WindowPadding get viewInsets => WindowPadding.zero; - - WindowPadding get viewPadding => WindowPadding.zero; - - WindowPadding get systemGestureInsets => WindowPadding.zero; - WindowPadding get padding => WindowPadding.zero; - double get textScaleFactor => _textScaleFactor; - double _textScaleFactor = 1.0; - bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; - bool _alwaysUse24HourFormat = false; - VoidCallback? get onTextScaleFactorChanged; - set onTextScaleFactorChanged(VoidCallback? callback); - Brightness get platformBrightness; - VoidCallback? get onPlatformBrightnessChanged; - set onPlatformBrightnessChanged(VoidCallback? callback); - VoidCallback? get onMetricsChanged; - set onMetricsChanged(VoidCallback? callback); - Locale? get locale; - List? get locales; - Locale? computePlatformResolvedLocale(List supportedLocales) { - // TODO(garyq): Implement on web. - return null; + Brightness get platformBrightness => platformDispatcher.platformBrightness; + + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; + set onPlatformBrightnessChanged(VoidCallback? callback) { + platformDispatcher.onPlatformBrightnessChanged = callback; } - VoidCallback? get onLocaleChanged; - set onLocaleChanged(VoidCallback? callback); - void scheduleFrame(); - FrameCallback? get onBeginFrame; - set onBeginFrame(FrameCallback? callback); - TimingsCallback? get onReportTimings; - set onReportTimings(TimingsCallback? callback); - VoidCallback? get onDrawFrame; - set onDrawFrame(VoidCallback? callback); - PointerDataPacketCallback? get onPointerDataPacket; - set onPointerDataPacket(PointerDataPacketCallback? callback); - String get defaultRouteName; - bool get semanticsEnabled => engine.EngineSemanticsOwner.instance.semanticsEnabled; - VoidCallback? get onSemanticsEnabledChanged; - set onSemanticsEnabledChanged(VoidCallback? callback); - SemanticsActionCallback? get onSemanticsAction; - set onSemanticsAction(SemanticsActionCallback? callback); - VoidCallback? get onAccessibilityFeaturesChanged; - set onAccessibilityFeaturesChanged(VoidCallback? callback); - PlatformMessageCallback? get onPlatformMessage; - set onPlatformMessage(PlatformMessageCallback? callback); - void updateSemantics(SemanticsUpdate update) { - engine.EngineSemanticsOwner.instance.updateSemantics(update); + FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; + set onBeginFrame(FrameCallback? callback) { + platformDispatcher.onBeginFrame = callback; } + VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; + set onDrawFrame(VoidCallback? callback) { + platformDispatcher.onDrawFrame = callback; + } + + TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; + set onReportTimings(TimingsCallback? callback) { + platformDispatcher.onReportTimings = callback; + } + + PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; + set onPointerDataPacket(PointerDataPacketCallback? callback) { + platformDispatcher.onPointerDataPacket = callback; + } + + String get defaultRouteName => platformDispatcher.defaultRouteName; + + void scheduleFrame() => platformDispatcher.scheduleFrame(); + void render(Scene scene) => platformDispatcher.render(scene, this); + + bool get semanticsEnabled => platformDispatcher.semanticsEnabled; + + VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; + set onSemanticsEnabledChanged(VoidCallback? callback) { + platformDispatcher.onSemanticsEnabledChanged = callback; + } + + SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; + set onSemanticsAction(SemanticsActionCallback? callback) { + platformDispatcher.onSemanticsAction = callback; + } + + AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; + + VoidCallback? get onAccessibilityFeaturesChanged => + platformDispatcher.onAccessibilityFeaturesChanged; + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + platformDispatcher.onAccessibilityFeaturesChanged = callback; + } + + void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); + void sendPlatformMessage( String name, ByteData? data, PlatformMessageResponseCallback? callback, - ); - AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; - AccessibilityFeatures _accessibilityFeatures = AccessibilityFeatures._(0); - void render(Scene scene); - - String get initialLifecycleState => 'AppLifecycleState.resumed'; + ) { + platformDispatcher.sendPlatformMessage(name, data, callback); + } - void setIsolateDebugName(String name) {} + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback) { + platformDispatcher.onPlatformMessage = callback; + } - ByteData? getPersistentIsolateData() => null; + void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } +abstract class Window extends SingletonFlutterWindow {} + class AccessibilityFeatures { const AccessibilityFeatures._(this._index); @@ -271,6 +138,7 @@ class AccessibilityFeatures { // A bitfield which represents each enabled feature. final int _index; + bool get accessibleNavigation => _kAccessibleNavigation & _index != 0; bool get invertColors => _kInvertColorsIndex & _index != 0; bool get disableAnimations => _kDisableAnimationsIndex & _index != 0; @@ -352,7 +220,6 @@ class PluginUtilities { } } -// TODO(flutter_web): probably dont implement this one. class IsolateNameServer { // This class is only a namespace, and should not be instantiated or // extended directly. @@ -371,50 +238,4 @@ class IsolateNameServer { } } -enum FramePhase { - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish, -} - -class FrameTiming { - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - FrameTiming._(List timestamps) - : assert(timestamps.length == FramePhase.values.length), - _timestamps = timestamps; - - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - Window get window => engine.window; diff --git a/lib/web_ui/lib/ui.dart b/lib/web_ui/lib/ui.dart index 13d5ee2813b20..b616c223212d5 100644 --- a/lib/web_ui/lib/ui.dart +++ b/lib/web_ui/lib/ui.dart @@ -29,6 +29,7 @@ part 'src/ui/natives.dart'; part 'src/ui/painting.dart'; part 'src/ui/path.dart'; part 'src/ui/path_metrics.dart'; +part 'src/ui/platform_dispatcher.dart'; part 'src/ui/pointer.dart'; part 'src/ui/semantics.dart'; part 'src/ui/test_embedding.dart'; diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index a47246c24894b..6bd2321768ae4 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -50,7 +50,7 @@ void _tests() { when(mockRasterizer.addPostFrameCallback(any)).thenAnswer((_) { addPostFrameCallbackCount++; }); - window.rasterizer = mockRasterizer; + EnginePlatformDispatcher.instance.rasterizer = mockRasterizer; // Trigger first create final TestSkiaObject testObject = TestSkiaObject(); diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index f5be42ae61db7..ff2f7674d4a88 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -292,7 +292,7 @@ void testMain() { expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); expect(strategy.currentEntry.url, '/home'); - await routeInfomrationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page1', 'page1 state'); // Should have two history entries now. expect(strategy.history, hasLength(2)); expect(strategy.currentEntryIndex, 1); @@ -329,8 +329,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInfomrationUpdated('/page1', 'page1 state'); - await routeInfomrationUpdated('/page2', 'page2 state'); + await routeInformationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -426,8 +426,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInfomrationUpdated('/page1', 'page1 state'); - await routeInfomrationUpdated('/page2', 'page2 state'); + await routeInformationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -522,7 +522,7 @@ Future routeUpdated(String routeName) { return completer.future; } -Future routeInfomrationUpdated(String location, dynamic state) { +Future routeInformationUpdated(String location, dynamic state) { final Completer completer = Completer(); window.sendPlatformMessage( 'flutter/navigation', diff --git a/lib/web_ui/test/engine/surface/platform_view_test.dart b/lib/web_ui/test/engine/surface/platform_view_test.dart index eeda48709da7e..c806d67658a78 100644 --- a/lib/web_ui/test/engine/surface/platform_view_test.dart +++ b/lib/web_ui/test/engine/surface/platform_view_test.dart @@ -15,7 +15,7 @@ import 'package:test/test.dart'; import '../../matchers.dart'; const MethodCodec codec = StandardMethodCodec(); -final EngineWindow window = EngineWindow(); +final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 15e381440125a..e61c9f1e42289 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -31,7 +31,7 @@ void testMain() { expect(window.onTextScaleFactorChanged, same(callback)); }); - window.invokeOnTextScaleFactorChanged(); + EnginePlatformDispatcher.instance.invokeOnTextScaleFactorChanged(); }); test('onPlatformBrightnessChanged preserves the zone', () { @@ -47,7 +47,7 @@ void testMain() { expect(window.onPlatformBrightnessChanged, same(callback)); }); - window.invokeOnPlatformBrightnessChanged(); + EnginePlatformDispatcher.instance.invokeOnPlatformBrightnessChanged(); }); test('onMetricsChanged preserves the zone', () { @@ -63,7 +63,7 @@ void testMain() { expect(window.onMetricsChanged, same(callback)); }); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); }); test('onLocaleChanged preserves the zone', () { @@ -79,7 +79,7 @@ void testMain() { expect(window.onLocaleChanged, same(callback)); }); - window.invokeOnLocaleChanged(); + EnginePlatformDispatcher.instance.invokeOnLocaleChanged(); }); test('onBeginFrame preserves the zone', () { @@ -95,7 +95,7 @@ void testMain() { expect(window.onBeginFrame, same(callback)); }); - window.invokeOnBeginFrame(null); + EnginePlatformDispatcher.instance.invokeOnBeginFrame(null); }); test('onReportTimings preserves the zone', () { @@ -111,7 +111,7 @@ void testMain() { expect(window.onReportTimings, same(callback)); }); - window.invokeOnReportTimings(null); + EnginePlatformDispatcher.instance.invokeOnReportTimings(null); }); test('onDrawFrame preserves the zone', () { @@ -127,7 +127,7 @@ void testMain() { expect(window.onDrawFrame, same(callback)); }); - window.invokeOnDrawFrame(); + EnginePlatformDispatcher.instance.invokeOnDrawFrame(); }); test('onPointerDataPacket preserves the zone', () { @@ -143,7 +143,7 @@ void testMain() { expect(window.onPointerDataPacket, same(callback)); }); - window.invokeOnPointerDataPacket(null); + EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(null); }); test('onSemanticsEnabledChanged preserves the zone', () { @@ -159,7 +159,7 @@ void testMain() { expect(window.onSemanticsEnabledChanged, same(callback)); }); - window.invokeOnSemanticsEnabledChanged(); + EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); }); test('onSemanticsAction preserves the zone', () { @@ -175,7 +175,7 @@ void testMain() { expect(window.onSemanticsAction, same(callback)); }); - window.invokeOnSemanticsAction(null, null, null); + EnginePlatformDispatcher.instance.invokeOnSemanticsAction(null, null, null); }); test('onAccessibilityFeaturesChanged preserves the zone', () { @@ -191,7 +191,7 @@ void testMain() { expect(window.onAccessibilityFeaturesChanged, same(callback)); }); - window.invokeOnAccessibilityFeaturesChanged(); + EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged(); }); test('onPlatformMessage preserves the zone', () { @@ -207,7 +207,7 @@ void testMain() { expect(window.onPlatformMessage, same(callback)); }); - window.invokeOnPlatformMessage(null, null, null); + EnginePlatformDispatcher.instance.invokeOnPlatformMessage(null, null, null); }); test('sendPlatformMessage preserves the zone', () async { @@ -290,8 +290,8 @@ void testMain() { // Trigger a change notification (reset locales because the notification // doesn't actually change the list of languages; the test only observes // that the list is populated again). - window.debugResetLocales(); - expect(window.locales, null); + EnginePlatformDispatcher.instance.debugResetLocales(); + expect(window.locales, isEmpty); expect(localeChangedCount, 0); html.window.dispatchEvent(html.Event('languagechange')); expect(window.locales, isNotEmpty); diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index a072d025974fd..a9dca67448268 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -227,8 +227,8 @@ void testMain() async { ); final SceneBuilder sb = SceneBuilder(); - sb.pushTransform(Matrix4.diagonal3Values(EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); + sb.pushTransform(Matrix4.diagonal3Values(EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); sb.pushTransform(Matrix4.rotationZ(math.pi / 2).toFloat64()); sb.pushOffset(0, -500); sb.pushClipRect(canvasSize); diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 04dce71d0b986..d9f706f3ff9e5 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -412,8 +412,8 @@ void _testCullRectComputation() { final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushTransform(Matrix4.diagonal3Values( - EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); + EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); // TODO(yjbanov): see the TODO below. // final double screenWidth = html.window.innerWidth.toDouble(); diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index ef0a755f550cf..54f57cf9073ee 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -31,7 +31,7 @@ void testMain() { final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/initial'), ); - await window.debugInitializeHistory(strategy, useSingle: true); + await EnginePlatformDispatcher.instance.debugInitializeHistory(strategy, useSingle: true); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. @@ -41,7 +41,7 @@ void testMain() { test('window.defaultRouteName should reset after navigation platform message', () async { - await window.debugInitializeHistory(TestUrlStrategy.fromEntry( + await EnginePlatformDispatcher.instance.debugInitializeHistory(TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/initial'), ), useSingle: true); // Reading it multiple times should return the same value. @@ -64,16 +64,16 @@ void testMain() { // Disable URL strategy. expect(() => jsSetUrlStrategy(null), returnsNormally); // History should be initialized. - expect(window.browserHistory, isNotNull); + expect(EnginePlatformDispatcher.instance.browserHistory, isNotNull); // But without a URL strategy. - expect(window.browserHistory.urlStrategy, isNull); + expect(EnginePlatformDispatcher.instance.browserHistory.urlStrategy, isNull); // Current path is always "/" in this case. - expect(window.browserHistory.currentPath, '/'); + expect(EnginePlatformDispatcher.instance.browserHistory.currentPath, '/'); // Perform some navigation operations. - routeInfomrationUpdated('/foo/bar', null); + routeInformationUpdated('/foo/bar', null); // Path should not be updated because URL strategy is disabled. - expect(window.browserHistory.currentPath, '/'); + expect(EnginePlatformDispatcher.instance.browserHistory.currentPath, '/'); }); test('js interop throws on wrong type', () { diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 7761bee4ea7ea..e2b26c2f2f8ab 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -153,7 +153,7 @@ bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) { platform_data_.viewport_metrics = metrics; if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - platform_configuration->window()->UpdateWindowMetrics(metrics); + platform_configuration->get_window(0)->UpdateWindowMetrics(metrics); return true; } @@ -275,7 +275,7 @@ bool RuntimeController::DispatchPointerDataPacket( if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { TRACE_EVENT1("flutter", "RuntimeController::DispatchPointerDataPacket", "mode", "basic"); - platform_configuration->window()->DispatchPointerDataPacket(packet); + platform_configuration->get_window(0)->DispatchPointerDataPacket(packet); return true; } diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 4767d01fbcb13..89ed112055b74 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -146,7 +146,7 @@ class RuntimeController : public PlatformConfigurationClient { /// If the isolate is not running, these metrics will be saved and /// flushed to the isolate when it starts. /// - /// @param[in] metrics The viewport metrics. + /// @param[in] metrics The window's viewport metrics. /// /// @return If the window metrics were forwarded to the running isolate. /// diff --git a/shell/common/engine.h b/shell/common/engine.h index c5bbfc42c9c74..81771c00935ab 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -193,13 +193,14 @@ class Engine final : public RuntimeDelegate, /// @brief Notifies the shell of the name of the root isolate and its /// port when that isolate is launched, restarted (in the /// cold-restart scenario) or the application itself updates the - /// name of the root isolate (via `Window.setIsolateDebugName` - /// in `window.dart`). The name of the isolate is meaningless to - /// the engine but is used in instrumentation and tooling. - /// Currently, this information is to update the service - /// protocol list of available root isolates running in the VM - /// and their names so that the appropriate isolate can be - /// selected in the tools for debugging and instrumentation. + /// name of the root isolate (via + /// `PlatformDispatcher.setIsolateDebugName` in + /// `platform_dispatcher.dart`). The name of the isolate is + /// meaningless to the engine but is used in instrumentation and + /// tooling. Currently, this information is to update the + /// service protocol list of available root isolates running in + /// the VM and their names so that the appropriate isolate can + /// be selected in the tools for debugging and instrumentation. /// /// @param[in] isolate_name The isolate name /// @param[in] isolate_port The isolate port @@ -543,8 +544,8 @@ class Engine final : public RuntimeDelegate, /// "main.dart", the entrypoint is "main" and the port name /// "1234". Once launched, the isolate may re-christen itself /// using a name it selects via `setIsolateDebugName` in - /// `window.dart`. This name is purely advisory and only used by - /// instrumentation and reporting purposes. + /// `platform_dispatcher.dart`. This name is purely advisory and + /// only used by instrumentation and reporting purposes. /// /// @return The debug name of the root isolate. /// diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 492ddb8960d03..9c72ea87e0e42 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -15,7 +15,7 @@ void nativeOnPointerDataPacket(List sequences) native 'NativeOnPointerDataP @pragma('vm:entry-point') void reportTimingsMain() { - window.onReportTimings = (List timings) { + PlatformDispatcher.instance.onReportTimings = (List timings) { List timestamps = []; for (FrameTiming t in timings) { for (FramePhase phase in FramePhase.values) { @@ -28,15 +28,15 @@ void reportTimingsMain() { @pragma('vm:entry-point') void onBeginFrameMain() { - window.onBeginFrame = (Duration beginTime) { + PlatformDispatcher.instance.onBeginFrame = (Duration beginTime) { nativeOnBeginFrame(beginTime.inMicroseconds); }; } @pragma('vm:entry-point') void onPointerDataPacketMain() { - window.onPointerDataPacket = (PointerDataPacket packet) { - List sequence= []; + PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket packet) { + List sequence = []; for (PointerData data in packet.data) { sequence.add(PointerChange.values.indexOf(data.change)); } @@ -62,7 +62,7 @@ void _reportMetrics(double devicePixelRatio, double width, double height) native @pragma('vm:entry-point') void dummyReportTimingsMain() { - window.onReportTimings = (List timings) {}; + PlatformDispatcher.instance.onReportTimings = (List timings) {}; } @pragma('vm:entry-point') @@ -102,7 +102,7 @@ void testSkiaResourceCacheSendsResponse() { "method": "Skia.setResourceCacheMaxBytes", "args": 10000 }'''; - window.sendPlatformMessage( + PlatformDispatcher.instance.sendPlatformMessage( 'flutter/skia', Uint8List.fromList(utf8.encode(jsonRequest)).buffer.asByteData(), callback, @@ -120,17 +120,24 @@ void canCreateImageFromDecompressedData() { (int i) => i % 4 < 2 ? 0x00 : 0xFF, )); - decodeImageFromPixels( - pixels, imageWidth, imageHeight, PixelFormat.rgba8888, - (Image image) { - notifyWidthHeight(image.width, image.height); - }); + pixels, + imageWidth, + imageHeight, + PixelFormat.rgba8888, + (Image image) { + notifyWidthHeight(image.width, image.height); + }, + ); } @pragma('vm:entry-point') void canAccessIsolateLaunchData() { - notifyMessage(utf8.decode(window.getPersistentIsolateData().buffer.asUint8List())); + notifyMessage( + utf8.decode( + PlatformDispatcher.instance.getPersistentIsolateData().buffer.asUint8List(), + ), + ); } void notifyMessage(String string) native 'NotifyMessage'; @@ -145,9 +152,12 @@ void sendFixtureMapping(List list) native 'SendFixtureMapping'; @pragma('vm:entry-point') void canDecompressImageFromAsset() { - decodeImageFromList(Uint8List.fromList(getFixtureImage()), (Image result) { - notifyWidthHeight(result.width, result.height); - }); + decodeImageFromList( + Uint8List.fromList(getFixtureImage()), + (Image result) { + notifyWidthHeight(result.width, result.height); + }, + ); } List getFixtureImage() native 'GetFixtureImage'; diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 17d0eaf6e50ae..9e15322848311 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -5,7 +5,6 @@ #define FML_USED_ON_EMBEDDER #include "flutter/shell/common/shell_test.h" -#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/transform_layer.h" @@ -13,6 +12,7 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/shell/common/vsync_waiter_fallback.h" #include "flutter/testing/testing.h" @@ -110,6 +110,8 @@ void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { flutter::ViewportMetrics viewport_metrics = { 1, // device pixel ratio + 0, // physical left + 0, // physical top width, // physical width height, // physical height 0, // padding top @@ -159,7 +161,7 @@ void ShellTest::PumpOneFrame(Shell* shell, double width, double height, LayerTreeBuilder builder) { - PumpOneFrame(shell, {1.0, width, height}, std::move(builder)); + PumpOneFrame(shell, {1.0, 0.0, 0.0, width, height}, std::move(builder)); } void ShellTest::PumpOneFrame(Shell* shell, diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index fbf0647eb1191..80c0df5917dac 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1245,7 +1245,7 @@ TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) { configuration.SetEntrypoint("emptyMain"); RunEngine(shell.get(), std::move(configuration)); - PumpOneFrame(shell.get(), {1.0, 0.0, 0.0}); + PumpOneFrame(shell.get(), {1.0, 0.0, 0.0, 0.0, 0.0}); fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); ASSERT_FALSE(result.ok()); @@ -1327,7 +1327,7 @@ TEST_F(ShellTest, WaitForFirstFrameInlined) { DestroyShell(std::move(shell), std::move(task_runners)); } -static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) { +static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) { size_t bytes = 0; fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( @@ -1370,7 +1370,7 @@ TEST_F(ShellTest, SetResourceCacheSize) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -1389,7 +1389,7 @@ TEST_F(ShellTest, SetResourceCacheSize) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 800, 400}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 800, 400}); }); PumpOneFrame(shell.get()); @@ -1407,7 +1407,7 @@ TEST_F(ShellTest, SetResourceCacheSizeEarly) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -1435,7 +1435,7 @@ TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -2037,7 +2037,7 @@ TEST_F(ShellTest, DiscardLayerTreeOnResize) { shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &expected_size]() { shell->GetPlatformView()->SetViewportMetrics( - {1.0, static_cast(expected_size.width()), + {1.0, 0.0, 0.0, static_cast(expected_size.width()), static_cast(expected_size.height())}); }); @@ -2107,13 +2107,14 @@ TEST_F(ShellTest, IgnoresInvalidMetrics) { RunEngine(shell.get(), std::move(configuration)); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({0.0, 0.0, 0.0, 400, 200}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 200}); + shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 0.0, 0.0, 200}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.8, 400, 0.0}); + shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 0.0, 400, 0.0}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.8, 400, 200.0}); + shell->GetPlatformView()->SetViewportMetrics( + {0.8, 0.0, 0.0, 400, 200.0}); }); }); }); @@ -2125,7 +2126,7 @@ TEST_F(ShellTest, IgnoresInvalidMetrics) { latch.Reset(); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({1.2, 600, 300}); + shell->GetPlatformView()->SetViewportMetrics({1.2, 0.0, 0.0, 600, 300}); }); latch.Wait(); ASSERT_EQ(last_device_pixel_ratio, 1.2); diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 48d9243cf86c2..8c25da13d9e2d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1168,7 +1168,6 @@ public void removeFlutterEngineAttachmentListener( .send(); } - // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI private void sendViewportMetricsToFlutter() { if (!isAttachedToFlutterEngine()) { Log.w( diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 59fa7132e3008..5c61b7b6a9fe1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -383,6 +383,8 @@ public void onSurfaceDestroyed() { @UiThread public void setViewportMetrics( float devicePixelRatio, + int physicalLeft, + int physicalTop, int physicalWidth, int physicalHeight, int physicalPaddingTop, @@ -402,6 +404,8 @@ public void setViewportMetrics( nativeSetViewportMetrics( nativePlatformViewId, devicePixelRatio, + physicalLeft, + physicalTop, physicalWidth, physicalHeight, physicalPaddingTop, @@ -421,6 +425,8 @@ public void setViewportMetrics( private native void nativeSetViewportMetrics( long nativePlatformViewId, float devicePixelRatio, + int physicalLeft, + int physicalTop, int physicalWidth, int physicalHeight, int physicalPaddingTop, 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 0aa11c4f2aacf..40ff19106b0c7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -236,7 +236,11 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { Log.v( TAG, "Setting viewport metrics\n" - + "Size: " + + "Location: (" + + viewportMetrics.left + + ", " + + viewportMetrics.top + + "), Size: " + viewportMetrics.width + " x " + viewportMetrics.height @@ -270,6 +274,8 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { flutterJNI.setViewportMetrics( viewportMetrics.devicePixelRatio, + viewportMetrics.left, + viewportMetrics.top, viewportMetrics.width, viewportMetrics.height, viewportMetrics.paddingTop, @@ -341,6 +347,8 @@ public void dispatchSemanticsAction( */ public static final class ViewportMetrics { public float devicePixelRatio = 1.0f; + public int left = 0; + public int top = 0; public int width = 0; public int height = 0; public int paddingTop = 0; diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 50598cd7344f4..9e7a7febaf806 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -97,6 +97,8 @@ public interface Provider { static final class ViewportMetrics { float devicePixelRatio = 1.0f; + int physicalLeft = 0; + int physicalTop = 0; int physicalWidth = 0; int physicalHeight = 0; int physicalPaddingTop = 0; @@ -753,6 +755,8 @@ private void updateViewportMetrics() { .getFlutterJNI() .setViewportMetrics( mMetrics.devicePixelRatio, + mMetrics.physicalLeft, + mMetrics.physicalTop, mMetrics.physicalWidth, mMetrics.physicalHeight, mMetrics.physicalPaddingTop, diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 3b8a61bf39c0c..8ac12484edf7d 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -6,7 +6,6 @@ #include #include - #include #include "unicode/uchar.h" @@ -36,8 +35,9 @@ namespace flutter { namespace { bool CheckException(JNIEnv* env) { - if (env->ExceptionCheck() == JNI_FALSE) + if (env->ExceptionCheck() == JNI_FALSE) { return true; + } jthrowable exception = env->ExceptionOccurred(); env->ExceptionClear(); @@ -241,6 +241,8 @@ static void SetViewportMetrics(JNIEnv* env, jobject jcaller, jlong shell_holder, jfloat devicePixelRatio, + jint physicalLeft, + jint physicalTop, jint physicalWidth, jint physicalHeight, jint physicalPaddingTop, @@ -257,6 +259,8 @@ static void SetViewportMetrics(JNIEnv* env, jint systemGestureInsetLeft) { const flutter::ViewportMetrics metrics{ static_cast(devicePixelRatio), + static_cast(physicalLeft), + static_cast(physicalTop), static_cast(physicalWidth), static_cast(physicalHeight), static_cast(physicalPaddingTop), @@ -583,7 +587,7 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSetViewportMetrics", - .signature = "(JFIIIIIIIIIIIIII)V", + .signature = "(JFIIIIIIIIIIIIIIII)V", .fnPtr = reinterpret_cast(&SetViewportMetrics), }, { @@ -1174,10 +1178,10 @@ void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView( } case clip_rect: { const SkRect& rect = (*iter)->GetRect(); - env->CallVoidMethod(mutatorsStack, - g_mutators_stack_push_cliprect_method, - (int)rect.left(), (int)rect.top(), - (int)rect.right(), (int)rect.bottom()); + env->CallVoidMethod( + mutatorsStack, g_mutators_stack_push_cliprect_method, + static_cast(rect.left()), static_cast(rect.top()), + static_cast(rect.right()), static_cast(rect.bottom())); break; } // TODO(cyanglaz): Implement other mutators. @@ -1301,16 +1305,16 @@ PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale( } fml::jni::ScopedJavaLocalRef j_locales_data = fml::jni::VectorToStringArray(env, supported_locales_data); - jobjectArray result = (jobjectArray)env->CallObjectMethod( + jobjectArray result = static_cast(env->CallObjectMethod( java_object.obj(), g_compute_platform_resolved_locale_method, - j_locales_data.obj()); + j_locales_data.obj())); FML_CHECK(CheckException(env)); int length = env->GetArrayLength(result); for (int i = 0; i < length; i++) { out->emplace_back(fml::jni::JavaStringToString( - env, (jstring)env->GetObjectArrayElement(result, i))); + env, static_cast(env->GetObjectArrayElement(result, i)))); } return out; } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 0e585b3e508c4..7e6250728dfdb 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -632,6 +632,8 @@ public void onSurfaceWindowChanged(Surface surface) {} @Implementation public void setViewportMetrics( float devicePixelRatio, + int physicalLeft, + int physicalRight, int physicalWidth, int physicalHeight, int physicalPaddingTop, diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ed7e1d79fd56d..19e1b3092f27f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -923,18 +923,20 @@ - (CGFloat)statusBarPadding { } - (void)viewDidLayoutSubviews { - CGSize viewSize = self.view.bounds.size; + CGRect viewBounds = self.view.bounds; CGFloat scale = [UIScreen mainScreen].scale; // Purposefully place this not visible. - _scrollView.get().frame = CGRectMake(0.0, 0.0, viewSize.width, 0.0); + _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0); _scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); // First time since creation that the dimensions of its view is known. bool firstViewBoundsUpdate = !_viewportMetrics.physical_width; _viewportMetrics.device_pixel_ratio = scale; - _viewportMetrics.physical_width = viewSize.width * scale; - _viewportMetrics.physical_height = viewSize.height * scale; + _viewportMetrics.physical_width = viewBounds.size.width * scale; + _viewportMetrics.physical_height = viewBounds.size.height * scale; + _viewportMetrics.physical_left = viewBounds.origin.x * scale; + _viewportMetrics.physical_top = viewBounds.origin.y * scale; [self updateViewportPadding]; [self updateViewportMetrics]; @@ -1105,14 +1107,18 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification { } auto platformView = [_engine.get() platformView]; int32_t flags = 0; - if (UIAccessibilityIsInvertColorsEnabled()) + if (UIAccessibilityIsInvertColorsEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kInvertColors); - if (UIAccessibilityIsReduceMotionEnabled()) + } + if (UIAccessibilityIsReduceMotionEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kReduceMotion); - if (UIAccessibilityIsBoldTextEnabled()) + } + if (UIAccessibilityIsBoldTextEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); - if (UIAccessibilityDarkerSystemColorsEnabled()) + } + if (UIAccessibilityDarkerSystemColorsEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kHighContrast); + } #if TARGET_OS_SIMULATOR // There doesn't appear to be any way to determine whether the accessibility // inspector is enabled on the simulator. We conservatively always turn on the diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 6d0de14a71cfa..4697dc5fa5d81 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -411,16 +411,19 @@ - (void)updateWindowMetrics { return; } NSView* view = _viewController.view; - CGSize scaledSize = [view convertRectToBacking:view.bounds].size; + CGRect scaledBounds = [view convertRectToBacking:view.bounds]; + CGSize scaledSize = scaledBounds.size; double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; - const FlutterWindowMetricsEvent event = { - .struct_size = sizeof(event), + const FlutterWindowMetricsEvent windowMetricsEvent = { + .struct_size = sizeof(windowMetricsEvent), .width = static_cast(scaledSize.width), .height = static_cast(scaledSize.height), .pixel_ratio = pixelRatio, + .left = static_cast(scaledBounds.origin.x), + .top = static_cast(scaledBounds.origin.y), }; - FlutterEngineSendWindowMetricsEvent(_engine, &event); + FlutterEngineSendWindowMetricsEvent(_engine, &windowMetricsEvent); } - (void)sendPointerEvent:(const FlutterPointerEvent&)event { diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 971ef22024f59..32779c3fea182 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1,8 +1,6 @@ // 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. -// FLUTTER_NOLINT -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER #define RAPIDJSON_HAS_STDSTRING 1 @@ -661,8 +659,9 @@ FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { return kSuccess; } -void PopulateSnapshotMappingCallbacks(const FlutterProjectArgs* args, - flutter::Settings& settings) { +void PopulateSnapshotMappingCallbacks( + const FlutterProjectArgs* args, + flutter::Settings& settings) { // NOLINT(google-runtime-references) // There are no ownership concerns here as all mappings are owned by the // embedder and not the engine. auto make_mapping_callback = [](const uint8_t* mapping, size_t size) { diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 8ed2dadfac37f..06a148d55a643 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -461,6 +461,10 @@ typedef struct { size_t height; /// Scale factor for the physical screen. double pixel_ratio; + /// Horizontal physical location of the left side of the window on the screen. + size_t left; + /// Vertical physical location of the top of the window on the screen. + size_t top; } FlutterWindowMetricsEvent; /// The phase of the pointer event. diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index de4cf422b662f..deefde20f06c7 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -34,7 +34,7 @@ void sayHiFromCustomEntrypoint3() native 'SayHiFromCustomEntrypoint3'; @pragma('vm:entry-point') void invokePlatformTaskRunner() { - window.sendPlatformMessage('OhHi', null, null); + PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null); } @@ -59,19 +59,19 @@ void notifySemanticsEnabled(bool enabled) native 'NotifySemanticsEnabled'; void notifyAccessibilityFeatures(bool reduceMotion) native 'NotifyAccessibilityFeatures'; void notifySemanticsAction(int nodeId, int action, List data) native 'NotifySemanticsAction'; -/// Returns a future that completes when `window.onSemanticsEnabledChanged` -/// fires. +/// Returns a future that completes when +/// `PlatformDispatcher.instance.onSemanticsEnabledChanged` fires. Future get semanticsChanged { final Completer semanticsChanged = Completer(); - window.onSemanticsEnabledChanged = semanticsChanged.complete; + PlatformDispatcher.instance.onSemanticsEnabledChanged = semanticsChanged.complete; return semanticsChanged.future; } -/// Returns a future that completes when `window.onAccessibilityFeaturesChanged` -/// fires. +/// Returns a future that completes when +/// `PlatformDispatcher.instance.onAccessibilityFeaturesChanged` fires. Future get accessibilityFeaturesChanged { final Completer featuresChanged = Completer(); - window.onAccessibilityFeaturesChanged = featuresChanged.complete; + PlatformDispatcher.instance.onAccessibilityFeaturesChanged = featuresChanged.complete; return featuresChanged.future; } @@ -84,7 +84,7 @@ class SemanticsActionData { Future get semanticsAction { final Completer actionReceived = Completer(); - window.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { + PlatformDispatcher.instance.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { actionReceived.complete(SemanticsActionData(id, action, args)); }; return actionReceived.future; @@ -93,18 +93,18 @@ Future get semanticsAction { @pragma('vm:entry-point') void a11y_main() async { // ignore: non_constant_identifier_names // Return initial state (semantics disabled). - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); // Await semantics enabled from embedder. await semanticsChanged; - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); // Return initial state of accessibility features. - notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); // Await accessibility features changed from embedder. await accessibilityFeaturesChanged; - notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); // Fire semantics update. final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder() @@ -143,7 +143,7 @@ void a11y_main() async { // ignore: non_constant_identifier_names label: 'Archive', hint: 'archive message', ); - window.updateSemantics(builder.build()); + PlatformDispatcher.instance.updateSemantics(builder.build()); signalNativeTest(); // Await semantics action from embedder. @@ -153,13 +153,13 @@ void a11y_main() async { // ignore: non_constant_identifier_names // Await semantics disabled from embedder. await semanticsChanged; - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); } @pragma('vm:entry-point') void platform_messages_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { callback(data); }; signalNativeTest(); @@ -167,7 +167,7 @@ void platform_messages_response() { @pragma('vm:entry-point') void platform_messages_no_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { var list = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); signalNativeMessage(utf8.decode(list)); // This does nothing because no one is listening on the other side. But complete the loop anyway @@ -179,7 +179,7 @@ void platform_messages_no_response() { @pragma('vm:entry-point') void null_platform_messages() { - window.onPlatformMessage = + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { // This checks if the platform_message null data is converted to Flutter null. signalNativeMessage((null == data).toString()); @@ -198,7 +198,7 @@ Picture CreateSimplePicture() { @pragma('vm:entry-point') void can_composite_platform_views() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pushOffset(1.0, 2.0); @@ -206,15 +206,15 @@ void can_composite_platform_views() { builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_opacity() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Root node @@ -236,24 +236,24 @@ void can_composite_platform_views_with_opacity() { builder.pop(); signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_with_opacity() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOpacity(127); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateColoredBox(Color color, Size size) { @@ -267,7 +267,7 @@ Picture CreateColoredBox(Color color, Size size) { @pragma('vm:entry-point') void can_composite_platform_views_with_known_scene() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color blue = Color.fromARGB(127, 0, 0, 255); Color gray = Color.fromARGB(127, 127, 127, 127); @@ -298,17 +298,17 @@ void can_composite_platform_views_with_known_scene() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_root_layer_only() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -319,17 +319,17 @@ void can_composite_platform_views_with_root_layer_only() { builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_platform_layer_on_bottom() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -345,17 +345,17 @@ void can_composite_platform_views_with_platform_layer_on_bottom() { builder.pop(); builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_render_scene_without_custom_compositor() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color green = Color.fromARGB(127, 0, 255, 0); Color blue = Color.fromARGB(127, 0, 0, 255); @@ -380,9 +380,9 @@ void can_render_scene_without_custom_compositor() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateGradientBox(Size size) { @@ -417,7 +417,7 @@ Picture CreateGradientBox(Size size) { @pragma('vm:entry-point') void render_gradient() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); SceneBuilder builder = SceneBuilder(); @@ -428,14 +428,14 @@ void render_gradient() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void render_gradient_on_non_root_backing_store() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); Color red = Color.fromARGB(127, 255, 0, 0); @@ -452,14 +452,14 @@ void render_gradient_on_non_root_backing_store() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void verify_b141980393() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { // The platform view in the test case is screen sized but with margins of 31 // and 37 points from the top and bottom. double top_margin = 31.0; @@ -478,14 +478,14 @@ void verify_b141980393() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_display_platform_view_with_pixel_ratio() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushTransform(Float64List.fromList([ 2.0, 0.0, 0.0, 0.0, @@ -499,15 +499,15 @@ void can_display_platform_view_with_pixel_ratio() { builder.pop(); // offset builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(128, 255, 0, 0), Size(400.0, 300.0))); builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_receive_locale_updates() { - window.onLocaleChanged = (){ - signalNativeCount(window.locales.length); + PlatformDispatcher.instance.onLocaleChanged = (){ + signalNativeCount(PlatformDispatcher.instance.locales.length); }; signalNativeTest(); } @@ -515,7 +515,7 @@ void can_receive_locale_updates() { // Verifies behavior tracked in https://github.com/flutter/flutter/issues/43732 @pragma('vm:entry-point') void verify_b143464703() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base @@ -542,14 +542,14 @@ void verify_b143464703() { builder.pop(); // 1 builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void push_frames_over_and_over() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(255, 128, 128, 128), Size(1024.0, 600.0))); @@ -557,17 +557,17 @@ void push_frames_over_and_over() { builder.addPlatformView(42, width: 1024.0, height: 540.0); builder.pop(); builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(800.0, 600.0))); @@ -581,14 +581,14 @@ void platform_view_mutators() { builder.pop(); // opacity builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators_with_pixel_ratio() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); @@ -602,29 +602,29 @@ void platform_view_mutators_with_pixel_ratio() { builder.pop(); // opacity builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void empty_scene() { - window.onBeginFrame = (Duration duration) { - window.render(SceneBuilder().build()); + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.views.first.render(SceneBuilder().build()); signalNativeTest(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_with_no_container() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateArcEndCapsPicture() { @@ -646,29 +646,29 @@ Picture CreateArcEndCapsPicture() { @pragma('vm:entry-point') void arc_end_caps_correct() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateArcEndCapsPicture()); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_clips() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(10.0, 10.0, 390.0, 290.0)); builder.addPlatformView(42, width: 400.0, height: 300.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_complex_clips() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 1024.0, 600.0)); @@ -679,9 +679,9 @@ void scene_builder_with_complex_clips() { builder.addPlatformView(42, width: 1024.0, height: 600.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(1024.0, 600.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } void sendObjectToNativeCode(dynamic object) native 'SendObjectToNativeCode'; @@ -695,41 +695,41 @@ void objects_can_be_posted() { @pragma('vm:entry-point') void empty_scene_posts_zero_layers_to_compositor() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Should not render anything. builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 300.0, 200.0)); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void compositor_can_post_only_platform_views() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPlatformView(42, width: 300.0, height: 200.0); builder.addPlatformView(24, width: 300.0, height: 200.0); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void render_targets_are_recycled() { int frame_count = 0; - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); for (int i = 0; i < 10; i++) { builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(30.0, 20.0))); builder.addPlatformView(42 + i, width: 30.0, height: 20.0); } - window.render(builder.build()); - window.scheduleFrame(); + PlatformDispatcher.instance.views.first.render(builder.build()); + PlatformDispatcher.instance.scheduleFrame(); frame_count++; if (frame_count == 8) { signalNativeTest(); } }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index db227ffd336de..4989bcd81d9e0 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -327,6 +327,8 @@ void PlatformView::OnScenicEvent( const std::pair logical_size = *view_logical_size_; SetViewportMetrics({ pixel_ratio, // device_pixel_ratio + 0.0f, // physical_left + 0.0f, // physical_top logical_size.first * pixel_ratio, // physical_width logical_size.second * pixel_ratio, // physical_height 0.0f, // physical_padding_top diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 21eda23623664..fe13678fff033 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -27,6 +27,7 @@ part '../../lib/ui/hooks.dart'; part '../../lib/ui/lerp.dart'; part '../../lib/ui/natives.dart'; part '../../lib/ui/painting.dart'; +part '../../lib/ui/platform_dispatcher.dart'; part '../../lib/ui/pointer.dart'; part '../../lib/ui/semantics.dart'; part '../../lib/ui/text.dart'; @@ -44,15 +45,22 @@ void main() { PlatformMessageCallback? originalOnPlatformMessage; VoidCallback? originalOnTextScaleFactorChanged; - double? oldDPR; - Size? oldSize; - WindowPadding? oldPadding; - WindowPadding? oldInsets; - WindowPadding? oldSystemGestureInsets; + Object? oldWindowId; + double? oldDevicePixelRatio; + Rect? oldGeometry; + + WindowPadding? oldPadding; + WindowPadding? oldInsets; + WindowPadding? oldSystemGestureInsets; void setUp() { - oldDPR = window.devicePixelRatio; - oldSize = window.physicalSize; + PlatformDispatcher.instance._viewConfigurations.clear(); + PlatformDispatcher.instance._views.clear(); + PlatformDispatcher.instance._viewConfigurations[0] = const ViewConfiguration(); + PlatformDispatcher.instance._views[0] = FlutterWindow._(0, PlatformDispatcher.instance); + oldWindowId = window._windowId; + oldDevicePixelRatio = window.devicePixelRatio; + oldGeometry = window.viewConfiguration.geometry; oldPadding = window.viewPadding; oldInsets = window.viewInsets; oldSystemGestureInsets = window.systemGestureInsets; @@ -69,34 +77,37 @@ void main() { originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged; } - void tearDown() { - _updateWindowMetrics( - oldDPR!, // DPR - oldSize!.width, // width - oldSize!.height, // height - oldPadding!.top, // padding top - oldPadding!.right, // padding right - oldPadding!.bottom, // padding bottom - oldPadding!.left, // padding left - oldInsets!.top, // inset top - oldInsets!.right, // inset right - oldInsets!.bottom, // inset bottom - oldInsets!.left, // inset left - oldSystemGestureInsets!.top, // system gesture inset top - oldSystemGestureInsets!.right, // system gesture inset right - oldSystemGestureInsets!.bottom, // system gesture inset bottom - oldSystemGestureInsets!.left, // system gesture inset left - ); - window.onMetricsChanged = originalOnMetricsChanged; - window.onLocaleChanged = originalOnLocaleChanged; - window.onBeginFrame = originalOnBeginFrame; - window.onDrawFrame = originalOnDrawFrame; - window.onReportTimings = originalOnReportTimings; - window.onPointerDataPacket = originalOnPointerDataPacket; - window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; - window.onSemanticsAction = originalOnSemanticsAction; - window.onPlatformMessage = originalOnPlatformMessage; - window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; + tearDown() { + _updateWindowMetrics( + oldWindowId!, // window id + oldDevicePixelRatio!, // device pixel ratio + oldGeometry!.left, // window left coordinate + oldGeometry!.top, // window top coordinate + oldGeometry!.width, // width + oldGeometry!.height, // height + oldPadding!.top, // padding top + oldPadding!.right, // padding right + oldPadding!.bottom, // padding bottom + oldPadding!.left, // padding left + oldInsets!.top, // inset top + oldInsets!.right, // inset right + oldInsets!.bottom, // inset bottom + oldInsets!.left, // inset left + oldSystemGestureInsets!.top, // system gesture inset top + oldSystemGestureInsets!.right, // system gesture inset right + oldSystemGestureInsets!.bottom, // system gesture inset bottom + oldSystemGestureInsets!.left, // system gesture inset left + ); + window.onMetricsChanged = originalOnMetricsChanged; + window.onLocaleChanged = originalOnLocaleChanged; + window.onBeginFrame = originalOnBeginFrame; + window.onDrawFrame = originalOnDrawFrame; + window.onReportTimings = originalOnReportTimings; + window.onPointerDataPacket = originalOnPointerDataPacket; + window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; + window.onSemanticsAction = originalOnSemanticsAction; + window.onPlatformMessage = originalOnPlatformMessage; + window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; } void test(String description, void Function() testFunction) { @@ -155,7 +166,10 @@ void main() { window.onMetricsChanged!(); _updateWindowMetrics( - 0.1234, // DPR + 0, // window id + 0.1234, // device pixel ratio + 0.0, // device pixel ratio + 0.0, // top 0.0, // width 0.0, // height 0.0, // padding top @@ -234,7 +248,7 @@ void main() { late Zone innerZone; late Zone runZone; - window._setNeedsReportTimings = (bool _) {}; + PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; runZoned(() { innerZone = Zone.current; @@ -265,7 +279,7 @@ void main() { _dispatchPointerDataPacket(testData); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); - expectIterablesEqual(data.data, _unpackPointerDataPacket(testData).data); + expectIterablesEqual(data.data, PlatformDispatcher._unpackPointerDataPacket(testData).data); }); test('onSemanticsEnabledChanged preserves callback zone', () { @@ -281,11 +295,12 @@ void main() { }; }); - _updateSemanticsEnabled(window._semanticsEnabled); + final bool newValue = !window.semanticsEnabled; // needed? + _updateSemanticsEnabled(newValue); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); expectNotEquals(enabled, null); - expectEquals(enabled, window._semanticsEnabled); + expectEquals(enabled, newValue); }); test('onSemanticsAction preserves callback zone', () { @@ -331,47 +346,47 @@ void main() { test('onTextScaleFactorChanged preserves callback zone', () { late Zone innerZone; - late Zone runZone; - late double textScaleFactor; + late Zone runZoneTextScaleFactor; + late Zone runZonePlatformBrightness; + late double? textScaleFactor; + late Brightness? platformBrightness; runZoned(() { innerZone = Zone.current; window.onTextScaleFactorChanged = () { - runZone = Zone.current; + runZoneTextScaleFactor = Zone.current; textScaleFactor = window.textScaleFactor; }; + window.onPlatformBrightnessChanged = () { + runZonePlatformBrightness = Zone.current; + platformBrightness = window.platformBrightness; + }; }); window.onTextScaleFactorChanged!(); - _updateTextScaleFactor(0.5); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(textScaleFactor, 0.5); - }); - test('onThemeBrightnessMode preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late Brightness platformBrightness; + _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); + expectNotEquals(runZoneTextScaleFactor, null); + expectIdentical(runZoneTextScaleFactor, innerZone); + expectEquals(textScaleFactor, 0.5); - runZoned(() { - innerZone = Zone.current; - window.onPlatformBrightnessChanged = () { - runZone = Zone.current; - platformBrightness = window.platformBrightness; - }; - }); + textScaleFactor = null; + platformBrightness = null; window.onPlatformBrightnessChanged!(); - _updatePlatformBrightness('dark'); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); + _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); + expectNotEquals(runZonePlatformBrightness, null); + expectIdentical(runZonePlatformBrightness, innerZone); expectEquals(platformBrightness, Brightness.dark); + }); test('Window padding/insets/viewPadding/systemGestureInsets', () { _updateWindowMetrics( - 1.0, // DPR + 0, // window id + 0, // screen id + 10.0, // left + 11.0, // top 800.0, // width 600.0, // height 50.0, // padding top @@ -394,7 +409,10 @@ void main() { expectEquals(window.systemGestureInsets.bottom, 0.0); _updateWindowMetrics( - 1.0, // DPR + 0, // window id + 0, // screen id + 10.0, // left + 11.0, // top 800.0, // width 600.0, // height 50.0, // padding top @@ -405,10 +423,10 @@ void main() { 0.0, // inset right 400.0, // inset bottom 0.0, // inset left - 0.0, // system gesture insets top - 0.0, // system gesture insets right - 44.0, // system gesture insets bottom - 0.0, // system gesture insets left + 0.0, // system gesture inset top + 0.0, // system gesture inset right + 44.0, // system gesture inset bottom + 0.0, // system gesture inset left ); expectEquals(window.viewInsets.bottom, 400.0); diff --git a/testing/scenario_app/lib/src/animated_color_square.dart b/testing/scenario_app/lib/src/animated_color_square.dart index f1e9865f55309..b85f20d4d442b 100644 --- a/testing/scenario_app/lib/src/animated_color_square.dart +++ b/testing/scenario_app/lib/src/animated_color_square.dart @@ -16,10 +16,10 @@ import 'scenario.dart'; class AnimatedColorSquareScenario extends Scenario { /// Creates the AnimatedColorSquare scenario. /// - /// The [window] parameter must not be null. - AnimatedColorSquareScenario(Window window) - : assert(window != null), - super(window); + /// The [dispatcher] parameter must not be null. + AnimatedColorSquareScenario(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); static const double _squareSize = 200; /// Used to animate the red value in the color of the square. diff --git a/testing/scenario_app/lib/src/channel_util.dart b/testing/scenario_app/lib/src/channel_util.dart index 986494f5ff4e9..24f05f2fc0658 100644 --- a/testing/scenario_app/lib/src/channel_util.dart +++ b/testing/scenario_app/lib/src/channel_util.dart @@ -10,13 +10,13 @@ import 'package:meta/meta.dart'; /// Util method to replicate the behavior of a `MethodChannel` in the Flutter /// framework. void sendJsonMethodCall({ - @required Window window, + @required PlatformDispatcher dispatcher, @required String channel, @required String method, dynamic arguments, PlatformMessageResponseCallback callback, }) { - window.sendPlatformMessage( + dispatcher.sendPlatformMessage( channel, // This recreates a combination of OptionalMethodChannel, JSONMethodCodec, // and _DefaultBinaryMessenger in the framework. diff --git a/testing/scenario_app/lib/src/initial_route_reply.dart b/testing/scenario_app/lib/src/initial_route_reply.dart index c42e7608da93d..ffabb8d072ced 100644 --- a/testing/scenario_app/lib/src/initial_route_reply.dart +++ b/testing/scenario_app/lib/src/initial_route_reply.dart @@ -15,14 +15,14 @@ class InitialRouteReply extends Scenario { /// Creates the InitialRouteReply. /// /// The [window] parameter must not be null. - InitialRouteReply(Window window) - : assert(window != null), - super(window); + InitialRouteReply(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); @override void onBeginFrame(Duration duration) { sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'initial_route_test_channel', method: window.defaultRouteName, ); diff --git a/testing/scenario_app/lib/src/locale_initialization.dart b/testing/scenario_app/lib/src/locale_initialization.dart index 6157de30be903..7d9faf6415f7a 100644 --- a/testing/scenario_app/lib/src/locale_initialization.dart +++ b/testing/scenario_app/lib/src/locale_initialization.dart @@ -12,9 +12,9 @@ import 'scenario.dart'; /// Sends the recieved locale data back as semantics information. class LocaleInitialization extends Scenario { /// Constructor - LocaleInitialization(Window window) - : assert(window != null), - super(window); + LocaleInitialization(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); int _tapCount = 0; @@ -82,10 +82,11 @@ class LocaleInitialization extends Scenario { /// Send changing information via semantics on each successive tap. @override void onPointerDataPacket(PointerDataPacket packet) { - String label; + String label = ''; switch(_tapCount) { case 1: { // Set label to string data we wish to pass on first frame. + label = '1'; break; } // Expand for other test cases. diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index e9802438eb5e4..fd6f5f47887e9 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -32,11 +32,11 @@ List _to64(num value) { class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -56,11 +56,11 @@ class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewNoOverlayIntersectionScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -84,11 +84,11 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatf class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewPartialIntersectionScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewPartialIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier . @@ -112,11 +112,11 @@ class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatfor class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -128,7 +128,7 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -153,11 +153,11 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewOneOverlayTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -169,7 +169,7 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -199,12 +199,12 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewWithoutOverlaysScenario(Window window, String text, { this.firstId, this.secondId }) - : assert(window != null), - super(window) { - createPlatformView(window, text, firstId); - createPlatformView(window, text, secondId); + /// The [dispatcher] parameter must not be null. + MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.firstId, this.secondId }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, firstId); + createPlatformView(dispatcher, text, secondId); } /// The platform view identifier to use for the first platform view. @@ -220,10 +220,10 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -245,11 +245,11 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewMaxOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewMaxOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -261,7 +261,7 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -296,12 +296,12 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewScenario(Window window, {this.firstId, this.secondId}) - : assert(window != null), - super(window) { - createPlatformView(window, 'platform view 1', firstId); - createPlatformView(window, 'platform view 2', secondId); + /// The [dispatcher] parameter must not be null. + MultiPlatformViewScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, 'platform view 1', firstId); + createPlatformView(dispatcher, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -317,7 +317,7 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); finishBuilderByAddingPlatformViewAndPicture(builder, secondId); @@ -332,13 +332,13 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewBackgroundForegroundScenario(Window window, {this.firstId, this.secondId}) - : assert(window != null), - super(window) { + /// The [dispatcher] parameter must not be null. + MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) + : assert(dispatcher != null), + super(dispatcher) { _nextFrame = _firstFrame; - createPlatformView(window, 'platform view 1', firstId); - createPlatformView(window, 'platform view 2', secondId); + createPlatformView(dispatcher, 'platform view 1', firstId); + createPlatformView(dispatcher, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -360,10 +360,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -386,10 +386,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final Scene scene = builder.build(); window.render(scene); scene.dispose(); @@ -419,10 +419,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP /// Platform view with clip rect. class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Constructs a platform view with clip rect scenario. - PlatformViewClipRectScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + PlatformViewClipRectScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -440,8 +440,8 @@ class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenar /// Platform view with clip rrect. class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipRRectScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewClipRRectScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -466,8 +466,8 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Platform view with clip path. class PlatformViewClipPathScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipPathScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewClipPathScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -490,8 +490,8 @@ class PlatformViewClipPathScenario extends PlatformViewScenario { /// Platform view with transform. class PlatformViewTransformScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewTransformScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewTransformScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -512,8 +512,8 @@ class PlatformViewTransformScenario extends PlatformViewScenario { /// Platform view with opacity. class PlatformViewOpacityScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewOpacityScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewOpacityScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -536,16 +536,16 @@ class PlatformViewForTouchIOSScenario extends Scenario VoidCallback _nextFrame; /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewForTouchIOSScenario(Window window, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) - : assert(window != null), + /// The [dispatcher] parameter must not be null. + PlatformViewForTouchIOSScenario(PlatformDispatcher dispatcher, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) + : assert(dispatcher != null), _accept = accept, _viewId = id, - super(window) { + super(dispatcher) { if (rejectUntilTouchesEnded) { - createPlatformView(window, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); + createPlatformView(dispatcher, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); } else { - createPlatformView(window, text, id); + createPlatformView(dispatcher, text, id); } _nextFrame = _firstFrame; } @@ -625,7 +625,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { /// It prepare a TextPlatformView so it can be added to the SceneBuilder in `onBeginFrame`. /// Call this method in the constructor of the platform view related scenarios /// to perform necessary set up. - void createPlatformView(Window window, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { + void createPlatformView(PlatformDispatcher dispatcher, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { const int _valueTrue = 1; const int _valueInt32 = 3; const int _valueFloat64 = 6; @@ -691,7 +691,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ...utf8.encode(text), ]); - window.sendPlatformMessage( + dispatcher.sendPlatformMessage( 'flutter/platform_views', message.buffer.asByteData(), (ByteData response) { @@ -703,7 +703,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ); } - void _addPlatformViewtoScene( + void _addPlatformViewToScene( SceneBuilder sceneBuilder, int viewId, double width, @@ -729,7 +729,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { Offset overlayOffset, }) { overlayOffset ??= const Offset(50, 50); - _addPlatformViewtoScene( + _addPlatformViewToScene( sceneBuilder, viewId, 500, diff --git a/testing/scenario_app/lib/src/poppable_screen.dart b/testing/scenario_app/lib/src/poppable_screen.dart index 6ecd655b02995..f53cce7890b66 100644 --- a/testing/scenario_app/lib/src/poppable_screen.dart +++ b/testing/scenario_app/lib/src/poppable_screen.dart @@ -14,10 +14,10 @@ import 'scenario.dart'; class PoppableScreenScenario extends Scenario with PlatformEchoMixin { /// Creates the PoppableScreenScenario. /// - /// The [window] parameter must not be null. - PoppableScreenScenario(Window window) - : assert(window != null), - super(window); + /// The [dispatcher] parameter must not be null. + PoppableScreenScenario(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); // Rect for the pop button. Only defined once onMetricsChanged is called. Rect _buttonRect; @@ -74,7 +74,7 @@ class PoppableScreenScenario extends Scenario with PlatformEchoMixin { void _pop() { sendJsonMethodCall( - window: window, + dispatcher: dispatcher, // 'flutter/platform' is the hardcoded name of the 'platform' // `SystemChannel` from the `SystemNavigator` API. // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/system_navigator.dart. diff --git a/testing/scenario_app/lib/src/scenario.dart b/testing/scenario_app/lib/src/scenario.dart index ccf2be02778de..94319e692ea0e 100644 --- a/testing/scenario_app/lib/src/scenario.dart +++ b/testing/scenario_app/lib/src/scenario.dart @@ -8,31 +8,31 @@ import 'dart:ui'; /// A scenario to run for testing. abstract class Scenario { - /// Creates a new scenario using a specific Window instance. - Scenario(this.window); + /// Creates a new scenario using a specific FlutterWindow instance. + Scenario(this.dispatcher); /// The window used by this scenario. May be mocked. - final Window window; + final PlatformDispatcher dispatcher; /// [true] if a screenshot is taken in the next frame. bool _didScheduleScreenshot = false; /// Called by the program when a frame is ready to be drawn. /// - /// See [Window.onBeginFrame] for more details. + /// See [PlatformDispatcher.onBeginFrame] for more details. void onBeginFrame(Duration duration) {} /// Called by the program when the microtasks from [onBeginFrame] have been /// flushed. /// - /// See [Window.onDrawFrame] for more details. + /// See [PlatformDispatcher.onDrawFrame] for more details. void onDrawFrame() { Future.delayed(const Duration(seconds: 1), () { if (_didScheduleScreenshot) { - window.sendPlatformMessage('take_screenshot', null, null); + dispatcher.sendPlatformMessage('take_screenshot', null, null); } else { _didScheduleScreenshot = true; - window.scheduleFrame(); + dispatcher.scheduleFrame(); } }); } @@ -45,18 +45,18 @@ abstract class Scenario { /// Called by the program when the window metrics have changed. /// - /// See [Window.onMetricsChanged]. + /// See [PlatformDispatcher.onMetricsChanged]. void onMetricsChanged() {} /// Called by the program when a pointer event is received. /// - /// See [Window.onPointerDataPacket]. + /// See [PlatformDispatcher.onPointerDataPacket]. void onPointerDataPacket(PointerDataPacket packet) {} /// Called by the program when an engine side platform channel message is /// received. /// - /// See [Window.onPlatformMessage]. + /// See [PlatformDispatcher.onPlatformMessage]. void onPlatformMessage( String name, ByteData data, diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index 6aa553380d34c..69c4d034953b2 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -19,30 +19,30 @@ typedef ScenarioFactory = Scenario Function(); int _viewId = 0; Map _scenarios = { - 'animated_color_square': () => AnimatedColorSquareScenario(window), - 'locale_initialization': () => LocaleInitialization(window), - 'platform_view': () => PlatformViewScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(window, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), - 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_cliprect': () => PlatformViewClipRectScenario(window, 'PlatformViewClipRect', id: _viewId++), - 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(window, 'PlatformViewClipRRect', id: _viewId++), - 'platform_view_clippath': () => PlatformViewClipPathScenario(window, 'PlatformViewClipPath', id: _viewId++), - 'platform_view_transform': () => PlatformViewTransformScenario(window, 'PlatformViewTransform', id: _viewId++), - 'platform_view_opacity': () => PlatformViewOpacityScenario(window, 'PlatformViewOpacity', id: _viewId++), - 'platform_view_multiple': () => MultiPlatformViewScenario(window, firstId: 6, secondId: _viewId++), - 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(window, firstId: _viewId++, secondId: _viewId++), - 'poppable_screen': () => PoppableScreenScenario(window), - 'platform_view_rotate': () => PlatformViewScenario(window, 'Rotate Platform View', id: _viewId++), - 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false), - 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: true), - 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), - 'tap_status_bar': () => TouchesScenario(window), - 'text_semantics_focus': () => SendTextFocusScemantics(window), - 'initial_route_reply': () => InitialRouteReply(window), + 'animated_color_square': () => AnimatedColorSquareScenario(PlatformDispatcher.instance), + 'locale_initialization': () => LocaleInitialization(PlatformDispatcher.instance), + 'platform_view': () => PlatformViewScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), + 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_cliprect': () => PlatformViewClipRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRect', id: _viewId++), + 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRRect', id: _viewId++), + 'platform_view_clippath': () => PlatformViewClipPathScenario(PlatformDispatcher.instance, 'PlatformViewClipPath', id: _viewId++), + 'platform_view_transform': () => PlatformViewTransformScenario(PlatformDispatcher.instance, 'PlatformViewTransform', id: _viewId++), + 'platform_view_opacity': () => PlatformViewOpacityScenario(PlatformDispatcher.instance, 'PlatformViewOpacity', id: _viewId++), + 'platform_view_multiple': () => MultiPlatformViewScenario(PlatformDispatcher.instance, firstId: 6, secondId: _viewId++), + 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher.instance, firstId: _viewId++, secondId: _viewId++), + 'poppable_screen': () => PoppableScreenScenario(PlatformDispatcher.instance), + 'platform_view_rotate': () => PlatformViewScenario(PlatformDispatcher.instance, 'Rotate Platform View', id: _viewId++), + 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false), + 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: true), + 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), + 'tap_status_bar': () => TouchesScenario(PlatformDispatcher.instance), + 'text_semantics_focus': () => SendTextFocusSemantics(PlatformDispatcher.instance), + 'initial_route_reply': () => InitialRouteReply(PlatformDispatcher.instance), }; Map _currentScenarioParams = {}; diff --git a/testing/scenario_app/lib/src/send_text_focus_semantics.dart b/testing/scenario_app/lib/src/send_text_focus_semantics.dart index 529dafcea5416..94997cf0701e4 100644 --- a/testing/scenario_app/lib/src/send_text_focus_semantics.dart +++ b/testing/scenario_app/lib/src/send_text_focus_semantics.dart @@ -11,9 +11,9 @@ import 'channel_util.dart'; import 'scenario.dart'; /// A scenario that sends back messages when touches are received. -class SendTextFocusScemantics extends Scenario { +class SendTextFocusSemantics extends Scenario { /// Constructor for `SendTextFocusScemantics`. - SendTextFocusScemantics(Window window) : super(window); + SendTextFocusSemantics(PlatformDispatcher dispatcher) : super(dispatcher); @override void onBeginFrame(Duration duration) { @@ -79,7 +79,7 @@ class SendTextFocusScemantics extends Scenario { // This mimics the framework which shows the FlutterTextInputView before // updating the TextInputSemanticsObject. sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'flutter/textinput', method: 'TextInput.setClient', arguments: [ @@ -91,7 +91,7 @@ class SendTextFocusScemantics extends Scenario { ); sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'flutter/textinput', method: 'TextInput.show', ); diff --git a/testing/scenario_app/lib/src/touches_scenario.dart b/testing/scenario_app/lib/src/touches_scenario.dart index dbbf50ff15581..6eb35eebe9b28 100644 --- a/testing/scenario_app/lib/src/touches_scenario.dart +++ b/testing/scenario_app/lib/src/touches_scenario.dart @@ -10,7 +10,7 @@ import 'scenario.dart'; /// A scenario that sends back messages when touches are received. class TouchesScenario extends Scenario { /// Constructor for `TouchesScenario`. - TouchesScenario(Window window) : super(window); + TouchesScenario(PlatformDispatcher dispatcher) : super(dispatcher); @override void onPointerDataPacket(PointerDataPacket packet) { From a7b5cdcc72cffc7014d45cef537cf438713d035b Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 9 Oct 2020 12:29:56 -0700 Subject: [PATCH 2/3] Revert viewport metrics location API --- lib/ui/hooks.dart | 4 ---- lib/ui/platform_dispatcher.dart | 4 +--- lib/ui/window/platform_configuration.cc | 5 ++-- .../platform_configuration_unittests.cc | 2 +- lib/ui/window/viewport_metrics.cc | 18 --------------- lib/ui/window/viewport_metrics.h | 12 ---------- lib/ui/window/window.cc | 2 -- lib/ui/window/window.h | 2 +- shell/common/shell_test.cc | 4 +--- shell/common/shell_unittests.cc | 23 +++++++++---------- .../flutter/embedding/engine/FlutterJNI.java | 6 ----- .../engine/renderer/FlutterRenderer.java | 10 +------- .../android/io/flutter/view/FlutterView.java | 4 ---- .../android/platform_view_android_jni_impl.cc | 6 +---- .../platform/PlatformViewsControllerTest.java | 2 -- .../framework/Source/FlutterViewController.mm | 2 -- .../platform/fuchsia/flutter/platform_view.cc | 2 -- .../dart/window_hooks_integration_test.dart | 8 ------- 18 files changed, 19 insertions(+), 97 deletions(-) diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 3b8d66b8a46b4..8802af5f27c18 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -13,8 +13,6 @@ part of dart.ui; void _updateWindowMetrics( Object id, double devicePixelRatio, - double left, - double top, double width, double height, double viewPaddingTop, @@ -33,8 +31,6 @@ void _updateWindowMetrics( PlatformDispatcher.instance._updateWindowMetrics( id, devicePixelRatio, - left, - top, width, height, viewPaddingTop, diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index e829e9ca3b27d..766ac5535081a 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -155,8 +155,6 @@ class PlatformDispatcher { void _updateWindowMetrics( Object id, double devicePixelRatio, - double left, - double top, double width, double height, double viewPaddingTop, @@ -180,7 +178,7 @@ class PlatformDispatcher { _viewConfigurations[id] = previousConfiguration.copyWith( window: _views[id], devicePixelRatio: devicePixelRatio, - geometry: Rect.fromLTWH(left, top, width, height), + geometry: Rect.fromLTWH(0.0, 0.0, width, height), viewPadding: WindowPadding._( top: viewPaddingTop, right: viewPaddingRight, diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index f0df4efaf7a76..e8ff2274fe7a1 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -198,9 +198,8 @@ PlatformConfiguration::~PlatformConfiguration() {} void PlatformConfiguration::DidCreateIsolate() { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); - windows_.insert( - std::make_pair(0, std::unique_ptr(new Window{ - 0, ViewportMetrics{1.0, 0.0, 0.0, 0.0, 0.0}}))); + windows_.insert(std::make_pair(0, std::unique_ptr(new Window{ + 0, ViewportMetrics{1.0, 0.0, 0.0}}))); } void PlatformConfiguration::UpdateLocales( diff --git a/lib/ui/window/platform_configuration_unittests.cc b/lib/ui/window/platform_configuration_unittests.cc index 049f88920c94a..f38498614e187 100644 --- a/lib/ui/window/platform_configuration_unittests.cc +++ b/lib/ui/window/platform_configuration_unittests.cc @@ -102,7 +102,7 @@ TEST_F(ShellTest, PlatformConfigurationWindowMetricsUpdate) { ASSERT_NE(configuration->get_window(0), nullptr); configuration->get_window(0)->UpdateWindowMetrics( - ViewportMetrics{2.0, 0.0, 0.0, 10.0, 20.0}); + ViewportMetrics{2.0, 10.0, 20.0}); ASSERT_EQ( configuration->get_window(0)->viewport_metrics().device_pixel_ratio, 2.0); diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index 9eb43d61811ac..d52bfb83756e4 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -18,8 +18,6 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_height(p_physical_height) {} ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, - double p_physical_left, - double p_physical_top, double p_physical_width, double p_physical_height, double p_physical_padding_top, @@ -35,8 +33,6 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left) : device_pixel_ratio(p_device_pixel_ratio), - physical_left(p_physical_left), - physical_top(p_physical_top), physical_width(p_physical_width), physical_height(p_physical_height), physical_padding_top(p_physical_padding_top), @@ -55,21 +51,8 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_system_gesture_inset_left(p_physical_system_gesture_inset_left) { } -ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, - double p_physical_left, - double p_physical_top, - double p_physical_width, - double p_physical_height) - : device_pixel_ratio(p_device_pixel_ratio), - physical_left(p_physical_left), - physical_top(p_physical_top), - physical_width(p_physical_width), - physical_height(p_physical_height) {} - bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { return a.device_pixel_ratio == b.device_pixel_ratio && - a.physical_left == b.physical_left && - a.physical_top == b.physical_top && a.physical_width == b.physical_width && a.physical_height == b.physical_height && a.physical_padding_top == b.physical_padding_top && @@ -92,7 +75,6 @@ bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { std::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) { os << "DPR: " << a.device_pixel_ratio << " " - << "Location: [" << a.physical_left << "L, " << a.physical_top << "T] " << "Size: [" << a.physical_width << "W " << a.physical_height << "H] " << "Padding: [" << a.physical_padding_top << "T " << a.physical_padding_right << "R " << a.physical_padding_bottom << "B " diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h index 5b7d604bbc9bc..adce20a91cdc9 100644 --- a/lib/ui/window/viewport_metrics.h +++ b/lib/ui/window/viewport_metrics.h @@ -15,8 +15,6 @@ struct ViewportMetrics { double p_physical_width, double p_physical_height); ViewportMetrics(double p_device_pixel_ratio, - double p_physical_left, - double p_physical_top, double p_physical_width, double p_physical_height, double p_physical_padding_top, @@ -32,17 +30,7 @@ struct ViewportMetrics { double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left); - // Create a ViewportMetrics instance that doesn't include depth, padding, or - // insets. - ViewportMetrics(double p_device_pixel_ratio, - double p_physical_left, - double p_physical_top, - double p_physical_width, - double p_physical_height); - double device_pixel_ratio = 1.0; - double physical_left = 0; - double physical_top = 0; double physical_width = 0; double physical_height = 0; double physical_padding_top = 0; diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index c2e0568d7b586..082df1b823d32 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -49,8 +49,6 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { { tonic::ToDart(window_id_), tonic::ToDart(metrics.device_pixel_ratio), - tonic::ToDart(metrics.physical_top), - tonic::ToDart(metrics.physical_left), tonic::ToDart(metrics.physical_width), tonic::ToDart(metrics.physical_height), tonic::ToDart(metrics.physical_padding_top), diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index bccad35e274b3..b6fa2555b04d7 100644 --- a/lib/ui/window/window.h +++ b/lib/ui/window/window.h @@ -18,7 +18,7 @@ namespace flutter { class Window final { public: - explicit Window(int64_t window_id, ViewportMetrics metrics); + Window(int64_t window_id, ViewportMetrics metrics); ~Window(); diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 9e15322848311..aadfd6db0e683 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -110,8 +110,6 @@ void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { flutter::ViewportMetrics viewport_metrics = { 1, // device pixel ratio - 0, // physical left - 0, // physical top width, // physical width height, // physical height 0, // padding top @@ -161,7 +159,7 @@ void ShellTest::PumpOneFrame(Shell* shell, double width, double height, LayerTreeBuilder builder) { - PumpOneFrame(shell, {1.0, 0.0, 0.0, width, height}, std::move(builder)); + PumpOneFrame(shell, {1.0, width, height}, std::move(builder)); } void ShellTest::PumpOneFrame(Shell* shell, diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 80c0df5917dac..904d8611a4471 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1245,7 +1245,7 @@ TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) { configuration.SetEntrypoint("emptyMain"); RunEngine(shell.get(), std::move(configuration)); - PumpOneFrame(shell.get(), {1.0, 0.0, 0.0, 0.0, 0.0}); + PumpOneFrame(shell.get(), {1.0, 0.0, 0.0}); fml::Status result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); ASSERT_FALSE(result.ok()); @@ -1370,7 +1370,7 @@ TEST_F(ShellTest, SetResourceCacheSize) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -1389,7 +1389,7 @@ TEST_F(ShellTest, SetResourceCacheSize) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 800, 400}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 800, 400}); }); PumpOneFrame(shell.get()); @@ -1407,7 +1407,7 @@ TEST_F(ShellTest, SetResourceCacheSizeEarly) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -1435,7 +1435,7 @@ TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) { fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() { - shell->GetPlatformView()->SetViewportMetrics({1.0, 0.0, 0.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200}); }); PumpOneFrame(shell.get()); @@ -2037,7 +2037,7 @@ TEST_F(ShellTest, DiscardLayerTreeOnResize) { shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell, &expected_size]() { shell->GetPlatformView()->SetViewportMetrics( - {1.0, 0.0, 0.0, static_cast(expected_size.width()), + {1.0, static_cast(expected_size.width()), static_cast(expected_size.height())}); }); @@ -2107,14 +2107,13 @@ TEST_F(ShellTest, IgnoresInvalidMetrics) { RunEngine(shell.get(), std::move(configuration)); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.0, 0.0, 0.0, 400, 200}); + shell->GetPlatformView()->SetViewportMetrics({0.0, 400, 200}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 0.0, 0.0, 200}); + shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 200}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({0.8, 0.0, 0.0, 400, 0.0}); + shell->GetPlatformView()->SetViewportMetrics({0.8, 400, 0.0}); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics( - {0.8, 0.0, 0.0, 400, 200.0}); + shell->GetPlatformView()->SetViewportMetrics({0.8, 400, 200.0}); }); }); }); @@ -2126,7 +2125,7 @@ TEST_F(ShellTest, IgnoresInvalidMetrics) { latch.Reset(); task_runner->PostTask([&]() { - shell->GetPlatformView()->SetViewportMetrics({1.2, 0.0, 0.0, 600, 300}); + shell->GetPlatformView()->SetViewportMetrics({1.2, 600, 300}); }); latch.Wait(); ASSERT_EQ(last_device_pixel_ratio, 1.2); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 5c61b7b6a9fe1..59fa7132e3008 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -383,8 +383,6 @@ public void onSurfaceDestroyed() { @UiThread public void setViewportMetrics( float devicePixelRatio, - int physicalLeft, - int physicalTop, int physicalWidth, int physicalHeight, int physicalPaddingTop, @@ -404,8 +402,6 @@ public void setViewportMetrics( nativeSetViewportMetrics( nativePlatformViewId, devicePixelRatio, - physicalLeft, - physicalTop, physicalWidth, physicalHeight, physicalPaddingTop, @@ -425,8 +421,6 @@ public void setViewportMetrics( private native void nativeSetViewportMetrics( long nativePlatformViewId, float devicePixelRatio, - int physicalLeft, - int physicalTop, int physicalWidth, int physicalHeight, int physicalPaddingTop, 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 40ff19106b0c7..0aa11c4f2aacf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -236,11 +236,7 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { Log.v( TAG, "Setting viewport metrics\n" - + "Location: (" - + viewportMetrics.left - + ", " - + viewportMetrics.top - + "), Size: " + + "Size: " + viewportMetrics.width + " x " + viewportMetrics.height @@ -274,8 +270,6 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { flutterJNI.setViewportMetrics( viewportMetrics.devicePixelRatio, - viewportMetrics.left, - viewportMetrics.top, viewportMetrics.width, viewportMetrics.height, viewportMetrics.paddingTop, @@ -347,8 +341,6 @@ public void dispatchSemanticsAction( */ public static final class ViewportMetrics { public float devicePixelRatio = 1.0f; - public int left = 0; - public int top = 0; public int width = 0; public int height = 0; public int paddingTop = 0; diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 9e7a7febaf806..50598cd7344f4 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -97,8 +97,6 @@ public interface Provider { static final class ViewportMetrics { float devicePixelRatio = 1.0f; - int physicalLeft = 0; - int physicalTop = 0; int physicalWidth = 0; int physicalHeight = 0; int physicalPaddingTop = 0; @@ -755,8 +753,6 @@ private void updateViewportMetrics() { .getFlutterJNI() .setViewportMetrics( mMetrics.devicePixelRatio, - mMetrics.physicalLeft, - mMetrics.physicalTop, mMetrics.physicalWidth, mMetrics.physicalHeight, mMetrics.physicalPaddingTop, diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 8ac12484edf7d..d653a9eccb79d 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -241,8 +241,6 @@ static void SetViewportMetrics(JNIEnv* env, jobject jcaller, jlong shell_holder, jfloat devicePixelRatio, - jint physicalLeft, - jint physicalTop, jint physicalWidth, jint physicalHeight, jint physicalPaddingTop, @@ -259,8 +257,6 @@ static void SetViewportMetrics(JNIEnv* env, jint systemGestureInsetLeft) { const flutter::ViewportMetrics metrics{ static_cast(devicePixelRatio), - static_cast(physicalLeft), - static_cast(physicalTop), static_cast(physicalWidth), static_cast(physicalHeight), static_cast(physicalPaddingTop), @@ -587,7 +583,7 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSetViewportMetrics", - .signature = "(JFIIIIIIIIIIIIIIII)V", + .signature = "(JFIIIIIIIIIIIIII)V", .fnPtr = reinterpret_cast(&SetViewportMetrics), }, { diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 7e6250728dfdb..0e585b3e508c4 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -632,8 +632,6 @@ public void onSurfaceWindowChanged(Surface surface) {} @Implementation public void setViewportMetrics( float devicePixelRatio, - int physicalLeft, - int physicalRight, int physicalWidth, int physicalHeight, int physicalPaddingTop, diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 19e1b3092f27f..99c8f75c419e4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -935,8 +935,6 @@ - (void)viewDidLayoutSubviews { _viewportMetrics.device_pixel_ratio = scale; _viewportMetrics.physical_width = viewBounds.size.width * scale; _viewportMetrics.physical_height = viewBounds.size.height * scale; - _viewportMetrics.physical_left = viewBounds.origin.x * scale; - _viewportMetrics.physical_top = viewBounds.origin.y * scale; [self updateViewportPadding]; [self updateViewportMetrics]; diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 4989bcd81d9e0..db227ffd336de 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -327,8 +327,6 @@ void PlatformView::OnScenicEvent( const std::pair logical_size = *view_logical_size_; SetViewportMetrics({ pixel_ratio, // device_pixel_ratio - 0.0f, // physical_left - 0.0f, // physical_top logical_size.first * pixel_ratio, // physical_width logical_size.second * pixel_ratio, // physical_height 0.0f, // physical_padding_top diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index fe13678fff033..ab87370927149 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -81,8 +81,6 @@ void main() { _updateWindowMetrics( oldWindowId!, // window id oldDevicePixelRatio!, // device pixel ratio - oldGeometry!.left, // window left coordinate - oldGeometry!.top, // window top coordinate oldGeometry!.width, // width oldGeometry!.height, // height oldPadding!.top, // padding top @@ -168,8 +166,6 @@ void main() { _updateWindowMetrics( 0, // window id 0.1234, // device pixel ratio - 0.0, // device pixel ratio - 0.0, // top 0.0, // width 0.0, // height 0.0, // padding top @@ -385,8 +381,6 @@ void main() { _updateWindowMetrics( 0, // window id 0, // screen id - 10.0, // left - 11.0, // top 800.0, // width 600.0, // height 50.0, // padding top @@ -411,8 +405,6 @@ void main() { _updateWindowMetrics( 0, // window id 0, // screen id - 10.0, // left - 11.0, // top 800.0, // width 600.0, // height 50.0, // padding top From 46cbebfe57af87182d53aaf0acda715eeae9def7 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 9 Oct 2020 14:23:05 -0700 Subject: [PATCH 3/3] Fix test --- lib/web_ui/test/window_test.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index 54f57cf9073ee..3d164d1800401 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -31,7 +31,7 @@ void testMain() { final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/initial'), ); - await EnginePlatformDispatcher.instance.debugInitializeHistory(strategy, useSingle: true); + await window.debugInitializeHistory(strategy, useSingle: true); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. @@ -41,7 +41,7 @@ void testMain() { test('window.defaultRouteName should reset after navigation platform message', () async { - await EnginePlatformDispatcher.instance.debugInitializeHistory(TestUrlStrategy.fromEntry( + await window.debugInitializeHistory(TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/initial'), ), useSingle: true); // Reading it multiple times should return the same value. @@ -64,16 +64,16 @@ void testMain() { // Disable URL strategy. expect(() => jsSetUrlStrategy(null), returnsNormally); // History should be initialized. - expect(EnginePlatformDispatcher.instance.browserHistory, isNotNull); + expect(window.browserHistory, isNotNull); // But without a URL strategy. - expect(EnginePlatformDispatcher.instance.browserHistory.urlStrategy, isNull); + expect(window.browserHistory.urlStrategy, isNull); // Current path is always "/" in this case. - expect(EnginePlatformDispatcher.instance.browserHistory.currentPath, '/'); + expect(window.browserHistory.currentPath, '/'); // Perform some navigation operations. routeInformationUpdated('/foo/bar', null); // Path should not be updated because URL strategy is disabled. - expect(EnginePlatformDispatcher.instance.browserHistory.currentPath, '/'); + expect(window.browserHistory.currentPath, '/'); }); test('js interop throws on wrong type', () {