@@ -15,6 +15,7 @@ import 'configuration.dart';
1515import 'display.dart' ;
1616import 'dom.dart' ;
1717import 'initialization.dart' ;
18+ import 'js_interop/js_app.dart' ;
1819import 'mouse/context_menu.dart' ;
1920import 'mouse/cursor.dart' ;
2021import 'navigation/history.dart' ;
@@ -50,7 +51,9 @@ base class EngineFlutterView implements ui.FlutterView {
5051 /// the Flutter view will be rendered.
5152 factory EngineFlutterView (
5253 EnginePlatformDispatcher platformDispatcher,
53- DomElement hostElement,
54+ DomElement hostElement, {
55+ JsViewConstraints ? viewConstraints,
56+ }
5457 ) = _EngineFlutterViewImpl ;
5558
5659 EngineFlutterView ._(
@@ -59,8 +62,11 @@ base class EngineFlutterView implements ui.FlutterView {
5962 // This is nullable to accommodate the legacy `EngineFlutterWindow`. In
6063 // multi-view mode, the host element is required for each view (as reflected
6164 // by the public `EngineFlutterView` constructor).
62- DomElement ? hostElement,
63- ) : embeddingStrategy = EmbeddingStrategy .create (hostElement: hostElement),
65+ DomElement ? hostElement, {
66+ JsViewConstraints ? viewConstraints,
67+ }
68+ ) : _jsViewConstraints = viewConstraints,
69+ embeddingStrategy = EmbeddingStrategy .create (hostElement: hostElement),
6470 dimensionsProvider = DimensionsProvider .create (hostElement: hostElement) {
6571 // The embeddingStrategy will take care of cleaning up the rootElement on
6672 // hot restart.
@@ -117,7 +123,9 @@ base class EngineFlutterView implements ui.FlutterView {
117123 @override
118124 void render (ui.Scene scene, {ui.Size ? size}) {
119125 assert (! isDisposed, 'Trying to render a disposed EngineFlutterView.' );
120- // TODO(goderbauer): Respect the provided size when "physicalConstraints" are not always tight. See TODO on "physicalConstraints".
126+ if (size != null ) {
127+ resize (size);
128+ }
121129 platformDispatcher.render (scene, this );
122130 }
123131
@@ -145,9 +153,14 @@ base class EngineFlutterView implements ui.FlutterView {
145153
146154 late final PointerBinding pointerBinding;
147155
148- // TODO(goderbauer): Provide API to configure constraints. See also TODO in "render".
149156 @override
150- ViewConstraints get physicalConstraints => ViewConstraints .tight (physicalSize);
157+ ViewConstraints get physicalConstraints {
158+ final double dpr = devicePixelRatio;
159+ final ui.Size currentLogicalSize = physicalSize / dpr;
160+ return ViewConstraints .fromJs (_jsViewConstraints, currentLogicalSize) * dpr;
161+ }
162+
163+ final JsViewConstraints ? _jsViewConstraints;
151164
152165 late final EngineSemanticsOwner semantics = EngineSemanticsOwner (dom.semanticsHost);
153166
@@ -156,6 +169,54 @@ base class EngineFlutterView implements ui.FlutterView {
156169 return _physicalSize ?? = _computePhysicalSize ();
157170 }
158171
172+ /// Resizes the `rootElement` to `newPhysicalSize` by changing its CSS style.
173+ ///
174+ /// This is used by the [render] method, when the framework sends new dimensions
175+ /// for the current Flutter View.
176+ ///
177+ /// Dimensions from the framework are constrained by the [physicalConstraints]
178+ /// that can be configured by the user when adding a view to the app.
179+ ///
180+ /// In practice, this method changes the size of the `rootElement` of the app
181+ /// so it can push/shrink inside its `hostElement` . That way, a Flutter app
182+ /// can change the layout of the container page.
183+ ///
184+ /// ```
185+ /// <p>Some HTML content...</p>
186+ /// +--- (div) hostElement ------------------------------------+
187+ /// | +--- rootElement ---------------------+ |
188+ /// | | | |
189+ /// | | | container |
190+ /// | | size applied to *this* | must be able |
191+ /// | | | to reflow |
192+ /// | | | |
193+ /// | +-------------------------------------+ |
194+ /// +----------------------------------------------------------+
195+ /// <p>More HTML content...</p>
196+ /// ```
197+ ///
198+ /// The `hostElement` needs to be styled in a way that allows its size to flow
199+ /// with its contents. Things like `max-height: 100px; overflow: hidden` will
200+ /// work as expected (by hiding the overflowing part of the flutter app), but
201+ /// if in that case flutter is not made aware of that max-height with
202+ /// `physicalConstraints` , it will end up rendering more pixels that are visible
203+ /// on the screen, with a possible hit to performance.
204+ ///
205+ /// TL;DR: The `viewConstraints` of a Flutter view, must take into consideration
206+ /// the CSS box-model restrictions imposed on its `hostElement` (especially when
207+ /// hiding `overflow` ). Flutter does not attempt to interpret the styles of
208+ /// `hostElement` to compute its `physicalConstraints` , only its current size.
209+ void resize (ui.Size newPhysicalSize) {
210+ // The browser uses CSS, and CSS operates in logical sizes.
211+ final ui.Size logicalSize = newPhysicalSize / devicePixelRatio;
212+ dom.rootElement.style
213+ ..width = '${logicalSize .width }px'
214+ ..height = '${logicalSize .height }px' ;
215+
216+ // Force an update of the physicalSize so it's ready for the renderer.
217+ _computePhysicalSize ();
218+ }
219+
159220 /// Lazily populated and cleared at the end of the frame.
160221 ui.Size ? _physicalSize;
161222
@@ -278,8 +339,10 @@ base class EngineFlutterView implements ui.FlutterView {
278339final class _EngineFlutterViewImpl extends EngineFlutterView {
279340 _EngineFlutterViewImpl (
280341 EnginePlatformDispatcher platformDispatcher,
281- DomElement hostElement,
282- ) : super ._(_nextViewId++ , platformDispatcher, hostElement);
342+ DomElement hostElement, {
343+ JsViewConstraints ? viewConstraints,
344+ }
345+ ) : super ._(_nextViewId++ , platformDispatcher, hostElement, viewConstraints: viewConstraints);
283346}
284347
285348/// The Web implementation of [ui.SingletonFlutterWindow] .
@@ -708,6 +771,27 @@ class ViewConstraints implements ui.ViewConstraints {
708771 minHeight = size.height,
709772 maxHeight = size.height;
710773
774+ /// Converts JsViewConstraints into ViewConstraints.
775+ ///
776+ /// Since JsViewConstraints are expressed by the user, in logical pixels, this
777+ /// conversion uses logical pixels for the current size as well.
778+ ///
779+ /// The resulting ViewConstraints object will be multiplied by devicePixelRatio
780+ /// later to compute the physicalViewConstraints, which is what the framework
781+ /// uses.
782+ factory ViewConstraints .fromJs (
783+ JsViewConstraints ? constraints, ui.Size currentLogicalSize) {
784+ if (constraints == null ) {
785+ return ViewConstraints .tight (currentLogicalSize);
786+ }
787+ return ViewConstraints (
788+ minWidth: _computeMinConstraintValue (constraints.minWidth, currentLogicalSize.width),
789+ minHeight: _computeMinConstraintValue (constraints.minHeight, currentLogicalSize.height),
790+ maxWidth: _computeMaxConstraintValue (constraints.maxWidth, currentLogicalSize.width),
791+ maxHeight: _computeMaxConstraintValue (constraints.maxHeight, currentLogicalSize.height),
792+ );
793+ }
794+
711795 @override
712796 final double minWidth;
713797 @override
@@ -726,6 +810,15 @@ class ViewConstraints implements ui.ViewConstraints {
726810 @override
727811 bool get isTight => minWidth >= maxWidth && minHeight >= maxHeight;
728812
813+ ViewConstraints operator * (double factor) {
814+ return ViewConstraints (
815+ minWidth: minWidth * factor,
816+ maxWidth: maxWidth * factor,
817+ minHeight: minHeight * factor,
818+ maxHeight: maxHeight * factor,
819+ );
820+ }
821+
729822 @override
730823 ViewConstraints operator / (double factor) {
731824 return ViewConstraints (
@@ -774,3 +867,31 @@ class ViewConstraints implements ui.ViewConstraints {
774867 return 'ViewConstraints($width , $height )' ;
775868 }
776869}
870+
871+ // Computes the "min" value for a constraint that takes into account user `desired`
872+ // configuration and the actual available value.
873+ //
874+ // Returns the `desired` value unless it is `null`, in which case it returns the
875+ // `available` value.
876+ double _computeMinConstraintValue (double ? desired, double available) {
877+ assert (desired == null || desired >= 0 , 'Minimum constraint must be >= 0 if set.' );
878+ assert (desired == null || desired.isFinite, 'Minimum constraint must be finite.' );
879+ return desired ?? available;
880+ }
881+
882+ // Computes the "max" value for a constraint that takes into account user `desired`
883+ // configuration and the `available` size.
884+ //
885+ // Returns the `desired` value unless it is `null`, in which case it returns the
886+ // `available` value.
887+ //
888+ // A `desired` value of `Infinity` or `Number.POSITIVE_INFINITY` (from JS) means
889+ // "unconstrained".
890+ //
891+ // This method allows returning values larger than `available`, so the Flutter
892+ // app is able to stretch its container up to a certain value, without being
893+ // fully unconstrained.
894+ double _computeMaxConstraintValue (double ? desired, double available) {
895+ assert (desired == null || desired >= 0 , 'Maximum constraint must be >= 0 if set.' );
896+ return desired ?? available;
897+ }
0 commit comments