diff --git a/DEPS b/DEPS index f1f21ada982e5..892aeb5752663 100644 --- a/DEPS +++ b/DEPS @@ -35,7 +35,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'e5dd92c3ca766810e0e5a02d8725b1cebc19f564', + 'dart_revision': 'bc1fa173477efb6192bddd5f95c1ad0cccbc8d6f', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index de9c75aa3b29c..7ad4b2aec1c93 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -59,6 +59,11 @@ class Surface { /// due to the browser tab becoming dormant. final html.Element htmlElement = html.Element.tag('flt-canvas-container'); + /// The underlying `` element used for this surface. + html.CanvasElement? htmlCanvas; + int _pixelWidth = -1; + int _pixelHeight = -1; + /// Specify the GPU resource cache limits. void setSkiaResourceCacheMaxBytes(int bytes) { _skiaCacheBytes = bytes; @@ -102,6 +107,7 @@ class Surface { } ui.Size? _currentSize; + double _currentDevicePixelRatio = -1; CkSurface _createOrUpdateSurfaces(ui.Size size) { if (size.isEmpty) { @@ -116,9 +122,13 @@ class Surface { size.width <= previousSize.width && size.height <= previousSize.height) { // The existing surface is still reusable. + if (window.devicePixelRatio != _currentDevicePixelRatio) { + _updateLogicalHtmlCanvasSize(); + } return _surface!; } + _currentDevicePixelRatio = window.devicePixelRatio; _currentSize = _currentSize == null // First frame. Allocate a canvas of the exact size as the window. The // window is frequently never resized, particularly on mobile, so using @@ -131,36 +141,44 @@ class Surface { _surface = null; _addedToScene = false; - return _surface = _wrapHtmlCanvas(_currentSize!); + return _surface = _createNewSurface(_currentSize!); } - CkSurface _wrapHtmlCanvas(ui.Size physicalSize) { - // Clear the container, if it's not empty. - while (htmlElement.firstChild != null) { - htmlElement.firstChild!.remove(); - } + /// Sets the CSS size of the canvas so that canvas pixels are 1:1 with device + /// pixels. + /// + /// The logical size of the canvas is not based on the size of the window + /// but on the size of the canvas, which, due to `ceil()` above, may not be + /// the same as the window. We do not round/floor/ceil the logical size as + /// CSS pixels can contain more than one physical pixel and therefore to + /// match the size of the window precisely we use the most precise floating + /// point value we can get. + void _updateLogicalHtmlCanvasSize() { + final double logicalWidth = _pixelWidth / ui.window.devicePixelRatio; + final double logicalHeight = _pixelHeight / ui.window.devicePixelRatio; + htmlCanvas!.style + ..width = '${logicalWidth}px' + ..height = '${logicalHeight}px'; + } + + /// This function is expensive. + /// + /// It's better to reuse surface if possible. + CkSurface _createNewSurface(ui.Size physicalSize) { + // Clear the container, if it's not empty. We're going to create a new . + this.htmlCanvas?.remove(); // If `physicalSize` is not precise, use a slightly bigger canvas. This way // we ensure that the rendred picture covers the entire browser window. - final int pixelWidth = physicalSize.width.ceil(); - final int pixelHeight = physicalSize.height.ceil(); + _pixelWidth = physicalSize.width.ceil(); + _pixelHeight = physicalSize.height.ceil(); final html.CanvasElement htmlCanvas = html.CanvasElement( - width: pixelWidth, - height: pixelHeight, + width: _pixelWidth, + height: _pixelHeight, ); - - // The logical size of the canvas is not based on the size of the window - // but on the size of the canvas, which, due to `ceil()` above, may not be - // the same as the window. We do not round/floor/ceil the logical size as - // CSS pixels can contain more than one physical pixel and therefore to - // match the size of the window precisely we use the most precise floating - // point value we can get. - final double logicalWidth = pixelWidth / ui.window.devicePixelRatio; - final double logicalHeight = pixelHeight / ui.window.devicePixelRatio; - htmlCanvas.style - ..position = 'absolute' - ..width = '${logicalWidth}px' - ..height = '${logicalHeight}px'; + this.htmlCanvas = htmlCanvas; + htmlCanvas.style.position = 'absolute'; + _updateLogicalHtmlCanvasSize(); // When the browser tab using WebGL goes dormant the browser and/or OS may // decide to clear GPU resources to let other tabs/programs use the GPU. @@ -212,8 +230,8 @@ class Surface { SkSurface? skSurface = canvasKit.MakeOnScreenGLSurface( _grContext!, - pixelWidth, - pixelHeight, + _pixelWidth, + _pixelHeight, SkColorSpaceSRGB, ); 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 b131a08fa153f..d2a7eb0c6cf23 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 @@ -373,7 +373,11 @@ class AutofillInfo { /// The current text and selection state of a text field. @visibleForTesting class EditingState { - EditingState({this.text, this.baseOffset = 0, this.extentOffset = 0}); + EditingState({this.text, int? baseOffset, int? extentOffset}) : + // Don't allow negative numbers. Pick the smallest selection index for base. + baseOffset = math.max(0, math.min(baseOffset ?? 0, extentOffset ?? 0)), + // Don't allow negative numbers. Pick the greatest selection index for extent. + extentOffset = math.max(0, math.max(baseOffset ?? 0, extentOffset ?? 0)); /// Creates an [EditingState] instance using values from an editing state Map /// coming from Flutter. @@ -401,9 +405,10 @@ class EditingState { final String? text = flutterEditingState['text']; return EditingState( - text: text, - baseOffset: math.max(0, selectionBase), - extentOffset: math.max(0, selectionExtent)); + text: text, + baseOffset: selectionBase, + extentOffset: selectionExtent, + ); } /// Creates an [EditingState] instance using values from the editing element diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index b5c98bae3661c..ddf45c1f9c926 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -27,10 +27,14 @@ void testMain() { // Expect exact requested dimensions. expect(original.width(), 9); expect(original.height(), 19); + expect(surface.htmlCanvas!.style.width, '9px'); + expect(surface.htmlCanvas!.style.height, '19px'); // Shrinking reuses the existing surface straight-up. final CkSurface shrunk = surface.acquireFrame(ui.Size(5, 15)).skiaSurface; expect(shrunk, same(original)); + expect(surface.htmlCanvas!.style.width, '9px'); + expect(surface.htmlCanvas!.style.height, '19px'); // The first increase will allocate a new surface, but will overallocate // by 40% to accommodate future increases. @@ -40,6 +44,8 @@ void testMain() { // Expect overallocated dimensions expect(firstIncrease.width(), 14); expect(firstIncrease.height(), 28); + expect(surface.htmlCanvas!.style.width, '14px'); + expect(surface.htmlCanvas!.style.height, '28px'); // Subsequent increases within 40% reuse the old surface. final CkSurface secondIncrease = surface.acquireFrame(ui.Size(11, 22)).skiaSurface; @@ -52,6 +58,8 @@ void testMain() { // Also over-allocated expect(huge.width(), 28); expect(huge.height(), 56); + expect(surface.htmlCanvas!.style.width, '28px'); + expect(surface.htmlCanvas!.style.height, '56px'); // Shrink again. Reuse the last allocated surface. final CkSurface shrunk2 = surface.acquireFrame(ui.Size(5, 15)).skiaSurface; @@ -88,5 +96,34 @@ void testMain() { // Firefox doesn't have the WEBGL_lose_context extension. skip: isFirefox || isIosSafari, ); + + // Regression test for https://github.com/flutter/flutter/issues/75286 + test('updates canvas logical size when device-pixel ratio changes', () { + final Surface surface = Surface(HtmlViewEmbedder()); + final CkSurface original = surface.acquireFrame(ui.Size(10, 16)).skiaSurface; + + expect(original.width(), 10); + expect(original.height(), 16); + expect(surface.htmlCanvas!.style.width, '10px'); + expect(surface.htmlCanvas!.style.height, '16px'); + + // Increase device-pixel ratio: this makes CSS pixels bigger, so we need + // fewer of them to cover the browser window. + window.debugOverrideDevicePixelRatio(2.0); + final CkSurface highDpr = surface.acquireFrame(ui.Size(10, 16)).skiaSurface; + expect(highDpr.width(), 10); + expect(highDpr.height(), 16); + expect(surface.htmlCanvas!.style.width, '5px'); + expect(surface.htmlCanvas!.style.height, '8px'); + + // Decrease device-pixel ratio: this makes CSS pixels smaller, so we need + // more of them to cover the browser window. + window.debugOverrideDevicePixelRatio(0.5); + final CkSurface lowDpr = surface.acquireFrame(ui.Size(10, 16)).skiaSurface; + expect(lowDpr.width(), 10); + expect(lowDpr.height(), 16); + expect(surface.htmlCanvas!.style.width, '20px'); + expect(surface.htmlCanvas!.style.height, '32px'); + }); }, skip: isIosSafari); } diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index efcf24df89585..d573b1dbcacac 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/text_editing_test.dart @@ -2095,6 +2095,24 @@ void testMain() { ); }); + test('Fix flipped base and extent offsets', () { + expect( + EditingState(baseOffset: 10, extentOffset: 4), + EditingState(baseOffset: 4, extentOffset: 10), + ); + + expect( + EditingState.fromFrameworkMessage({ + 'selectionBase': 10, + 'selectionExtent': 4, + }), + EditingState.fromFrameworkMessage({ + 'selectionBase': 4, + 'selectionExtent': 10, + }), + ); + }); + test('Configure input element from the editing state', () { final InputElement input = document.getElementsByTagName('input')[0]; _editingState = diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 42a6ea6bc9d67..06cf0f16cc103 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -392,7 +392,8 @@ bool RuntimeController::LaunchRootIsolate( dart_entrypoint, // dart_entrypoint_library, // std::move(isolate_configuration), // - volatile_path_tracker_ // + volatile_path_tracker_, // + spawning_isolate_.lock().get() // ) .lock(); @@ -438,6 +439,17 @@ std::optional RuntimeController::GetRootIsolateReturnCode() { return root_isolate_return_code_; } +uint64_t RuntimeController::GetRootIsolateGroup() const { + auto isolate = root_isolate_.lock(); + if (isolate) { + auto isolate_scope = tonic::DartIsolateScope(isolate->isolate()); + Dart_IsolateGroup isolate_group = Dart_CurrentIsolateGroup(); + return reinterpret_cast(isolate_group); + } else { + return 0; + } +} + void RuntimeController::LoadDartDeferredLibrary( intptr_t loading_unit_id, std::unique_ptr snapshot_data, diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 09ff677814e17..4ae96f9512e16 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -494,6 +494,14 @@ class RuntimeController : public PlatformConfigurationClient { /// std::optional GetRootIsolateReturnCode(); + //---------------------------------------------------------------------------- + /// @brief Get an identifier that represents the Dart isolate group the + /// root isolate is in. + /// + /// @return The root isolate isolate group identifier, zero if one can't + /// be established. + uint64_t GetRootIsolateGroup() const; + //-------------------------------------------------------------------------- /// @brief Loads the Dart shared library into the Dart VM. When the /// Dart library is loaded successfully, the Dart future diff --git a/shell/common/engine.h b/shell/common/engine.h index a5581611e5c31..29c687693089c 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -871,6 +871,13 @@ class Engine final : public RuntimeDelegate, const std::string error_message, bool transient); + //-------------------------------------------------------------------------- + /// @brief Accessor for the RuntimeController. + /// + const RuntimeController* GetRuntimeController() const { + return runtime_controller_.get(); + } + private: Engine::Delegate& delegate_; const Settings settings_; diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index aca67acd5c081..74a0b74e633cf 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2480,11 +2480,27 @@ TEST_F(ShellTest, Spawn) { ASSERT_NE(nullptr, spawn.get()); ASSERT_TRUE(ValidateShell(spawn.get())); - PostSync(spawner->GetTaskRunners().GetUITaskRunner(), [&spawn] { - // Check second shell ran the second entrypoint. - ASSERT_EQ("testCanLaunchSecondaryIsolate", - spawn->GetEngine()->GetLastEntrypoint()); - }); + PostSync(spawner->GetTaskRunners().GetUITaskRunner(), + [&spawn, &spawner] { + // Check second shell ran the second entrypoint. + ASSERT_EQ("testCanLaunchSecondaryIsolate", + spawn->GetEngine()->GetLastEntrypoint()); + + // TODO(74520): Remove conditional once isolate groups are + // supported by JIT. + if (DartVM::IsRunningPrecompiledCode()) { + ASSERT_NE(spawner->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup(), + 0u); + ASSERT_EQ(spawner->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup(), + spawn->GetEngine() + ->GetRuntimeController() + ->GetRootIsolateGroup()); + } + }); PostSync( spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] { diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 60ac0cb6954bc..f098803c74d2d 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -20,10 +20,10 @@ static bool SurfaceWillUpdate(size_t cur_width, size_t target_height) { // TODO (https://github.com/flutter/flutter/issues/65061) : Avoid special // handling for zero dimensions. - bool non_zero_dims = target_height > 0 && target_width > 0; + bool non_zero_target_dims = target_height > 0 && target_width > 0; bool not_same_size = (cur_height != target_height) || (cur_width != target_width); - return non_zero_dims && not_same_size; + return non_zero_target_dims && not_same_size; } FlutterWindowsView::FlutterWindowsView( @@ -99,8 +99,11 @@ void FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { // Called on the platform thread. std::unique_lock lock(resize_mutex_); - bool surface_will_update = SurfaceWillUpdate( - resize_target_width_, resize_target_height_, width, height); + EGLint surface_width, surface_height; + surface_manager_->GetSurfaceDimensions(&surface_width, &surface_height); + + bool surface_will_update = + SurfaceWillUpdate(surface_width, surface_height, width, height); if (surface_will_update) { resize_status_ = ResizeState::kResizeStarted; resize_target_width_ = width;