Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -6134,6 +6134,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart + ../../../f
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/display_dpr_stream.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart + ../../../flutter/LICENSE
Expand Down Expand Up @@ -8997,6 +8998,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/display_dpr_stream.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export 'engine/vector_math.dart';
export 'engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart';
export 'engine/view_embedder/dimensions_provider/dimensions_provider.dart';
export 'engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart';
export 'engine/view_embedder/display_dpr_stream.dart';
export 'engine/view_embedder/dom_manager.dart';
export 'engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart';
export 'engine/view_embedder/embedding_strategy/embedding_strategy.dart';
Expand Down
16 changes: 13 additions & 3 deletions lib/web_ui/lib/src/engine/html/scene.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:ui/src/engine/display.dart';
import 'package:ui/ui.dart' as ui;

import '../dom.dart';
Expand Down Expand Up @@ -45,9 +46,18 @@ class PersistedScene extends PersistedContainerSurface {

@override
void recomputeTransformAndClip() {
// The scene clip is the size of the entire window.
final ui.Size screen = window.physicalSize;
localClipBounds = ui.Rect.fromLTRB(0, 0, screen.width, screen.height);
// Must be the true DPR from the browser, nothing overridable.
// See: https://github.com/flutter/flutter/issues/143124
final double browserDpr = EngineFlutterDisplay.instance.browserDevicePixelRatio;
// The scene clip is the size of the entire window **in Logical pixels**.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why capital "L"?

//
// Even though the majority of the engine uses `physicalSize`, there are some
// bits (like the HTML renderer, or dynamic view sizing) that are implemented
// using CSS, and CSS operates in logical pixels.
//
// See also: [EngineFlutterView.resize].
final ui.Size bounds = window.physicalSize / browserDpr;
localClipBounds = ui.Rect.fromLTRB(0, 0, bounds.width, bounds.height);
projectedClip = null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';

import 'package:ui/src/engine/display.dart';
import 'package:ui/src/engine/dom.dart';
import 'package:ui/src/engine/window.dart';
import 'package:ui/ui.dart' as ui show Size;
Expand All @@ -12,21 +13,36 @@ import 'dimensions_provider.dart';

/// This class provides observable, real-time dimensions of a host element.
///
/// This class needs a `Stream` of `devicePixelRatio` changes, like the one
/// provided by [DisplayDprStream], because html resize observers do not report
/// DPR changes.
///
/// All the measurements returned from this class are potentially *expensive*,
/// and should be cached as needed. Every call to every method on this class
/// WILL perform actual DOM measurements.
///
/// This broadcasts `null` size events, to match the implementation of the
/// FullPageDimensionsProvider, but it could broadcast the size coming from the
/// DomResizeObserverEntry. Further changes in the engine are required for this
/// to be effective.
class CustomElementDimensionsProvider extends DimensionsProvider {
/// Creates a [CustomElementDimensionsProvider] from a [_hostElement].
CustomElementDimensionsProvider(this._hostElement) {
CustomElementDimensionsProvider(this._hostElement, {
Stream<double>? onDprChange,
}) {
// Send a resize event when the page DPR changes.
_dprChangeStreamSubscription = onDprChange?.listen((_) {
_broadcastSize(null);
});

// Hook up a resize observer on the hostElement (if supported!).
_hostElementResizeObserver = createDomResizeObserver((
List<DomResizeObserverEntry> entries,
DomResizeObserver _,
) {
entries
.map((DomResizeObserverEntry entry) =>
ui.Size(entry.contentRect.width, entry.contentRect.height))
.forEach(_broadcastSize);
for (final DomResizeObserverEntry _ in entries) {
_broadcastSize(null);
}
});

assert(() {
Expand All @@ -45,11 +61,12 @@ class CustomElementDimensionsProvider extends DimensionsProvider {

// Handle resize events
late DomResizeObserver? _hostElementResizeObserver;
final StreamController<ui.Size> _onResizeStreamController =
StreamController<ui.Size>.broadcast();
late StreamSubscription<double>? _dprChangeStreamSubscription;
final StreamController<ui.Size?> _onResizeStreamController =
StreamController<ui.Size?>.broadcast();

// Broadcasts the last seen `Size`.
void _broadcastSize(ui.Size size) {
void _broadcastSize(ui.Size? size) {
_onResizeStreamController.add(size);
}

Expand All @@ -58,16 +75,17 @@ class CustomElementDimensionsProvider extends DimensionsProvider {
super.close();
_hostElementResizeObserver?.disconnect();
// ignore:unawaited_futures
_dprChangeStreamSubscription?.cancel();
// ignore:unawaited_futures
_onResizeStreamController.close();
}

@override
Stream<ui.Size> get onResize => _onResizeStreamController.stream;
Stream<ui.Size?> get onResize => _onResizeStreamController.stream;

@override
ui.Size computePhysicalSize() {
final double devicePixelRatio = getDevicePixelRatio();

final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio;
return ui.Size(
_hostElement.clientWidth * devicePixelRatio,
_hostElement.clientHeight * devicePixelRatio,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:ui/src/engine/dom.dart';
import 'package:ui/src/engine/view_embedder/display_dpr_stream.dart';
import 'package:ui/src/engine/window.dart';
import 'package:ui/ui.dart' as ui show Size;

import '../../display.dart';
import '../../dom.dart';
import 'custom_element_dimensions_provider.dart';
import 'full_page_dimensions_provider.dart';

Expand All @@ -32,18 +32,15 @@ abstract class DimensionsProvider {
/// Creates the appropriate DimensionsProvider depending on the incoming [hostElement].
factory DimensionsProvider.create({DomElement? hostElement}) {
if (hostElement != null) {
return CustomElementDimensionsProvider(hostElement);
return CustomElementDimensionsProvider(
hostElement,
onDprChange: DisplayDprStream.instance.dprChanged,
);
} else {
return FullPageDimensionsProvider();
}
}

/// Returns the DPI reported by the browser.
double getDevicePixelRatio() {
// This is overridable in tests.
return EngineFlutterDisplay.instance.devicePixelRatio;
}

/// Returns the [ui.Size] of the "viewport".
///
/// This function is expensive. It triggers browser layout if there are
Expand All @@ -57,6 +54,16 @@ abstract class DimensionsProvider {
);

/// Returns a Stream with the changes to [ui.Size] (when cheap to get).
///
/// Currently this Stream always returns `null` measurements because the
/// resize event that we use for [FullPageDimensionsProvider] does not contain
/// the new size, so users of this Stream everywhere immediately retrieve the
/// new `physicalSize` from the window.
///
/// The [CustomElementDimensionsProvider] *could* broadcast the new size, but
/// to keep both implementations consistent (and their consumers), for now all
/// events from this Stream are going to be `null` (until we find a performant
/// way to retrieve the dimensions in full-page mode).
Stream<ui.Size?> get onResize;

/// Whether the [DimensionsProvider] instance has been closed or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';

import 'package:ui/src/engine/browser_detection.dart';
import 'package:ui/src/engine/display.dart';
import 'package:ui/src/engine/dom.dart';
import 'package:ui/src/engine/window.dart';
import 'package:ui/ui.dart' as ui show Size;
Expand Down Expand Up @@ -67,7 +68,7 @@ class FullPageDimensionsProvider extends DimensionsProvider {
late double windowInnerWidth;
late double windowInnerHeight;
final DomVisualViewport? viewport = domWindow.visualViewport;
final double devicePixelRatio = getDevicePixelRatio();
final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio;

if (viewport != null) {
if (operatingSystem == OperatingSystem.iOs) {
Expand Down Expand Up @@ -102,7 +103,7 @@ class FullPageDimensionsProvider extends DimensionsProvider {
double physicalHeight,
bool isEditingOnMobile,
) {
final double devicePixelRatio = getDevicePixelRatio();
final double devicePixelRatio = EngineFlutterDisplay.instance.devicePixelRatio;
final DomVisualViewport? viewport = domWindow.visualViewport;
late double windowInnerHeight;

Expand Down
93 changes: 93 additions & 0 deletions lib/web_ui/lib/src/engine/view_embedder/display_dpr_stream.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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 'dart:async';
import 'dart:js_interop';

import 'package:meta/meta.dart';
import 'package:ui/src/engine/display.dart';
import 'package:ui/src/engine/dom.dart';
import 'package:ui/ui.dart' as ui show Display;

/// Provides a stream of `devicePixelRatio` changes for the given display.
///
/// Note that until the Window Management API is generally available, this class
/// only monitors the global `devicePixelRatio` property, provided by the default
/// [EngineFlutterDisplay.instance].
///
/// See: https://developer.mozilla.org/en-US/docs/Web/API/Window_Management_API
class DisplayDprStream {
DisplayDprStream(
this._display, {
@visibleForTesting DebugDisplayDprStreamOverrides? overrides,
}) : _currentDpr = _display.devicePixelRatio,
_debugOverrides = overrides {
// Start listening to DPR changes.
_subscribeToMediaQuery();
}

/// A singleton instance of DisplayDprStream.
static DisplayDprStream instance =
DisplayDprStream(EngineFlutterDisplay.instance);

// The display object that will provide the DPR information.
final ui.Display _display;

// Last reported value of DPR.
double _currentDpr;

// Controls the [dprChanged] broadcast Stream.
final StreamController<double> _dprStreamController =
StreamController<double>.broadcast();

// Object that fires a `change` event for the `_currentDpr`.
late DomEventTarget _dprMediaQuery;

// Creates the media query for the latest known DPR value, and adds a change listener to it.
void _subscribeToMediaQuery() {
if (_debugOverrides?.getMediaQuery != null) {
_dprMediaQuery = _debugOverrides!.getMediaQuery!(_currentDpr);
} else {
_dprMediaQuery = domWindow.matchMedia('(resolution: ${_currentDpr}dppx)');
}
_dprMediaQuery.addEventListenerWithOptions(
'change',
createDomEventListener(_onDprMediaQueryChange),
<String, Object>{
// We only listen `once` because this event only triggers once when the
// DPR changes from `_currentDpr`. Once that happens, we need a new
// `_dprMediaQuery` that is watching the new `_currentDpr`.
//
// By using `once`, we don't need to worry about detaching the event
// listener from the old mediaQuery after we're done with it.
'once': true,
'passive': true,
},
);
}

// Handler of the _dprMediaQuery 'change' event.
//
// This calls subscribe again because events are listened to with `once: true`.
JSVoid _onDprMediaQueryChange(DomEvent _) {
_currentDpr = _display.devicePixelRatio;
_dprStreamController.add(_currentDpr);
// Re-subscribe...
_subscribeToMediaQuery();
}

/// A stream that emits the latest value of [EngineFlutterDisplay.instance.devicePixelRatio].
Stream<double> get dprChanged => _dprStreamController.stream;

// The overrides object that is used for testing.
final DebugDisplayDprStreamOverrides? _debugOverrides;
}

@visibleForTesting
class DebugDisplayDprStreamOverrides {
DebugDisplayDprStreamOverrides({
this.getMediaQuery,
});
final DomEventTarget Function(double currentValue)? getMediaQuery;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@TestOn('browser')
library;

import 'dart:async';

import 'package:test/bootstrap/browser.dart';
Expand Down Expand Up @@ -109,23 +106,48 @@ void doTests() {
});

test('funnels resize events on sizeSource', () async {
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(2.7);

sizeSource
..style.width = '100px'
..style.height = '100px';

expect(await provider.onResize.first, const ui.Size(100, 100));
expect(provider.onResize.first, completes);
expect(provider.computePhysicalSize(), const ui.Size(270, 270));

sizeSource
..style.width = '200px'
..style.height = '200px';

expect(await provider.onResize.first, const ui.Size(200, 200));
expect(provider.onResize.first, completes);
expect(provider.computePhysicalSize(), const ui.Size(540, 540));

sizeSource
..style.width = '300px'
..style.height = '300px';

expect(await provider.onResize.first, const ui.Size(300, 300));
expect(provider.onResize.first, completes);
expect(provider.computePhysicalSize(), const ui.Size(810, 810));
});

test('funnels DPR change events too', () async {
// Override the source of DPR events...
final StreamController<double> dprController =
StreamController<double>.broadcast();

// Inject the dprController stream into the CustomElementDimensionsProvider.
final CustomElementDimensionsProvider provider =
CustomElementDimensionsProvider(
sizeSource,
onDprChange: dprController.stream,
);

// Set and broadcast the mock DPR value
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(3.2);
dprController.add(3.2);

expect(provider.onResize.first, completes);
expect(provider.computePhysicalSize(), const ui.Size(32, 32));
});

test('closed by onHotRestart', () async {
Expand Down
Loading