[web] Make devicePixelRatio ready for multi-view (flutter/engine#44783)

This PR moves the source of truth for`devicePixelRatio` to the `EngineFlutterDisplay` singleton.

Main things that this PR is trying to do:
- `EngineFlutterDisplay` is a singleton that represents information about the display.
  - The `devicePixelRatio` can be overriden in tests.
  - The `browserDevicePixelRatio` gets the value directly from the browser and can't be overriden.
- Remove `EngineSingletonFlutterWindow` and incorporate it into `EngineFlutterWindow`.
This commit is contained in:
Mouad Debbar
2023-08-29 11:51:05 -04:00
committed by GitHub
parent bf6f6b6b80
commit e2e1b979c4
17 changed files with 152 additions and 98 deletions

View File

@@ -1981,6 +1981,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/vertices.dart + ../
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/clipboard.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/configuration.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/display.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/dom.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/embedder.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart + ../../../flutter/LICENSE
@@ -4731,6 +4732,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/vertices.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/clipboard.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/configuration.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/display.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/embedder.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart

View File

@@ -54,6 +54,7 @@ export 'engine/canvaskit/vertices.dart';
export 'engine/clipboard.dart';
export 'engine/color_filter.dart';
export 'engine/configuration.dart';
export 'engine/display.dart';
export 'engine/dom.dart';
export 'engine/embedder.dart';
export 'engine/engine_canvas.dart';

View File

@@ -9,6 +9,7 @@ import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;
import 'browser_detection.dart';
import 'display.dart';
import 'dom.dart';
import 'engine_canvas.dart';
import 'html/bitmap_canvas.dart';
@@ -20,7 +21,6 @@ import 'html/path/path_utils.dart';
import 'html/picture.dart';
import 'html/shaders/image_shader.dart';
import 'html/shaders/shader.dart';
import 'platform_dispatcher.dart';
import 'rrect_renderer.dart';
import 'safe_browser_api.dart';
import 'shadow.dart';
@@ -156,9 +156,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 / EnginePlatformDispatcher.browserDevicePixelRatio;
_widthInBitmapPixels / EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double cssHeight =
_heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio;
_heightInBitmapPixels / EngineFlutterDisplay.instance.browserDevicePixelRatio;
canvas = _allocCanvas(_widthInBitmapPixels, _heightInBitmapPixels);
_canvas = canvas;
@@ -385,8 +385,7 @@ class CanvasPool extends _SaveStackTracking {
}
/// Returns effective dpi (browser DPI and pixel density due to transform).
double get dpi =>
EnginePlatformDispatcher.browserDevicePixelRatio * _density;
double get dpi => EngineFlutterDisplay.instance.browserDevicePixelRatio * _density;
void _resetTransform() {
final DomCanvasElement? canvas = _canvas;

View File

@@ -0,0 +1,61 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:ui/ui.dart' as ui;
import '../engine.dart';
class EngineFlutterDisplay extends ui.Display {
EngineFlutterDisplay({
required this.id,
required this.size,
required this.refreshRate,
});
/// The single [EngineFlutterDisplay] that the web page is rendered on.
static EngineFlutterDisplay get instance => _instance;
static final EngineFlutterDisplay _instance = EngineFlutterDisplay(
id: 0,
size: ui.Size(domWindow.screen?.width ?? 0, domWindow.screen?.height ?? 0),
refreshRate: 60,
);
@override
final int id;
// TODO(mdebbar): https://github.com/flutter/flutter/issues/133562
// `size` and `refreshRate` should be kept up-to-date with the
// browser. E.g. the window could be resized or moved to another display with
// a different refresh rate.
@override
final ui.Size size;
@override
final double refreshRate;
@override
double get devicePixelRatio =>
_debugDevicePixelRatioOverride ?? browserDevicePixelRatio;
/// The real device pixel ratio of the browser.
///
/// This value cannot be overriden by tests, for example.
double get browserDevicePixelRatio {
final double ratio = domWindow.devicePixelRatio;
// Guard against WebOS returning 0.
return (ratio == 0.0) ? 1.0 : ratio;
}
/// Overrides the default device pixel ratio.
///
/// This is useful in tests to emulate screens of different dimensions.
///
/// Passing `null` resets the device pixel ratio to the browser's default.
void debugOverrideDevicePixelRatio(double? value) {
_debugDevicePixelRatioOverride = value;
}
double? _debugDevicePixelRatioOverride;
}

View File

@@ -9,11 +9,11 @@ import 'package:ui/ui.dart' as ui;
import '../browser_detection.dart';
import '../canvas_pool.dart';
import '../display.dart';
import '../dom.dart';
import '../engine_canvas.dart';
import '../frame_reference.dart';
import '../html_image_codec.dart';
import '../platform_dispatcher.dart';
import '../text/canvas_paragraph.dart';
import '../util.dart';
import '../vector_math.dart';
@@ -126,7 +126,7 @@ class BitmapCanvas extends EngineCanvas {
/// Keeps track of what device pixel ratio was used when this [BitmapCanvas]
/// was created.
final double _devicePixelRatio =
EnginePlatformDispatcher.browserDevicePixelRatio;
EngineFlutterDisplay.instance.browserDevicePixelRatio;
// Compensation for [_initializeViewport] snapping canvas position to 1 pixel.
int? _canvasPositionX, _canvasPositionY;
@@ -201,14 +201,14 @@ class BitmapCanvas extends EngineCanvas {
static int widthToPhysical(double width) {
final double boundsWidth = width + 1;
return (boundsWidth * EnginePlatformDispatcher.browserDevicePixelRatio)
return (boundsWidth * EngineFlutterDisplay.instance.browserDevicePixelRatio)
.ceil() +
2 * kPaddingPixels;
}
static int heightToPhysical(double height) {
final double boundsHeight = height + 1;
return (boundsHeight * EnginePlatformDispatcher.browserDevicePixelRatio)
return (boundsHeight * EngineFlutterDisplay.instance.browserDevicePixelRatio)
.ceil() +
2 * kPaddingPixels;
}
@@ -253,8 +253,7 @@ class BitmapCanvas extends EngineCanvas {
/// * [PersistedPicture._recycleCanvas] which also uses this method
/// for the same reason.
bool isReusable() {
return _devicePixelRatio ==
EnginePlatformDispatcher.browserDevicePixelRatio;
return _devicePixelRatio == EngineFlutterDisplay.instance.browserDevicePixelRatio;
}
/// Returns a "data://" URI containing a representation of the image in this

View File

@@ -4,8 +4,8 @@
import 'dart:async';
import '../display.dart';
import '../dom.dart';
import '../platform_dispatcher.dart';
import 'bitmap_canvas.dart';
import 'dom_canvas.dart';
import 'picture.dart';
@@ -131,9 +131,9 @@ void debugRepaintSurfaceStatsOverlay(PersistedScene scene) {
..fill();
final double physicalScreenWidth =
domWindow.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio;
domWindow.innerWidth! * EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double physicalScreenHeight =
domWindow.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio;
domWindow.innerHeight! * EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
@@ -300,9 +300,9 @@ void debugPrintSurfaceStats(PersistedScene scene, int frameNumber) {
return pixels;
}).fold(0, (int total, int pixels) => total + pixels);
final double physicalScreenWidth =
domWindow.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio;
domWindow.innerWidth! * EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double physicalScreenHeight =
domWindow.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio;
domWindow.innerHeight! * EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double physicsScreenPixelCount =
physicalScreenWidth * physicalScreenHeight;
final double screenPixelRatio = pixelCount / physicsScreenPixelCount;

View File

@@ -60,24 +60,6 @@ class HighContrastSupport {
}
}
class EngineFlutterDisplay extends ui.Display {
EngineFlutterDisplay({
required this.id,
required this.devicePixelRatio,
required this.size,
required this.refreshRate,
});
@override
final int id;
@override
final double devicePixelRatio;
@override
final ui.Size size;
@override
final double refreshRate;
}
/// Platform event dispatcher.
///
/// This is the central entry point for platform messages and configuration
@@ -141,13 +123,8 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
}
@override
Iterable<ui.Display> get displays => <ui.Display>[
EngineFlutterDisplay(
id: 0,
size: ui.Size(domWindow.screen?.width ?? 0, domWindow.screen?.height ?? 0),
devicePixelRatio: domWindow.devicePixelRatio,
refreshRate: 60,
)
Iterable<EngineFlutterDisplay> displays = <EngineFlutterDisplay>[
EngineFlutterDisplay.instance,
];
/// The current list of windows.
@@ -231,13 +208,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
}
}
/// Returns device pixel ratio returned by browser.
static double get browserDevicePixelRatio {
final double ratio = domWindow.devicePixelRatio;
// Guard against WebOS returning 0.
return (ratio == 0.0) ? 1.0 : ratio;
}
/// A callback invoked when any window begins a frame.
///
/// A callback that is invoked to notify the application that it is an
@@ -1297,7 +1267,7 @@ bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) {
final double ratio = double.parse(decoded.arguments as String);
switch (decoded.method) {
case 'setDevicePixelRatio':
window.debugOverrideDevicePixelRatio(ratio);
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(ratio);
EnginePlatformDispatcher.instance.onMetricsChanged!();
return true;
}

View File

@@ -20,8 +20,8 @@ import 'dart:typed_data';
import 'package:ui/ui.dart' as ui;
import 'browser_detection.dart';
import 'display.dart';
import 'dom.dart';
import 'platform_dispatcher.dart';
import 'vector_math.dart';
export 'package:js/js_util.dart' show allowInterop;
@@ -967,8 +967,8 @@ class OffScreenCanvas {
static bool? _supported;
void _updateCanvasCssSize(DomCanvasElement element) {
final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio;
final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio;
final double cssWidth = width / EngineFlutterDisplay.instance.browserDevicePixelRatio;
final double cssHeight = height / EngineFlutterDisplay.instance.browserDevicePixelRatio;
element.style
..position = 'absolute'
..width = '${cssWidth}px'

View File

@@ -14,6 +14,7 @@ import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
import '../engine.dart' show DimensionsProvider, registerHotRestartListener, renderer;
import 'display.dart';
import 'dom.dart';
import 'navigation/history.dart';
import 'platform_dispatcher.dart';
@@ -31,10 +32,8 @@ const int kImplicitViewId = 0;
/// The Web implementation of [ui.SingletonFlutterWindow].
class EngineFlutterWindow extends ui.SingletonFlutterWindow {
EngineFlutterWindow(this.viewId, this.platformDispatcher) {
final EnginePlatformDispatcher engineDispatcher =
platformDispatcher as EnginePlatformDispatcher;
engineDispatcher.viewData[viewId] = this;
engineDispatcher.windowConfigurations[viewId] = const ViewConfiguration();
platformDispatcher.viewData[viewId] = this;
platformDispatcher.windowConfigurations[viewId] = const ViewConfiguration();
if (ui_web.isCustomUrlStrategySet) {
_browserHistory = createHistoryForExistingState(ui_web.urlStrategy);
}
@@ -46,15 +45,13 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
}
@override
ui.Display get display {
return ui.PlatformDispatcher.instance.displays.first;
}
EngineFlutterDisplay get display => EngineFlutterDisplay.instance;
@override
final int viewId;
@override
final ui.PlatformDispatcher platformDispatcher;
final EnginePlatformDispatcher platformDispatcher;
/// Handles the browser history integration to allow users to use the back
/// button, etc.
@@ -203,10 +200,8 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
}
ViewConfiguration get _viewConfiguration {
final EnginePlatformDispatcher engineDispatcher =
platformDispatcher as EnginePlatformDispatcher;
assert(engineDispatcher.windowConfigurations.containsKey(viewId));
return engineDispatcher.windowConfigurations[viewId] ??
assert(platformDispatcher.windowConfigurations.containsKey(viewId));
return platformDispatcher.windowConfigurations[viewId] ??
const ViewConfiguration();
}
@@ -234,7 +229,21 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
}
@override
double get devicePixelRatio => _dimensionsProvider.getDevicePixelRatio();
double get devicePixelRatio => display.devicePixelRatio;
// TODO(mdebbar): Deprecate this and remove it.
// https://github.com/flutter/flutter/issues/127395
void debugOverrideDevicePixelRatio(double? value) {
assert(() {
printWarning(
'The window.debugOverrideDevicePixelRatio API is deprecated and will '
'be removed in a future release. Please use '
'`debugOverrideDevicePixelRatio` from `dart:ui_web` instead.',
);
return true;
}());
display.debugOverrideDevicePixelRatio(value);
}
Stream<ui.Size?> get onResize => _dimensionsProvider.onResize;
@@ -352,33 +361,13 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
ui.Size? debugPhysicalSizeOverride;
}
/// The Web implementation of [ui.SingletonFlutterWindow].
class EngineSingletonFlutterWindow extends EngineFlutterWindow {
EngineSingletonFlutterWindow(
super.windowId, super.platformDispatcher);
@override
double get devicePixelRatio =>
_debugDevicePixelRatio ??
EnginePlatformDispatcher.browserDevicePixelRatio;
/// Overrides the default device pixel ratio.
///
/// This is useful in tests to emulate screens of different dimensions.
void debugOverrideDevicePixelRatio(double? value) {
_debugDevicePixelRatio = value;
}
double? _debugDevicePixelRatio;
}
/// The window singleton.
///
/// `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 EngineSingletonFlutterWindow window =
EngineSingletonFlutterWindow(kImplicitViewId, EnginePlatformDispatcher.instance);
final EngineFlutterWindow window =
EngineFlutterWindow(kImplicitViewId, EnginePlatformDispatcher.instance);
/// The Web implementation of [ui.ViewPadding].
class ViewPadding implements ui.ViewPadding {

View File

@@ -13,8 +13,10 @@ extension SingletonFlutterWindowExtension on ui.SingletonFlutterWindow {
}
/// Overrides the value of [ui.FlutterView.devicePixelRatio] in tests.
///
/// Passing `null` resets the device pixel ratio to the browser's default.
void debugOverrideDevicePixelRatio(double? value) {
(ui.window as EngineSingletonFlutterWindow).debugOverrideDevicePixelRatio(value);
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(value);
}
/// Whether the Flutter engine is running in `flutter test` emulation mode.

View File

@@ -0,0 +1,31 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
group('EngineFlutterDisplay', () {
test('overrides and restores devicePixelRatio', () {
final EngineFlutterDisplay display = EngineFlutterDisplay(
id: 0,
size: const ui.Size(100.0, 100.0),
refreshRate: 60.0,
);
final double originalDevicePixelRatio = display.devicePixelRatio;
display.debugOverrideDevicePixelRatio(99.3);
expect(display.devicePixelRatio, 99.3);
display.debugOverrideDevicePixelRatio(null);
expect(display.devicePixelRatio, originalDevicePixelRatio);
});
});
}

View File

@@ -27,14 +27,14 @@ void main() {
}
void testMain() {
late EngineSingletonFlutterWindow window;
late EngineFlutterWindow window;
setUpAll(() async {
await initializeEngine();
});
setUp(() {
window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance);
window = EngineFlutterWindow(0, EnginePlatformDispatcher.instance);
});
tearDown(() async {

View File

@@ -13,7 +13,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
import '../../common/matchers.dart';
const MethodCodec codec = StandardMethodCodec();
final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance);
final EngineFlutterWindow window = EngineFlutterWindow(0, EnginePlatformDispatcher.instance);
void main() {
internalBootstrapBrowserTest(() => testMain);

View File

@@ -38,7 +38,7 @@ void testMain() {
test('pushTransform implements surface lifecycle', () {
testLayerLifeCycle((ui.SceneBuilder sceneBuilder, ui.EngineLayer? oldLayer) {
return sceneBuilder.pushTransform(
(Matrix4.identity()..scale(domWindow.devicePixelRatio)).toFloat64());
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio)).toFloat64());
}, () {
return '''<s><flt-transform></flt-transform></s>''';
});

View File

@@ -161,7 +161,7 @@ void testMain() {
final SurfaceSceneBuilder builder1 = SurfaceSceneBuilder();
final PersistedTransform a1 =
builder1.pushTransform(
(Matrix4.identity()..scale(domWindow.devicePixelRatio)).toFloat64()) as PersistedTransform;
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio)).toFloat64()) as PersistedTransform;
final PersistedOpacity b1 = builder1.pushOpacity(100) as PersistedOpacity;
final PersistedTransform c1 =
builder1.pushTransform(Matrix4.identity().toFloat64()) as PersistedTransform;
@@ -182,7 +182,7 @@ void testMain() {
final SurfaceSceneBuilder builder2 = SurfaceSceneBuilder();
final PersistedTransform a2 =
builder2.pushTransform(
(Matrix4.identity()..scale(domWindow.devicePixelRatio)).toFloat64(),
(Matrix4.identity()..scale(EngineFlutterDisplay.instance.browserDevicePixelRatio)).toFloat64(),
oldLayer: a1) as PersistedTransform;
final PersistedTransform c2 =
builder2.pushTransform(Matrix4.identity().toFloat64(), oldLayer: c1) as PersistedTransform;

View File

@@ -244,8 +244,8 @@ Future<void> testMain() async {
);
final SceneBuilder sb = SceneBuilder();
sb.pushTransform(Matrix4.diagonal3Values(EnginePlatformDispatcher.browserDevicePixelRatio,
EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64());
sb.pushTransform(Matrix4.diagonal3Values(EngineFlutterDisplay.instance.browserDevicePixelRatio,
EngineFlutterDisplay.instance.browserDevicePixelRatio, 1.0).toFloat64());
sb.pushTransform(Matrix4.rotationZ(math.pi / 2).toFloat64());
sb.pushOffset(0, -500);
sb.pushClipRect(canvasSize);

View File

@@ -487,8 +487,8 @@ void _testCullRectComputation() {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
builder.pushTransform(Matrix4.diagonal3Values(
EnginePlatformDispatcher.browserDevicePixelRatio,
EnginePlatformDispatcher.browserDevicePixelRatio,
EngineFlutterDisplay.instance.browserDevicePixelRatio,
EngineFlutterDisplay.instance.browserDevicePixelRatio,
1.0)
.toFloat64());