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
4 changes: 2 additions & 2 deletions DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ vars = {

# WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY
# See `lib/web_ui/README.md` for how to roll CanvasKit to a new version.
'canvaskit_cipd_instance': 'NcwvqeeKK7urddCbEdDvHytdaCiCA_8-4oS_T_ouGfgC',
'canvaskit_cipd_instance': '8MSYGWVWzrTJIoVL00ZquruZs-weuwLBy1kt1AawJiIC',

# When updating the Dart revision, ensure that all entries that are
# dependencies of Dart are also updated to match the entries in the
# Dart SDK's DEPS file for that revision of Dart. The DEPS file for
# Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS.
# You can use //tools/dart/create_updated_flutter_deps.py to produce
# updated revision list of existing dependencies.
'dart_revision': '547d54e13cc8c1ce9279792fd16c189663e18f96',
'dart_revision': '0180af250ff518cc0fa494a4eb484ce11ec1e62c',

# 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
2 changes: 1 addition & 1 deletion ci/licenses_golden/licenses_third_party
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Signature: 51c303e95a17cde2eb9ee7806c16a1c5
Signature: cc2abaf0233d38199643282d27336c28

UNUSED LICENSES:

Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/dev/canvaskit_lock.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Specifies the version of CanvasKit to use for Flutter Web apps.
#
# See `lib/web_ui/README.md` for how to update this file.
canvaskit_version: "0.31.0"
canvaskit_version: "0.33.0"
7 changes: 6 additions & 1 deletion lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,14 @@ class SkColorType {
class SkAnimatedImage {
external int getFrameCount();

/// Returns duration in milliseconds.
external int getRepetitionCount();

/// Returns duration in milliseconds.
external int currentFrameDuration();

/// Advances to the next frame and returns its duration in milliseconds.
external int decodeNextFrame();

external SkImage makeImageAtCurrentFrame();
external int width();
external int height();
Expand Down
38 changes: 28 additions & 10 deletions lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
int _frameCount = 0;
int _repetitionCount = -1;

/// The index to the next frame to be decoded.
int _nextFrameIndex = 0;
/// Current frame index.
int _currentFrameIndex = 0;

@override
SkAnimatedImage createDefault() {
Expand All @@ -48,11 +48,16 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
_frameCount = animatedImage.getFrameCount();
_repetitionCount = animatedImage.getRepetitionCount();

// If the object has been deleted then resurrected, it may already have
// iterated over some frames. We need to skip over them.
for (int i = 0; i < _nextFrameIndex; i++) {
// Normally CanvasKit initializes `SkAnimatedImage` to point to the first
// frame in the animation. However, if the Skia object has been deleted then
// resurrected, the framework/app may already have advanced to one of the
// subsequent frames. When that happens the value of _currentFrameIndex will
// be something other than zero, and we need to tell the decoder to skip
// over the previous frames to point to the current one.
for (int i = 0; i < _currentFrameIndex; i++) {
animatedImage.decodeNextFrame();
}

return animatedImage;
}

Expand Down Expand Up @@ -100,10 +105,23 @@ class CkAnimatedImage extends ManagedSkiaObject<SkAnimatedImage>
@override
Future<ui.FrameInfo> getNextFrame() {
assert(_debugCheckIsNotDisposed());
final int durationMillis = skiaObject.decodeNextFrame();
final Duration duration = Duration(milliseconds: durationMillis);
final CkImage image = CkImage(skiaObject.makeImageAtCurrentFrame());
_nextFrameIndex = (_nextFrameIndex + 1) % _frameCount;
return Future<ui.FrameInfo>.value(AnimatedImageFrameInfo(duration, image));
final SkAnimatedImage animatedImage = skiaObject;

// SkAnimatedImage comes pre-initialized to point to the current frame (by
// default the first frame, and, with some special resurrection logic in
// `createDefault`, to a subsequent frame if resurrection happens in the
// middle of animation). Flutter's `Codec` semantics is to initialize to
// point to "just before the first frame", i.e. the first invocation of
// `getNextFrame` returns the first frame. Therefore, we have to read the
// current Skia frame, then advance SkAnimatedImage to the next frame, and
// return the current frame.
final ui.FrameInfo currentFrame = AnimatedImageFrameInfo(
Duration(milliseconds: animatedImage.currentFrameDuration()),
CkImage(animatedImage.makeImageAtCurrentFrame()),
);

animatedImage.decodeNextFrame();
_currentFrameIndex = (_currentFrameIndex + 1) % _frameCount;
return Future<ui.FrameInfo>.value(currentFrame);
}
}
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import 'package:js/js.dart';
/// The version of CanvasKit used by the web engine by default.
// DO NOT EDIT THE NEXT LINE OF CODE MANUALLY
// See `lib/web_ui/README.md` for how to roll CanvasKit to a new version.
const String _canvaskitVersion = '0.31.0';
const String _canvaskitVersion = '0.33.0';

/// The Web Engine configuration for the current application.
FlutterConfiguration get configuration => _configuration ??= FlutterConfiguration(_jsConfiguration);
Expand Down
10 changes: 7 additions & 3 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -782,9 +782,10 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy {
// On Safari Desktop, when a form is focused, it opens an autofill menu
// immediately.
// Flutter framework sends `setEditableSizeAndTransform` for informing
// the engine about the location of the text field. This call will
// arrive after `show` call. Therefore form is placed, when
// `setEditableSizeAndTransform` method is called and focus called on the
// the engine about the location of the text field. This call may arrive
// after the first `show` call, depending on the text input widget's
// implementation. Therefore form is placed, when
// `setEditableSizeAndTransform` method is called and focus called on the
// form only after placing it to the correct position and only once after
// that. Calling focus multiple times causes flickering.
focusedFormElement!.focus();
Expand All @@ -800,6 +801,9 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy {

@override
void initializeElementPlacement() {
if (geometry != null) {
placeElement();
}
activeDomElement.focus();
}
}
Expand Down
9 changes: 4 additions & 5 deletions lib/web_ui/test/canvaskit/image_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) {
expect(image.repetitionCount, -1);

final ui.FrameInfo frame1 = await image.getNextFrame();
await expectFrameData(frame1, <int>[0, 255, 0, 255]);
await expectFrameData(frame1, <int>[255, 0, 0, 255]);
final ui.FrameInfo frame2 = await image.getNextFrame();
await expectFrameData(frame2, <int>[0, 0, 255, 255]);
await expectFrameData(frame2, <int>[0, 255, 0, 255]);

// Pretend that the image is temporarily deleted.
image.delete();
image.didDelete();

// Check that we got the 3rd frame after resurrection.
final ui.FrameInfo frame3 = await image.getNextFrame();
await expectFrameData(frame3, <int>[255, 0, 0, 255]);
await expectFrameData(frame3, <int>[0, 0, 255, 255]);

testCollector.collectNow();
});
Expand Down Expand Up @@ -548,11 +548,10 @@ void _testCkAnimatedImage() {

test('CkAnimatedImage toByteData(RGBA)', () async {
final CkAnimatedImage image = CkAnimatedImage.decodeFromBytes(kAnimatedGif, 'test');
// TODO(yjbanov): frame sequence is wrong (https://github.com/flutter/flutter/issues/95281)
const List<List<int>> expectedColors = <List<int>>[
<int>[255, 0, 0, 255],
<int>[0, 255, 0, 255],
<int>[0, 0, 255, 255],
<int>[255, 0, 0, 255],
];
for (int i = 0; i < image.frameCount; i++) {
final ui.FrameInfo frame = await image.getNextFrame();
Expand Down
34 changes: 34 additions & 0 deletions lib/web_ui/test/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,40 @@ void testMain() {
expect(spy.messages, isEmpty);
});

test('setClient, setEditingState, setSizeAndTransform, show - input element is put into the DOM', () {
editingStrategy = SafariDesktopTextEditingStrategy(textEditing!);
textEditing!.debugTextEditingStrategyOverride = editingStrategy;
final MethodCall setClient = MethodCall(
'TextInput.setClient', <dynamic>[123, flutterSinglelineConfig]);
sendFrameworkMessage(codec.encodeMethodCall(setClient));

const MethodCall setEditingState =
MethodCall('TextInput.setEditingState', <String, dynamic>{
'text': 'abcd',
'selectionBase': 2,
'selectionExtent': 3,
});
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));

// Editing shouldn't have started yet.
expect(document.activeElement, document.body);

// The "setSizeAndTransform" message has to be here before we call
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
// we don't put the input element into the DOM until we get its correct
// dimensions from the framework.
final MethodCall setSizeAndTransform =
configureSetSizeAndTransformMethodCall(150, 50,
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList());
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));

const MethodCall show = MethodCall('TextInput.show');
sendFrameworkMessage(codec.encodeMethodCall(show));

expect(defaultTextEditingRoot.activeElement,
textEditing!.strategy.domElement);
});

test('setClient, setEditingState, show, updateConfig, clearClient', () {
final MethodCall setClient = MethodCall('TextInput.setClient', <dynamic>[
123,
Expand Down
10 changes: 6 additions & 4 deletions shell/common/rasterizer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
}
}

void Rasterizer::TeardownExternalViewEmbedder() {
if (external_view_embedder_) {
external_view_embedder_->Teardown();
}
}

void Rasterizer::Teardown() {
auto context_switch =
surface_ ? surface_->MakeRenderContextCurrent() : nullptr;
Expand All @@ -97,10 +103,6 @@ void Rasterizer::Teardown() {
raster_thread_merger_->UnMergeNowIfLastOne();
raster_thread_merger_->SetMergeUnmergeCallback(nullptr);
}

if (external_view_embedder_) {
external_view_embedder_->Teardown();
}
}

void Rasterizer::EnableThreadMergerIfNeeded() {
Expand Down
7 changes: 7 additions & 0 deletions shell/common/rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ class Rasterizer final : public SnapshotDelegate {
///
void Teardown();

//----------------------------------------------------------------------------
/// @brief Releases any resource used by the external view embedder.
/// For example, overlay surfaces or Android views.
/// On Android, this method post a task to the platform thread,
/// and waits until it completes.
void TeardownExternalViewEmbedder();

//----------------------------------------------------------------------------
/// @brief Notifies the rasterizer that there is a low memory situation
/// and it must purge as many unnecessary resources as possible.
Expand Down
5 changes: 5 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ void Shell::OnPlatformViewDestroyed() {
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetRasterTaskRunner(),
raster_task);
latch.Wait();
// On Android, the external view embedder posts a task to the platform thread,
// and waits until it completes.
// As a result, the platform thread must not be blocked prior to calling
// this method.
rasterizer_->TeardownExternalViewEmbedder();
}

// |PlatformView::Delegate|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h"

#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/task_runner.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/platform/android/surface/android_surface.h"

Expand All @@ -12,12 +14,14 @@ namespace flutter {
AndroidExternalViewEmbedder::AndroidExternalViewEmbedder(
const AndroidContext& android_context,
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
std::shared_ptr<AndroidSurfaceFactory> surface_factory)
std::shared_ptr<AndroidSurfaceFactory> surface_factory,
TaskRunners task_runners)
: ExternalViewEmbedder(),
android_context_(android_context),
jni_facade_(jni_facade),
surface_factory_(surface_factory),
surface_pool_(std::make_unique<SurfacePool>()) {}
surface_pool_(std::make_unique<SurfacePool>()),
task_runners_(task_runners) {}

// |ExternalViewEmbedder|
void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
Expand Down Expand Up @@ -264,8 +268,8 @@ void AndroidExternalViewEmbedder::BeginFrame(

// The surface size changed. Therefore, destroy existing surfaces as
// the existing surfaces in the pool can't be recycled.
if (frame_size_ != frame_size && raster_thread_merger->IsOnPlatformThread()) {
surface_pool_->DestroyLayers(jni_facade_);
if (frame_size_ != frame_size) {
DestroySurfaces();
}
surface_pool_->SetFrameSize(frame_size);
// JNI method must be called on the platform thread.
Expand Down Expand Up @@ -300,7 +304,18 @@ bool AndroidExternalViewEmbedder::SupportsDynamicThreadMerging() {

// |ExternalViewEmbedder|
void AndroidExternalViewEmbedder::Teardown() {
surface_pool_->DestroyLayers(jni_facade_);
DestroySurfaces();
}

// |ExternalViewEmbedder|
void AndroidExternalViewEmbedder::DestroySurfaces() {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetPlatformTaskRunner(),
[&]() {
surface_pool_->DestroyLayers(jni_facade_);
latch.Signal();
});
latch.Wait();
}

} // namespace flutter
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <unordered_map>

#include "flutter/common/task_runners.h"
#include "flutter/flow/embedded_views.h"
#include "flutter/flow/rtree.h"
#include "flutter/shell/platform/android/context/android_context.h"
Expand All @@ -32,7 +33,8 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder {
AndroidExternalViewEmbedder(
const AndroidContext& android_context,
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
std::shared_ptr<AndroidSurfaceFactory> surface_factory);
std::shared_ptr<AndroidSurfaceFactory> surface_factory,
TaskRunners task_runners);

// |ExternalViewEmbedder|
void PrerollCompositeEmbeddedView(
Expand Down Expand Up @@ -99,6 +101,9 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder {
// Holds surfaces. Allows to recycle surfaces or allocate new ones.
const std::unique_ptr<SurfacePool> surface_pool_;

// The task runners.
const TaskRunners task_runners_;

// The size of the root canvas.
SkISize frame_size_;

Expand Down Expand Up @@ -126,6 +131,11 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder {
// The number of platform views in the previous frame.
int64_t previous_frame_view_count_;

// Destroys the surfaces created from the surface factory.
// This method schedules a task on the platform thread, and waits for
// the task until it completes.
void DestroySurfaces();

// Resets the state.
void Reset();

Expand Down
Loading