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: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 43 additions & 25 deletions lib/web_ui/lib/src/engine/canvaskit/surface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<canvas>` 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;
Expand Down Expand Up @@ -102,6 +107,7 @@ class Surface {
}

ui.Size? _currentSize;
double _currentDevicePixelRatio = -1;

CkSurface _createOrUpdateSurfaces(ui.Size size) {
if (size.isEmpty) {
Expand All @@ -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
Expand All @@ -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 <canvas>.
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.
Expand Down Expand Up @@ -212,8 +230,8 @@ class Surface {

SkSurface? skSurface = canvasKit.MakeOnScreenGLSurface(
_grContext!,
pixelWidth,
pixelHeight,
_pixelWidth,
_pixelHeight,
SkColorSpaceSRGB,
);

Expand Down
13 changes: 9 additions & 4 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions lib/web_ui/test/canvaskit/surface_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
18 changes: 18 additions & 0 deletions lib/web_ui/test/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(<String, dynamic>{
'selectionBase': 10,
'selectionExtent': 4,
}),
EditingState.fromFrameworkMessage(<String, dynamic>{
'selectionBase': 4,
'selectionExtent': 10,
}),
);
});

test('Configure input element from the editing state', () {
final InputElement input = document.getElementsByTagName('input')[0];
_editingState =
Expand Down
14 changes: 13 additions & 1 deletion runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -438,6 +439,17 @@ std::optional<uint32_t> 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<uint64_t>(isolate_group);
} else {
return 0;
}
}

void RuntimeController::LoadDartDeferredLibrary(
intptr_t loading_unit_id,
std::unique_ptr<const fml::Mapping> snapshot_data,
Expand Down
8 changes: 8 additions & 0 deletions runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,14 @@ class RuntimeController : public PlatformConfigurationClient {
///
std::optional<uint32_t> 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
Expand Down
7 changes: 7 additions & 0 deletions shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
26 changes: 21 additions & 5 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down
11 changes: 7 additions & 4 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -99,8 +99,11 @@ void FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
// Called on the platform thread.
std::unique_lock<std::mutex> 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;
Expand Down