From 891fd8eacf216a6b67693672342d0159939a9283 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 15 Oct 2020 16:31:33 -0700 Subject: [PATCH 1/2] Revert "Revert "Migration to PlatformDispatcher and multi-window #20496" (#21792)" This reverts commit c2938d06b193fb8a62e4c8fab65507be55f64242. --- 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 | 218 +-- lib/ui/natives.dart | 2 +- lib/ui/painting/canvas.cc | 2 +- lib/ui/platform_dispatcher.dart | 1554 +++++++++++++++++ lib/ui/semantics.dart | 17 +- lib/ui/text.dart | 2 +- lib/ui/ui.dart | 1 + lib/ui/window.dart | 1453 ++++++--------- lib/ui/window/platform_configuration.cc | 6 +- lib/ui/window/platform_configuration.h | 11 +- .../platform_configuration_unittests.cc | 27 +- lib/ui/window/window.cc | 4 +- 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/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 | 465 +++-- 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 | 2 +- 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 | 2 +- shell/common/shell_unittests.cc | 2 +- .../embedding/android/FlutterView.java | 1 - .../android/platform_view_android_jni_impl.cc | 18 +- .../framework/Source/FlutterViewController.mm | 20 +- .../macos/framework/Source/FlutterEngine.mm | 11 +- shell/platform/embedder/embedder.cc | 5 +- shell/platform/embedder/embedder.h | 4 + shell/platform/embedder/fixtures/main.dart | 180 +- .../dart/window_hooks_integration_test.dart | 146 +- .../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 +- 72 files changed, 4381 insertions(+), 2517 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 3dca5f1ffbf84..82886e8123b99 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 @@ -500,6 +501,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 @@ -554,6 +556,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 1bf7b1fd60a38..324520b9a1154 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 f3e4d1ee5ec81..07f299bbb7b38 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -11,6 +11,7 @@ part of dart.ui; @pragma('vm:entry-point') // ignore: unused_element void _updateWindowMetrics( + Object id, double devicePixelRatio, double width, double height, @@ -27,30 +28,24 @@ 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, + width, + height, + viewPaddingTop, + viewPaddingRight, + viewPaddingBottom, + viewPaddingLeft, + viewInsetTop, + viewInsetRight, + viewInsetBottom, + viewInsetLeft, + systemGestureInsetTop, + systemGestureInsetRight, + systemGestureInsetBottom, + systemGestureInsetLeft, + ); } String _localeClosure() { @@ -64,149 +59,72 @@ typedef _LocaleClosure = String Function(); @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 @@ -217,7 +135,7 @@ typedef _ListStringArgFunction(List args); void _runMainZoned(Function startMainIsolateFunction, Function userMainFunction, List args) { - startMainIsolateFunction((){ + startMainIsolateFunction(() { runZonedGuarded(() { if (userMainFunction is _ListStringArgFunction) { (userMainFunction as dynamic)(args); @@ -233,9 +151,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 @@ -247,9 +166,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 @@ -261,9 +181,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 @@ -275,54 +202,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..766ac5535081a --- /dev/null +++ b/lib/ui/platform_dispatcher.dart @@ -0,0 +1,1554 @@ +// 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 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(0.0, 0.0, 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 4e9c97cca2f95..422c1d163b2e2 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..a2372728346a7 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]. -/// -/// {@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. +/// A view into which a Flutter [Scene] is drawn. /// -/// 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 configuration of this view. + ViewConfiguration get viewConfiguration; - /// 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 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. + 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 the `size` member of [physicalGeometry]. /// /// 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 => _physicalSize; - Size _physicalSize = Size.zero; + 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], or + /// [systemGestureInsets] values change. + /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// + /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// + /// 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.accessorForwardWarning} + /// + /// 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.accessorForwardWarning} + /// /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use - /// [Window.onReportTimings] directly because + /// [SingletonFlutterWindow.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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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.accessorForwardWarning} + /// /// 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; + platformDispatcher.onPlatformMessage = callback; } - /// 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); - }; - } - - - /// 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. + /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// - /// 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. + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. /// - /// For asynchronous communication between the embedder and isolate, a - /// platform channel may be used. - ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; + /// 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,220 @@ 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. +// In order for the documentation build to succeed, this interface duplicates +// all of the methods with documentation, overrides them, and calls the super +// implementation. Once this merges into the framework and the framework +// references to it can be updated, this class will be removed entirely. +class Window extends SingletonFlutterWindow { + Window._(Object windowId, PlatformDispatcher platformDispatcher) + : super._(windowId, platformDispatcher); + + @override + // ignore: unnecessary_overrides + double get devicePixelRatio => super.devicePixelRatio; + + @override + // ignore: unnecessary_overrides + Rect get physicalGeometry => super.physicalGeometry; + + @override + // ignore: unnecessary_overrides + Size get physicalSize => super.physicalSize; + + @override + // ignore: unnecessary_overrides + WindowPadding get viewInsets => super.viewInsets; + + @override + // ignore: unnecessary_overrides + WindowPadding get viewPadding => super.viewPadding; + + @override + // ignore: unnecessary_overrides + WindowPadding get systemGestureInsets => super.systemGestureInsets; + + @override + // ignore: unnecessary_overrides + WindowPadding get padding => super.padding; + + @override + // ignore: unnecessary_overrides + void render(Scene scene) => super.render(scene); + + @override + // ignore: unnecessary_overrides + VoidCallback? get onMetricsChanged => super.onMetricsChanged; + @override + // ignore: unnecessary_overrides + set onMetricsChanged(VoidCallback? callback) { + super.onMetricsChanged = callback; + } + + @override + // ignore: unnecessary_overrides + Locale? get locale => super.locale; + + @override + // ignore: unnecessary_overrides + List? get locales => super.locales; + + @override + // ignore: unnecessary_overrides + Locale? computePlatformResolvedLocale(List supportedLocales) { + return super.computePlatformResolvedLocale(supportedLocales); + } + + @override + // ignore: unnecessary_overrides + VoidCallback? get onLocaleChanged => super.onLocaleChanged; + @override + // ignore: unnecessary_overrides + set onLocaleChanged(VoidCallback? callback) { + super.onLocaleChanged = callback; + } + + @override + // ignore: unnecessary_overrides + String get initialLifecycleState => super.initialLifecycleState; + + @override + // ignore: unnecessary_overrides + double get textScaleFactor => super.textScaleFactor; + + @override + // ignore: unnecessary_overrides + bool get alwaysUse24HourFormat => super.alwaysUse24HourFormat; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onTextScaleFactorChanged => super.onTextScaleFactorChanged; + @override + // ignore: unnecessary_overrides + set onTextScaleFactorChanged(VoidCallback? callback) { + super.onTextScaleFactorChanged = callback; + } + + @override + // ignore: unnecessary_overrides + Brightness get platformBrightness => super.platformBrightness; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onPlatformBrightnessChanged => super.onPlatformBrightnessChanged; + @override + // ignore: unnecessary_overrides + set onPlatformBrightnessChanged(VoidCallback? callback) { + super.onPlatformBrightnessChanged = callback; + } + + @override + // ignore: unnecessary_overrides + FrameCallback? get onBeginFrame => super.onBeginFrame; + @override + // ignore: unnecessary_overrides + set onBeginFrame(FrameCallback? callback) { + super.onBeginFrame = callback; + } + + @override + // ignore: unnecessary_overrides + VoidCallback? get onDrawFrame => super.onDrawFrame; + @override + // ignore: unnecessary_overrides + set onDrawFrame(VoidCallback? callback) { + super.onDrawFrame = callback; + } + + @override + // ignore: unnecessary_overrides + TimingsCallback? get onReportTimings => super.onReportTimings; + @override + // ignore: unnecessary_overrides + set onReportTimings(TimingsCallback? callback) { + super.onReportTimings = callback; + } + + @override + // ignore: unnecessary_overrides + PointerDataPacketCallback? get onPointerDataPacket => super.onPointerDataPacket; + @override + // ignore: unnecessary_overrides + set onPointerDataPacket(PointerDataPacketCallback? callback) { + super.onPointerDataPacket = callback; + } + + @override + // ignore: unnecessary_overrides + String get defaultRouteName => super.defaultRouteName; + + @override + // ignore: unnecessary_overrides + void scheduleFrame() => super.scheduleFrame(); + + @override + // ignore: unnecessary_overrides + bool get semanticsEnabled => super.semanticsEnabled; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onSemanticsEnabledChanged => super.onSemanticsEnabledChanged; + @override + // ignore: unnecessary_overrides + set onSemanticsEnabledChanged(VoidCallback? callback) { + super.onSemanticsEnabledChanged = callback; + } + + @override + // ignore: unnecessary_overrides + SemanticsActionCallback? get onSemanticsAction => super.onSemanticsAction; + @override + // ignore: unnecessary_overrides + set onSemanticsAction(SemanticsActionCallback? callback) { + super.onSemanticsAction = callback; + } + + @override + // ignore: unnecessary_overrides + AccessibilityFeatures get accessibilityFeatures => super.accessibilityFeatures; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onAccessibilityFeaturesChanged => + super.onAccessibilityFeaturesChanged; + @override + // ignore: unnecessary_overrides + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + super.onAccessibilityFeaturesChanged = callback; + } + + @override + // ignore: unnecessary_overrides + void updateSemantics(SemanticsUpdate update) => super.updateSemantics(update); + + @override + // ignore: unnecessary_overrides + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { + super.sendPlatformMessage(name, data, callback); + } + + @override + // ignore: unnecessary_overrides + PlatformMessageCallback? get onPlatformMessage => super.onPlatformMessage; + @override + // ignore: unnecessary_overrides + set onPlatformMessage(PlatformMessageCallback? callback) { + super.onPlatformMessage = callback; + } + + @override + // ignore: unnecessary_overrides + void setIsolateDebugName(String name) => super.setIsolateDebugName(name); +} + /// 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 +1023,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. +/// +/// Applications that are designed to use more than one window should interact +/// with the `WidgetsBinding.instance.platformDispatcher` instead. /// -/// Please try to avoid statically referencing this and instead use a -/// binding for dependency resolution such as `WidgetsBinding.instance.window`. +/// 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 +/// 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 +1042,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..e8ff2274fe7a1 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,8 @@ 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}}))); } void PlatformConfiguration::UpdateLocales( @@ -421,7 +423,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 01089dac9c202..693ec18bcbd95 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" @@ -364,11 +362,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 @@ -394,7 +395,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..f38498614e187 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( + ASSERT_NE(configuration->get_window(0), nullptr); + configuration->get_window(0)->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_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/window.cc b/lib/ui/window/window.cc index 1779998945554..082df1b823d32 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,6 +47,7 @@ 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_width), tonic::ToDart(metrics.physical_height), diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index 172cf6b8c2ae5..b6fa2555b04d7 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); + 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 8799a62ea4b19..bf31b05a38bd9 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -100,6 +100,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'; @@ -225,17 +226,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 557106a26412a..d2dc78673f172 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; @@ -168,13 +168,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; } @@ -217,7 +217,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 054ac12953acf..d39aee1171e91 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -96,9 +96,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, @@ -188,7 +188,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], @@ -222,7 +222,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]); @@ -305,8 +305,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/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 c15886bd6b1e0..23e3104e08f75 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 4d0951527978f..782865140d1bd 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..d3519f19b13d0 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -1,262 +1,259 @@ // 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; + } + + Locale? get locale => platformDispatcher.locale; + List? get locales => platformDispatcher.locales; - @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? 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; + } + + Brightness get platformBrightness => platformDispatcher.platformBrightness; + + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; + set onPlatformBrightnessChanged(VoidCallback? callback) { + platformDispatcher.onPlatformBrightnessChanged = callback; + } + + 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, + ) { + platformDispatcher.sendPlatformMessage(name, data, callback); + } + + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback) { + platformDispatcher.onPlatformMessage = callback; } + + void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } -abstract class Window { +// This class will go away entirely once references to it are removed from the +// framework. The many explicit overrides are an artifact of needing to add the +// same overrides to the one in dart:ui in order to get dartdoc to find the docs +// for them. +abstract class Window extends SingletonFlutterWindow { + @override double get devicePixelRatio; + + @override + Rect get physicalGeometry; + + @override Size get physicalSize; - WindowPadding get viewInsets => WindowPadding.zero; - WindowPadding get viewPadding => WindowPadding.zero; + @override + WindowPadding get viewInsets; + + @override + WindowPadding get viewPadding; + + @override + WindowPadding get systemGestureInsets; + + @override + WindowPadding get padding; + + @override + void render(Scene scene); + + @override + VoidCallback? get onMetricsChanged; + @override + set onMetricsChanged(VoidCallback? callback); + + @override + Locale? get locale => super.locale; + + @override + List? get locales => super.locales; + + @override + Locale? computePlatformResolvedLocale(List supportedLocales); + + @override + VoidCallback? get onLocaleChanged; + @override + set onLocaleChanged(VoidCallback? callback); + + @override + String get initialLifecycleState; - 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; + @override + double get textScaleFactor; + + @override + bool get alwaysUse24HourFormat; + + @override VoidCallback? get onTextScaleFactorChanged; + @override set onTextScaleFactorChanged(VoidCallback? callback); + + @override Brightness get platformBrightness; + + @override VoidCallback? get onPlatformBrightnessChanged; + @override 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; - } - VoidCallback? get onLocaleChanged; - set onLocaleChanged(VoidCallback? callback); - void scheduleFrame(); + @override FrameCallback? get onBeginFrame; + @override set onBeginFrame(FrameCallback? callback); - TimingsCallback? get onReportTimings; - set onReportTimings(TimingsCallback? callback); + + @override VoidCallback? get onDrawFrame; + @override set onDrawFrame(VoidCallback? callback); + + @override + TimingsCallback? get onReportTimings; + @override + set onReportTimings(TimingsCallback? callback); + + @override PointerDataPacketCallback? get onPointerDataPacket; + @override set onPointerDataPacket(PointerDataPacketCallback? callback); + + @override String get defaultRouteName; - bool get semanticsEnabled => engine.EngineSemanticsOwner.instance.semanticsEnabled; + + @override + void scheduleFrame(); + + @override + bool get semanticsEnabled; + + @override VoidCallback? get onSemanticsEnabledChanged; + @override set onSemanticsEnabledChanged(VoidCallback? callback); + + @override SemanticsActionCallback? get onSemanticsAction; + @override set onSemanticsAction(SemanticsActionCallback? callback); + + @override + AccessibilityFeatures get accessibilityFeatures; + + @override VoidCallback? get onAccessibilityFeaturesChanged; + @override set onAccessibilityFeaturesChanged(VoidCallback? callback); - PlatformMessageCallback? get onPlatformMessage; - set onPlatformMessage(PlatformMessageCallback? callback); - void updateSemantics(SemanticsUpdate update) { - engine.EngineSemanticsOwner.instance.updateSemantics(update); - } - void sendPlatformMessage( - String name, - ByteData? data, - PlatformMessageResponseCallback? callback, - ); - AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; - AccessibilityFeatures _accessibilityFeatures = AccessibilityFeatures._(0); - void render(Scene scene); + @override + void updateSemantics(SemanticsUpdate update); - String get initialLifecycleState => 'AppLifecycleState.resumed'; + @override + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback); - void setIsolateDebugName(String name) {} + @override + PlatformMessageCallback? get onPlatformMessage; + @override + set onPlatformMessage(PlatformMessageCallback? callback); - ByteData? getPersistentIsolateData() => null; + @override + void setIsolateDebugName(String name); } class AccessibilityFeatures { @@ -271,6 +268,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 +350,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 +368,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 0590725f909fc..50ad4da94a7d5 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 @@ -232,8 +232,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 8c2784c7ba03a..774e904f4dee8 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 @@ -414,8 +414,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..3d164d1800401 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -71,7 +71,7 @@ void testMain() { expect(window.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, '/'); }); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 83d1292d3fd00..ca7222d16afdf 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -111,7 +111,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; } @@ -233,7 +233,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 17b02b935f986..c272ddf30e5b8 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -178,7 +178,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 3319e9c9e28c5..1eb2cf36ecc72 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -202,13 +202,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 @@ -552,8 +553,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 15d9386df12e7..469dc250bea22 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..aadfd6db0e683 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" diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index e15182e8d264c..03e13dd9092c2 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1328,7 +1328,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( 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/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index b3b11dbb8ee3e..084872a023313 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(); @@ -1176,10 +1176,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. @@ -1303,16 +1303,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/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 0b88a1ed543d9..ba2a525f70d8c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -923,18 +923,18 @@ - (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; [self updateViewportPadding]; [self updateViewportMetrics]; @@ -1157,14 +1157,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 60968ceec0031..37eb3521bc2ed 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -418,16 +418,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 c72a272e44990..4f2aec0a03ef1 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -659,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 51d832bfe5397..f2cf40f914b73 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 1df95c1c8727c..36fc996ee2f8e 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() @@ -221,7 +221,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. @@ -231,13 +231,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(); @@ -245,7 +245,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 @@ -257,7 +257,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()); @@ -276,7 +276,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); @@ -284,15 +284,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 @@ -314,24 +314,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) { @@ -345,7 +345,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); @@ -376,17 +376,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); @@ -397,17 +397,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); @@ -423,17 +423,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); @@ -458,9 +458,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) { @@ -495,7 +495,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(); @@ -506,14 +506,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); @@ -530,14 +530,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; @@ -556,14 +556,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, @@ -577,15 +577,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(); } @@ -593,7 +593,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 @@ -620,14 +620,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))); @@ -635,17 +635,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))); @@ -659,14 +659,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))); @@ -680,29 +680,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() { @@ -724,29 +724,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)); @@ -757,9 +757,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'; @@ -773,43 +773,43 @@ 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(); } void nativeArgumentsCallback(List args) native 'NativeArgumentsCallback'; diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 21eda23623664..ab87370927149 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,35 @@ 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!.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 +164,8 @@ void main() { window.onMetricsChanged!(); _updateWindowMetrics( - 0.1234, // DPR + 0, // window id + 0.1234, // device pixel ratio 0.0, // width 0.0, // height 0.0, // padding top @@ -234,7 +244,7 @@ void main() { late Zone innerZone; late Zone runZone; - window._setNeedsReportTimings = (bool _) {}; + PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; runZoned(() { innerZone = Zone.current; @@ -265,7 +275,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 +291,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 +342,45 @@ 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 800.0, // width 600.0, // height 50.0, // padding top @@ -394,7 +403,8 @@ void main() { expectEquals(window.systemGestureInsets.bottom, 0.0); _updateWindowMetrics( - 1.0, // DPR + 0, // window id + 0, // screen id 800.0, // width 600.0, // height 50.0, // padding top @@ -405,10 +415,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 cc74066771e41..7431752d4bbe1 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(); // ignore: public_member_api_docs 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 7a688e5e5141cb82a1486320cbb706ab35c3b7c2 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 16 Oct 2020 15:02:03 -0700 Subject: [PATCH 2/2] Fix semantics enabling logic, fix some typos --- lib/ui/hooks.dart | 7 ------- lib/ui/platform_dispatcher.dart | 4 ++-- lib/web_ui/lib/src/engine/dom_renderer.dart | 2 +- .../lib/src/engine/html/render_vertices.dart | 4 ++-- lib/web_ui/lib/src/engine/semantics/semantics.dart | 9 ++++++--- .../lib/src/engine/semantics/semantics_helper.dart | 14 +++++++------- .../engine/semantics/semantics_helper_test.dart | 10 +++++----- 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 07f299bbb7b38..3e7643b63ddcd 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -48,13 +48,6 @@ void _updateWindowMetrics( ); } -String _localeClosure() { - if (window.locale == null) { - return ''; - } - return window.locale.toString(); -} - typedef _LocaleClosure = String Function(); @pragma('vm:entry-point') diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 766ac5535081a..24a1fe8fc8ba0 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -648,9 +648,9 @@ class PlatformDispatcher { } // Called from the engine, via hooks.dart - String? _localeClosure() { + String _localeClosure() { if (locale == null) { - return null; + return ''; } return locale.toString(); } diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 0a35dc3a11262..257596bea5e3c 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -404,7 +404,7 @@ flt-glass-pane * { final html.Element _accesibilityPlaceholder = EngineSemanticsOwner .instance.semanticsHelper - .prepareAccesibilityPlaceholder(); + .prepareAccessibilityPlaceholder(); // Insert the semantics placeholder after the scene host. For all widgets // in the scene, except for platform widgets, the scene host will pass the 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 c0d564bca1e76..4d18e17a69984 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -786,8 +786,8 @@ class _OffScreenCanvas { height: height, ); _glCanvas!.className = 'gl-canvas'; - final double cssWidth = width / EngineWindow.browserDevicePixelRatio; - final double cssHeight = height / EngineWindow.browserDevicePixelRatio; + final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio; + final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio; _glCanvas!.style ..position = 'absolute' ..width = '${cssWidth}px' diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index f3b626e9b0535..67fc749e901e4 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1252,9 +1252,12 @@ class EngineSemanticsOwner { _rootSemanticsElement = null; _gestureModeClock?.datetime = null; } - - if (EnginePlatformDispatcher.instance._onSemanticsEnabledChanged != null) { - EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); + if (_semanticsEnabled != EnginePlatformDispatcher.instance.semanticsEnabled) { + EnginePlatformDispatcher.instance._configuration = + EnginePlatformDispatcher.instance._configuration.copyWith(semanticsEnabled: _semanticsEnabled); + if (EnginePlatformDispatcher.instance._onSemanticsEnabledChanged != null) { + EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); + } } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart index db9a6cb0777d8..e07a9dc1f57f2 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart @@ -49,8 +49,8 @@ class SemanticsHelper { return _semanticsEnabler.shouldEnableSemantics(event); } - html.Element prepareAccesibilityPlaceholder() { - return _semanticsEnabler.prepareAccesibilityPlaceholder(); + html.Element prepareAccessibilityPlaceholder() { + return _semanticsEnabler.prepareAccessibilityPlaceholder(); } } @@ -78,15 +78,15 @@ abstract class SemanticsEnabler { /// should be forwarded to the framework. bool tryEnableSemantics(html.Event event); - /// Creates the placeholder for accesibility. + /// Creates the placeholder for accessibility. /// /// Puts it inside the glasspane. /// /// On focus the element announces that accessibility can be enabled by /// tapping/clicking. (Announcement depends on the assistive technology) - html.Element prepareAccesibilityPlaceholder(); + html.Element prepareAccessibilityPlaceholder(); - /// Whether platform is still consisering enabling semantics. + /// Whether platform is still considering enabling semantics. /// /// At this stage a relevant set of events are always assessed to see if /// they activate the semantics. @@ -189,7 +189,7 @@ class DesktopSemanticsEnabler extends SemanticsEnabler { } @override - html.Element prepareAccesibilityPlaceholder() { + html.Element prepareAccessibilityPlaceholder() { final html.Element placeholder = _semanticsPlaceholder = html.Element.tag('flt-semantics-placeholder'); // Only listen to "click" because other kinds of events are reported via @@ -366,7 +366,7 @@ class MobileSemanticsEnabler extends SemanticsEnabler { } @override - html.Element prepareAccesibilityPlaceholder() { + html.Element prepareAccessibilityPlaceholder() { final html.Element placeholder = _semanticsPlaceholder = html.Element.tag('flt-semantics-placeholder'); // Only listen to "click" because other kinds of events are reported via diff --git a/lib/web_ui/test/engine/semantics/semantics_helper_test.dart b/lib/web_ui/test/engine/semantics/semantics_helper_test.dart index 56fb691d117a1..137bd2baf8cb0 100644 --- a/lib/web_ui/test/engine/semantics/semantics_helper_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_helper_test.dart @@ -33,7 +33,7 @@ void testMain() { }); test('prepare accesibility placeholder', () async { - _placeholder = desktopSemanticsEnabler.prepareAccesibilityPlaceholder(); + _placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder(); expect(_placeholder.getAttribute('role'), 'button'); expect(_placeholder.getAttribute('aria-live'), 'true'); @@ -54,7 +54,7 @@ void testMain() { test('Not relevant events should be forwarded to the framework', () async { // Prework. Attach the placeholder to dom. - _placeholder = desktopSemanticsEnabler.prepareAccesibilityPlaceholder(); + _placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder(); html.document.body.append(_placeholder); html.Event event = html.MouseEvent('mousemove'); @@ -79,7 +79,7 @@ void testMain() { 'Relevants events targeting placeholder should not be forwarded to the framework', () async { // Prework. Attach the placeholder to dom. - _placeholder = desktopSemanticsEnabler.prepareAccesibilityPlaceholder(); + _placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder(); html.document.body.append(_placeholder); html.Event event = html.MouseEvent('mousedown'); @@ -95,7 +95,7 @@ void testMain() { 'After max number of relevant events, events should be forwarded to the framework', () async { // Prework. Attach the placeholder to dom. - _placeholder = desktopSemanticsEnabler.prepareAccesibilityPlaceholder(); + _placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder(); html.document.body.append(_placeholder); html.Event event = html.MouseEvent('mousedown'); @@ -134,7 +134,7 @@ void testMain() { }); test('prepare accesibility placeholder', () async { - _placeholder = mobileSemanticsEnabler.prepareAccesibilityPlaceholder(); + _placeholder = mobileSemanticsEnabler.prepareAccessibilityPlaceholder(); expect(_placeholder.getAttribute('role'), 'button');