Add ImmutableBuffer.fromFilePath (#36623)
diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc
index 5417889..595988a 100644
--- a/lib/ui/dart_ui.cc
+++ b/lib/ui/dart_ui.cc
@@ -83,6 +83,7 @@
V(ImageDescriptor::initEncoded, 3) \
V(ImmutableBuffer::init, 3) \
V(ImmutableBuffer::initFromAsset, 3) \
+ V(ImmutableBuffer::initFromFile, 3) \
V(ImageDescriptor::initRaw, 6) \
V(IsolateNameServerNatives::LookupPortByName, 1) \
V(IsolateNameServerNatives::RegisterPortWithName, 2) \
diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart
index 018060d..de27e80 100644
--- a/lib/ui/painting.dart
+++ b/lib/ui/painting.dart
@@ -6097,12 +6097,30 @@
}).then((int length) => instance.._length = length);
}
+ /// Create a buffer from the file with [path].
+ ///
+ /// Throws an [Exception] if the asset does not exist.
+ static Future<ImmutableBuffer> fromFilePath(String path) {
+ final ImmutableBuffer instance = ImmutableBuffer._(0);
+ return _futurize((_Callback<int> callback) {
+ return instance._initFromFile(path, callback);
+ }).then((int length) {
+ if (length == -1) {
+ throw Exception('Could not load file at $path.');
+ }
+ return instance.._length = length;
+ });
+ }
+
@FfiNative<Handle Function(Handle, Handle, Handle)>('ImmutableBuffer::init')
external String? _init(Uint8List list, _Callback<void> callback);
@FfiNative<Handle Function(Handle, Handle, Handle)>('ImmutableBuffer::initFromAsset')
external String? _initFromAsset(String assetKey, _Callback<int> callback);
+ @FfiNative<Handle Function(Handle, Handle, Handle)>('ImmutableBuffer::initFromFile')
+ external String? _initFromFile(String assetKey, _Callback<int> callback);
+
/// The length, in bytes, of the underlying data.
int get length => _length;
int _length;
diff --git a/lib/ui/painting/immutable_buffer.cc b/lib/ui/painting/immutable_buffer.cc
index 7540135..ee36730 100644
--- a/lib/ui/painting/immutable_buffer.cc
+++ b/lib/ui/painting/immutable_buffer.cc
@@ -6,11 +6,14 @@
#include <cstring>
+#include "flutter/fml/file.h"
+#include "flutter/fml/make_copyable.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/platform_configuration.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
+#include "third_party/tonic/dart_persistent_value.h"
#if FML_OS_ANDROID
#include <sys/mman.h>
@@ -77,6 +80,71 @@
return Dart_Null();
}
+Dart_Handle ImmutableBuffer::initFromFile(Dart_Handle raw_buffer_handle,
+ Dart_Handle file_path_handle,
+ Dart_Handle callback_handle) {
+ UIDartState::ThrowIfUIOperationsProhibited();
+ if (!Dart_IsClosure(callback_handle)) {
+ return tonic::ToDart("Callback must be a function");
+ }
+
+ uint8_t* chars = nullptr;
+ intptr_t file_path_length = 0;
+ Dart_Handle result =
+ Dart_StringToUTF8(file_path_handle, &chars, &file_path_length);
+ if (Dart_IsError(result)) {
+ return tonic::ToDart("File path must be valid UTF8");
+ }
+
+ std::string file_path = std::string{reinterpret_cast<const char*>(chars),
+ static_cast<size_t>(file_path_length)};
+
+ auto* dart_state = UIDartState::Current();
+ auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
+ auto buffer_callback =
+ std::make_unique<tonic::DartPersistentValue>(dart_state, callback_handle);
+ auto buffer_handle = std::make_unique<tonic::DartPersistentValue>(
+ dart_state, raw_buffer_handle);
+
+ auto ui_task = fml::MakeCopyable(
+ [buffer_callback = std::move(buffer_callback),
+ buffer_handle = std::move(buffer_handle)](const sk_sp<SkData>& sk_data,
+ size_t buffer_size) mutable {
+ auto dart_state = buffer_callback->dart_state().lock();
+ if (!dart_state) {
+ return;
+ }
+ tonic::DartState::Scope scope(dart_state);
+ if (!sk_data) {
+ // -1 is used as a sentinel that the file could not be opened.
+ tonic::DartInvoke(buffer_callback->Get(), {tonic::ToDart(-1)});
+ return;
+ }
+ auto buffer = fml::MakeRefCounted<ImmutableBuffer>(sk_data);
+ buffer->AssociateWithDartWrapper(buffer_handle->Get());
+ tonic::DartInvoke(buffer_callback->Get(), {tonic::ToDart(buffer_size)});
+ });
+
+ dart_state->GetConcurrentTaskRunner()->PostTask(
+ [file_path = std::move(file_path),
+ ui_task_runner = std::move(ui_task_runner), ui_task] {
+ auto mapping = std::make_unique<fml::FileMapping>(fml::OpenFile(
+ file_path.c_str(), false, fml::FilePermission::kRead));
+
+ sk_sp<SkData> sk_data;
+ size_t buffer_size = 0;
+ if (mapping->IsValid()) {
+ buffer_size = mapping->GetSize();
+ const void* bytes = static_cast<const void*>(mapping->GetMapping());
+ sk_data = MakeSkDataWithCopy(bytes, buffer_size);
+ }
+ ui_task_runner->PostTask(
+ [sk_data = std::move(sk_data), ui_task = std::move(ui_task),
+ buffer_size]() { ui_task(sk_data, buffer_size); });
+ });
+ return Dart_Null();
+}
+
#if FML_OS_ANDROID
// Compressed image buffers are allocated on the UI thread but are deleted on a
diff --git a/lib/ui/painting/immutable_buffer.h b/lib/ui/painting/immutable_buffer.h
index 5c51f65..daf1d6c 100644
--- a/lib/ui/painting/immutable_buffer.h
+++ b/lib/ui/painting/immutable_buffer.h
@@ -56,6 +56,20 @@
Dart_Handle asset_name_handle,
Dart_Handle callback_handle);
+ /// Initializes a new ImmutableData from an File path.
+ ///
+ /// The zero indexed argument is the caller that will be registered as the
+ /// Dart peer of the native ImmutableBuffer object.
+ ///
+ /// The first indexed argumented is a String corresponding to the file path
+ /// to load.
+ ///
+ /// The second indexed argument is expected to be a void callback to signal
+ /// when the copy has completed.
+ static Dart_Handle initFromFile(Dart_Handle buffer_handle,
+ Dart_Handle file_path_handle,
+ Dart_Handle callback_handle);
+
/// The length of the data in bytes.
size_t length() const {
FML_DCHECK(data_);
diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc
index d67b540..c60fe10 100644
--- a/lib/ui/ui_dart_state.cc
+++ b/lib/ui/ui_dart_state.cc
@@ -38,6 +38,7 @@
std::string advisory_script_uri,
std::string advisory_script_entrypoint,
std::shared_ptr<VolatilePathTracker> volatile_path_tracker,
+ std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
bool enable_impeller)
: task_runners(task_runners),
snapshot_delegate(std::move(snapshot_delegate)),
@@ -48,6 +49,7 @@
advisory_script_uri(std::move(advisory_script_uri)),
advisory_script_entrypoint(std::move(advisory_script_entrypoint)),
volatile_path_tracker(std::move(volatile_path_tracker)),
+ concurrent_task_runner(concurrent_task_runner),
enable_impeller(enable_impeller) {}
UIDartState::UIDartState(
@@ -145,6 +147,11 @@
return context_.volatile_path_tracker;
}
+std::shared_ptr<fml::ConcurrentTaskRunner>
+UIDartState::GetConcurrentTaskRunner() const {
+ return context_.concurrent_task_runner;
+}
+
void UIDartState::ScheduleMicrotask(Dart_Handle closure) {
if (tonic::CheckAndHandleError(closure) || !Dart_IsClosure(closure)) {
return;
diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h
index 2103cda..a1dff34 100644
--- a/lib/ui/ui_dart_state.h
+++ b/lib/ui/ui_dart_state.h
@@ -54,6 +54,7 @@
std::string advisory_script_uri,
std::string advisory_script_entrypoint,
std::shared_ptr<VolatilePathTracker> volatile_path_tracker,
+ std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
bool enable_impeller);
/// The task runners used by the shell hosting this runtime controller. This
@@ -93,6 +94,10 @@
/// Cache for tracking path volatility.
std::shared_ptr<VolatilePathTracker> volatile_path_tracker;
+ /// The task runner whose tasks may be executed concurrently on a pool
+ /// of shared worker threads.
+ std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner;
+
/// Whether Impeller is enabled or not.
bool enable_impeller = false;
};
@@ -128,6 +133,8 @@
std::shared_ptr<VolatilePathTracker> GetVolatilePathTracker() const;
+ std::shared_ptr<fml::ConcurrentTaskRunner> GetConcurrentTaskRunner() const;
+
fml::WeakPtr<SnapshotDelegate> GetSnapshotDelegate() const;
fml::WeakPtr<ImageDecoder> GetImageDecoder() const;
diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart
index c5ffd40..8b4dbed 100644
--- a/lib/web_ui/lib/painting.dart
+++ b/lib/web_ui/lib/painting.dart
@@ -728,6 +728,10 @@
throw UnsupportedError('ImmutableBuffer.fromAsset is not supported on the web.');
}
+ static Future<ImmutableBuffer> fromFilePath(String path) async {
+ throw UnsupportedError('ImmutableBuffer.fromFilePath is not supported on the web.');
+ }
+
Uint8List? _list;
int get length => _length;
diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc
index 9c95a58..24fd13e 100644
--- a/runtime/runtime_controller.cc
+++ b/runtime/runtime_controller.cc
@@ -61,7 +61,8 @@
std::move(io_manager), context_.unref_queue,
std::move(image_decoder), std::move(image_generator_registry),
std::move(advisory_script_uri), std::move(advisory_script_entrypoint),
- context_.volatile_path_tracker, context_.enable_impeller};
+ context_.volatile_path_tracker, context_.concurrent_task_runner,
+ context_.enable_impeller};
auto result =
std::make_unique<RuntimeController>(p_client, //
vm_, //
diff --git a/shell/common/engine.cc b/shell/common/engine.cc
index b80d866..cbd2f55 100644
--- a/shell/common/engine.cc
+++ b/shell/common/engine.cc
@@ -100,6 +100,7 @@
settings_.advisory_script_uri, // advisory script uri
settings_.advisory_script_entrypoint, // advisory script entrypoint
std::move(volatile_path_tracker), // volatile path tracker
+ vm.GetConcurrentWorkerTaskRunner(), // concurrent task runner
settings_.enable_impeller, // enable impeller
});
}
diff --git a/testing/dart/assets_test.dart b/testing/dart/assets_test.dart
index 64943c2..198f952 100644
--- a/testing/dart/assets_test.dart
+++ b/testing/dart/assets_test.dart
@@ -21,12 +21,29 @@
expect(error is Exception, true);
});
+ test('Loading a file that does not exist returns null', () async {
+ Object? error;
+ try {
+ await ImmutableBuffer.fromFilePath('ThisDoesNotExist');
+ } catch (err) {
+ error = err;
+ }
+ expect(error, isNotNull);
+ expect(error is Exception, true);
+ });
+
test('returns the bytes of a bundled asset', () async {
final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat.jpg');
expect(buffer.length == 354679, true);
});
+ test('returns the bytes of a file', () async {
+ final ImmutableBuffer buffer = await ImmutableBuffer.fromFilePath('flutter/lib/ui/fixtures/DashInNooglerHat.jpg');
+
+ expect(buffer.length == 354679, true);
+ });
+
test('Can load an asset with a space in the key', () async {
// This assets actual path is "fixtures/DashInNooglerHat%20WithSpace.jpg"
final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat WithSpace.jpg');