From 641d4e0e426c144e4b2a9856588bf9ea70f0e8f9 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 7 Oct 2020 15:34:59 -0700 Subject: [PATCH 001/219] Add an adjustment to currentLineWidth comparisons when pushing greedy line breaks (#21356) This is similar to the workaround used for https://github.com/flutter/flutter/issues/30347 The Minikin line breaker inserts greedy breaks based on a comparison of postBreak width and currentLineWidth. currentLineWidth is provided by the framework based on previous calls to Layout::measureText. That calculation may not exactly match the calculation of postBreak. This change ensures that breaks are only added if the difference between postBreak and currentLineWidth is significant. Fixes https://github.com/flutter/flutter/issues/65419 --- third_party/txt/src/minikin/LineBreaker.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/third_party/txt/src/minikin/LineBreaker.cpp b/third_party/txt/src/minikin/LineBreaker.cpp index 094e405bd7386..c0da19137ff8e 100644 --- a/third_party/txt/src/minikin/LineBreaker.cpp +++ b/third_party/txt/src/minikin/LineBreaker.cpp @@ -62,6 +62,12 @@ const size_t MAX_TEXT_BUF_RETAIN = 32678; // Maximum amount that spaces can shrink, in justified text. const float SHRINKABILITY = 1.0 / 3.0; +// libtxt: Add a fudge factor to comparisons between currentLineWidth and +// postBreak width. The currentLineWidth passed by the Flutter framework +// is based on maxIntrinsicWidth/Layout::measureText calculations that may +// not precisely match the postBreak width. +const float LIBTXT_WIDTH_ADJUST = 0.00001; + void LineBreaker::setLocale(const icu::Locale& locale, Hyphenator* hyphenator) { mWordBreaker.setLocale(locale); mLocale = locale; @@ -240,7 +246,7 @@ void LineBreaker::addWordBreak(size_t offset, // libtxt: add a fudge factor to this comparison. The currentLineWidth passed // by the framework is based on maxIntrinsicWidth/Layout::measureText // calculations that may not precisely match the postBreak width. - if (postBreak - width > currentLineWidth() + 0.00001) { + if (postBreak - width > currentLineWidth() + LIBTXT_WIDTH_ADJUST) { // Add desperate breaks. // Note: these breaks are based on the shaping of the (non-broken) original // text; they are imprecise especially in the presence of kerning, @@ -305,7 +311,7 @@ void LineBreaker::addCandidate(Candidate cand) { // mCandidates, and mPreBreak is its preBreak value. mBestBreak is the index // of the best line breaking candidate we have found since then, and // mBestScore is its penalty. - if (cand.postBreak - mPreBreak > currentLineWidth()) { + if (cand.postBreak - mPreBreak > currentLineWidth() + LIBTXT_WIDTH_ADJUST) { // This break would create an overfull line, pick the best break and break // there (greedy) if (mBestBreak == mLastBreak) { @@ -316,7 +322,8 @@ void LineBreaker::addCandidate(Candidate cand) { } while (mLastBreak != candIndex && - cand.postBreak - mPreBreak > currentLineWidth()) { + cand.postBreak - mPreBreak > + currentLineWidth() + LIBTXT_WIDTH_ADJUST) { // We should rarely come here. But if we are here, we have broken the line, // but the remaining part still doesn't fit. We now need to break at the // second best place after the last break, but we have not kept that From 2eac514f26a676d1df0821e815d501149d3ead7f Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Wed, 7 Oct 2020 16:32:01 -0700 Subject: [PATCH 002/219] Run desktop darwin tests in debug mode (#21660) --- shell/platform/darwin/macos/BUILD.gn | 2 ++ testing/run_tests.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 58a9e66bcb180..9800139a8a1cf 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -109,6 +109,8 @@ executable("flutter_desktop_darwin_unittests") { cflags_objcc = [ "-fobjc-arc" ] + ldflags = [ "-ObjC" ] + deps = [ ":flutter_desktop_darwin_fixtures", ":flutter_framework_source", diff --git a/testing/run_tests.py b/testing/run_tests.py index a11f91c47b1e8..6b2cf2f18cea3 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -148,6 +148,11 @@ def RunCCTests(build_dir, filter): if IsMac(): RunEngineExecutable(build_dir, 'flutter_channels_unittests', filter, shuffle_flags) + # These tests can only be run on Darwin on debug mode. + # See: https://github.com/flutter/flutter/issues/66664 + if IsMac() and ('debug' in build_dir): + RunEngineExecutable(build_dir, 'flutter_desktop_darwin_unittests', filter, shuffle_flags) + # https://github.com/flutter/flutter/issues/36296 if IsLinux(): RunEngineExecutable(build_dir, 'txt_unittests', filter, shuffle_flags) From 15739917f3416d5a34c16c90be70401c16cee318 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Wed, 7 Oct 2020 16:34:49 -0700 Subject: [PATCH 003/219] [macOS] Allow loading of AOT snapshots and instructions from elf bundle (#21670) --- .../macos/framework/Source/FlutterEngine.mm | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 359f13bd834b6..663ccd21f2fb1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -7,6 +7,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #include +#include #include #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" @@ -162,6 +163,16 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, #pragma mark - +namespace { + +struct AotDataDeleter { + void operator()(FlutterEngineAOTData aot_data) { FlutterEngineCollectAOTData(aot_data); } +}; + +using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>; + +} + @implementation FlutterEngine { // The embedding-API-level engine object. FLUTTER_API_SYMBOL(FlutterEngine) _engine; @@ -184,6 +195,9 @@ @implementation FlutterEngine { // A mapping of textureID to internal FlutterExternalTextureGL adapter. NSMutableDictionary* _textures; + + // Pointer to the Dart AOT snapshot and instruction data. + UniqueAotDataPtr _aotData; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -274,6 +288,11 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { }; flutterArguments.custom_task_runners = &custom_task_runners; + _aotData = [self loadAotData:flutterArguments.assets_path]; + if (_aotData) { + flutterArguments.aot_data = _aotData.get(); + } + FlutterEngineResult result = FlutterEngineInitialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); if (result != kSuccess) { @@ -293,6 +312,33 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { return YES; } +- (UniqueAotDataPtr)loadAotData:(std::string)assetsDir { + if (!FlutterEngineRunsAOTCompiledDartCode()) { + return nullptr; + } + + std::filesystem::path assetsFsDir(assetsDir); + std::filesystem::path elfFile("app_elf_snapshot.so"); + auto fullElfPath = assetsFsDir / elfFile; + + if (!std::filesystem::exists(fullElfPath)) { + return nullptr; + } + + FlutterEngineAOTDataSource source = {}; + source.type = kFlutterEngineAOTDataSourceTypeElfPath; + source.elf_path = fullElfPath.c_str(); + + FlutterEngineAOTData data = nullptr; + auto result = FlutterEngineCreateAOTData(&source, &data); + if (result != kSuccess) { + NSLog(@"Failed to load AOT data from: %@", @(fullElfPath.c_str())); + return nullptr; + } + + return UniqueAotDataPtr(data); +} + - (void)setViewController:(FlutterViewController*)controller { _viewController = controller; _mainOpenGLContext = controller.flutterView.openGLContext; From aab33c155aecbbcf6964e82a92a7acc3ee160ee3 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 7 Oct 2020 16:37:02 -0700 Subject: [PATCH 004/219] Ensure JNI is not called from raster thread (#21665) --- .../external_view_embedder.cc | 8 +- .../external_view_embedder_unittests.cc | 85 +++++++++++++++++++ .../external_view_embedder/surface_pool.cc | 10 +++ .../external_view_embedder/surface_pool.h | 13 +++ .../surface_pool_unittests.cc | 41 +++++++++ 5 files changed, 154 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index 5ccd649abab69..a45a8ae897ed1 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc @@ -265,15 +265,17 @@ 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) { + if (frame_size_ != frame_size && raster_thread_merger->IsOnPlatformThread()) { surface_pool_->DestroyLayers(jni_facade_); } - frame_size_ = frame_size; - device_pixel_ratio_ = device_pixel_ratio; + surface_pool_->SetFrameSize(frame_size); // JNI method must be called on the platform thread. if (raster_thread_merger->IsOnPlatformThread()) { jni_facade_->FlutterViewBeginFrame(); } + + frame_size_ = frame_size; + device_pixel_ratio_ = device_pixel_ratio; } // |ExternalViewEmbedder| diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index 5e8e5b6bcb01a..f69645f33dcf2 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -557,6 +557,91 @@ TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) { raster_thread_merger); } +TEST(AndroidExternalViewEmbedder, DoesNotDestroyOverlayLayersOnSizeChange) { + auto jni_mock = std::make_shared(); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto window = fml::MakeRefCounted(nullptr); + auto gr_context = GrDirectContext::MakeMock(nullptr); + auto frame_size = SkISize::Make(1000, 1000); + auto surface_factory = + [gr_context, window, frame_size]( + std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto surface_frame_1 = std::make_unique( + SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); + + auto surface_mock = std::make_unique(); + EXPECT_CALL(*surface_mock, AcquireFrame(frame_size)) + .WillOnce(Return(ByMove(std::move(surface_frame_1)))); + + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())) + .WillOnce(Return(ByMove(std::move(surface_mock)))); + + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + + return android_surface_mock; + }; + + auto embedder = std::make_unique( + android_context, jni_mock, surface_factory); + + // ------------------ First frame ------------------ // + { + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger); + + // Add an Android view. + MutatorsStack stack1; + // TODO(egarciad): Investigate why Flow applies the device pixel ratio to + // the offsetPixels, but not the sizePoints. + auto view_params_1 = std::make_unique( + SkMatrix(), SkSize::Make(200, 200), stack1); + + embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); + + // This simulates Flutter UI that intersects with the Android view. + embedder->CompositeEmbeddedView(0)->drawRect( + SkRect::MakeXYWH(50, 50, 200, 200), SkPaint()); + + // Create a new overlay surface. + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + // The JNI call to display the Android view. + EXPECT_CALL(*jni_mock, FlutterViewOnDisplayPlatformView(0, 0, 0, 200, 200, + 300, 300, stack1)); + EXPECT_CALL(*jni_mock, + FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200)); + + auto surface_frame = + std::make_unique(SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, + SkCanvas* canvas) { return true; }); + embedder->SubmitFrame(gr_context.get(), std::move(surface_frame)); + + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()); + embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger); + } + + // Changing the frame size from the raster thread does not make JNI calls. + + EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()).Times(0); + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()).Times(0); + + embedder->BeginFrame(SkISize::Make(30, 40), nullptr, 1.0, + GetThreadMergerFromRasterThread()); +} + TEST(AndroidExternalViewEmbedder, SupportsDynamicThreadMerging) { auto jni_mock = std::make_shared(); diff --git a/shell/platform/android/external_view_embedder/surface_pool.cc b/shell/platform/android/external_view_embedder/surface_pool.cc index bee34109b95b1..eccd2d671da5c 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.cc +++ b/shell/platform/android/external_view_embedder/surface_pool.cc @@ -24,6 +24,11 @@ std::shared_ptr SurfacePool::GetLayer( std::shared_ptr android_context, std::shared_ptr jni_facade, const AndroidSurface::Factory& surface_factory) { + // Destroy current layers in the pool if the frame size has changed. + if (requested_frame_size_ != current_frame_size_) { + DestroyLayers(jni_facade); + } + intptr_t gr_context_key = reinterpret_cast(gr_context); // Allocate a new surface if there isn't one available. if (available_layer_index_ >= layers_.size()) { @@ -63,6 +68,7 @@ std::shared_ptr SurfacePool::GetLayer( layer->surface = std::move(surface); } available_layer_index_++; + current_frame_size_ = requested_frame_size_; return layer; } @@ -87,4 +93,8 @@ std::vector> SurfacePool::GetUnusedLayers() { return results; } +void SurfacePool::SetFrameSize(SkISize frame_size) { + requested_frame_size_ = frame_size; +} + } // namespace flutter diff --git a/shell/platform/android/external_view_embedder/surface_pool.h b/shell/platform/android/external_view_embedder/surface_pool.h index 190119c75cf25..44236747f9331 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.h +++ b/shell/platform/android/external_view_embedder/surface_pool.h @@ -67,6 +67,11 @@ class SurfacePool { // Destroys all the layers in the pool. void DestroyLayers(std::shared_ptr jni_facade); + // Sets the frame size used by the layers in the pool. + // If the current layers in the pool have a different frame size, + // then they are deallocated as soon as |GetLayer| is called. + void SetFrameSize(SkISize frame_size); + private: // The index of the entry in the layers_ vector that determines the beginning // of the unused layers. For example, consider the following vector: @@ -81,7 +86,15 @@ class SurfacePool { // This indicates that entries starting from 1 can be reused meanwhile the // entry at position 0 cannot be reused. size_t available_layer_index_ = 0; + + // The layers in the pool. std::vector> layers_; + + // The frame size of the layers in the pool. + SkISize current_frame_size_; + + // The frame size to be used by future layers. + SkISize requested_frame_size_; }; } // namespace flutter diff --git a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc index 18ebb9a285fcf..6940c8c75e048 100644 --- a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc +++ b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc @@ -202,5 +202,46 @@ TEST(SurfacePool, DestroyLayers) { ASSERT_TRUE(pool->GetUnusedLayers().empty()); } +TEST(SurfacePool, DestroyLayers__frameSizeChanged) { + auto pool = std::make_unique(); + auto jni_mock = std::make_shared(); + + auto gr_context = GrDirectContext::MakeMock(nullptr); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto window = fml::MakeRefCounted(nullptr); + + auto surface_factory = + [gr_context, window](std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + return android_surface_mock; + }; + pool->SetFrameSize(SkISize::Make(10, 10)); + EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()).Times(0); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .Times(1) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + + pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); + + pool->SetFrameSize(SkISize::Make(20, 20)); + EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()).Times(1); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .Times(1) + .WillOnce(Return( + ByMove(std::make_unique( + 1, window)))); + pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); + + ASSERT_TRUE(pool->GetUnusedLayers().empty()); +} + } // namespace testing } // namespace flutter From 2e085660a15114a97b676bc3c18ca360893168c5 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 7 Oct 2020 19:42:02 -0400 Subject: [PATCH 005/219] Roll Skia from a7f69c290667 to 041fd0ad7d93 (5 revisions) (#21676) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 3e13410a8b7ec..a4baefbdc7ce6 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'a7f69c290667bb969788e014e3308cef6e187740', + 'skia_revision': '041fd0ad7d93f1bc87a2c5b7d2c4f2217a802341', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 07737c30603da..6d2888e951614 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: d3107608da67165804061970476ba2d7 +Signature: 2716e2054e1a67a28f842cd740815028 UNUSED LICENSES: @@ -5591,6 +5591,7 @@ FILE: ../../../third_party/skia/src/gpu/tessellate/GrStrokeTessellateShader.cpp FILE: ../../../third_party/skia/src/gpu/tessellate/GrStrokeTessellateShader.h FILE: ../../../third_party/skia/src/opts/SkVM_opts.h FILE: ../../../third_party/skia/src/sksl/SkSLAnalysis.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLModifiersPool.h FILE: ../../../third_party/skia/src/sksl/SkSLRehydrator.cpp FILE: ../../../third_party/skia/src/sksl/SkSLRehydrator.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLConstructor.cpp From 02324994a3f44a2777ade96b3d69aa61901fb9b4 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 7 Oct 2020 17:19:07 -0700 Subject: [PATCH 006/219] [web] Support custom url strategies (#19134) --- ci/licenses_golden/licenses_flutter | 5 +- lib/web_ui/lib/src/engine.dart | 5 +- .../lib/src/engine/browser_location.dart | 211 ------------- .../src/engine/{ => navigation}/history.dart | 213 +++++++------ .../engine/navigation/js_url_strategy.dart | 78 +++++ .../src/engine/navigation/url_strategy.dart | 296 ++++++++++++++++++ lib/web_ui/lib/src/engine/test_embedding.dart | 26 +- lib/web_ui/lib/src/engine/window.dart | 202 ++++++------ lib/web_ui/lib/src/ui/initialization.dart | 4 - lib/web_ui/test/engine/history_test.dart | 127 +++++--- lib/web_ui/test/engine/navigation_test.dart | 14 +- lib/web_ui/test/window_test.dart | 92 +++--- 12 files changed, 740 insertions(+), 533 deletions(-) delete mode 100644 lib/web_ui/lib/src/engine/browser_location.dart rename lib/web_ui/lib/src/engine/{ => navigation}/history.dart (69%) create mode 100644 lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart create mode 100644 lib/web_ui/lib/src/engine/navigation/url_strategy.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ed74366279e94..72bfafac4c963 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -426,7 +426,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvas_pool.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -463,7 +462,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/history.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/js_url_strategy.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/url_strategy.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/clip.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index d8c01373840f8..dcb19c8a3165f 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -26,7 +26,6 @@ part 'engine/alarm_clock.dart'; part 'engine/assets.dart'; part 'engine/bitmap_canvas.dart'; part 'engine/browser_detection.dart'; -part 'engine/browser_location.dart'; part 'engine/canvaskit/canvas.dart'; part 'engine/canvaskit/canvaskit_canvas.dart'; part 'engine/canvaskit/canvaskit_api.dart'; @@ -63,7 +62,9 @@ part 'engine/dom_canvas.dart'; part 'engine/dom_renderer.dart'; part 'engine/engine_canvas.dart'; part 'engine/frame_reference.dart'; -part 'engine/history.dart'; +part 'engine/navigation/history.dart'; +part 'engine/navigation/js_url_strategy.dart'; +part 'engine/navigation/url_strategy.dart'; part 'engine/html/backdrop_filter.dart'; part 'engine/html/canvas.dart'; part 'engine/html/clip.dart'; diff --git a/lib/web_ui/lib/src/engine/browser_location.dart b/lib/web_ui/lib/src/engine/browser_location.dart deleted file mode 100644 index a9701cd99060f..0000000000000 --- a/lib/web_ui/lib/src/engine/browser_location.dart +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of engine; - -// TODO(mdebbar): add other strategies. - -// Some parts of this file were inspired/copied from the AngularDart router. - -/// [LocationStrategy] is responsible for representing and reading route state -/// from the browser's URL. -/// -/// At the moment, only one strategy is implemented: [HashLocationStrategy]. -/// -/// This is used by [BrowserHistory] to interact with browser history APIs. -abstract class LocationStrategy { - const LocationStrategy(); - - /// Subscribes to popstate events and returns a function that could be used to - /// unsubscribe from popstate events. - ui.VoidCallback onPopState(html.EventListener fn); - - /// The active path in the browser history. - String get path; - - /// The state of the current browser history entry. - dynamic get state; - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - void pushState(dynamic state, String title, String url); - - /// Replace the currently active history entry. - void replaceState(dynamic state, String title, String url); - - /// Go to the previous history entry. - Future back({int count = 1}); -} - -/// This is an implementation of [LocationStrategy] that uses the browser URL's -/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) -/// to represent its state. -/// -/// In order to use this [LocationStrategy] for an app, it needs to be set in -/// [ui.window.locationStrategy]: -/// -/// ```dart -/// import 'package:flutter_web/material.dart'; -/// import 'package:flutter_web/ui.dart' as ui; -/// -/// void main() { -/// ui.window.locationStrategy = const ui.HashLocationStrategy(); -/// runApp(MyApp()); -/// } -/// ``` -class HashLocationStrategy extends LocationStrategy { - final PlatformLocation _platformLocation; - - const HashLocationStrategy( - [this._platformLocation = const BrowserPlatformLocation()]); - - @override - ui.VoidCallback onPopState(html.EventListener fn) { - _platformLocation.onPopState(fn); - return () => _platformLocation.offPopState(fn); - } - - @override - String get path { - // the hash value is always prefixed with a `#` - // and if it is empty then it will stay empty - String path = _platformLocation.hash ?? ''; - assert(path.isEmpty || path.startsWith('#')); - - // We don't want to return an empty string as a path. Instead we default to "/". - if (path.isEmpty || path == '#') { - return '/'; - } - // At this point, we know [path] starts with "#" and isn't empty. - return path.substring(1); - } - - @override - dynamic get state => _platformLocation.state; - - @override - String prepareExternalUrl(String internalUrl) { - // It's convention that if the hash path is empty, we omit the `#`; however, - // if the empty URL is pushed it won't replace any existing fragment. So - // when the hash path is empty, we instead return the location's path and - // query. - return internalUrl.isEmpty - ? '${_platformLocation.pathname}${_platformLocation.search}' - : '#$internalUrl'; - } - - @override - void pushState(dynamic state, String title, String url) { - _platformLocation.pushState(state, title, prepareExternalUrl(url)); - } - - @override - void replaceState(dynamic state, String title, String url) { - _platformLocation.replaceState(state, title, prepareExternalUrl(url)); - } - - @override - Future back({int count = 1}) { - _platformLocation.back(count); - return _waitForPopState(); - } - - /// Waits until the next popstate event is fired. - /// - /// This is useful for example to wait until the browser has handled the - /// `history.back` transition. - Future _waitForPopState() { - final Completer completer = Completer(); - late ui.VoidCallback unsubscribe; - unsubscribe = onPopState((_) { - unsubscribe(); - completer.complete(); - }); - return completer.future; - } -} - -/// [PlatformLocation] encapsulates all calls to DOM apis, which allows the -/// [LocationStrategy] classes to be platform agnostic and testable. -/// -/// The [PlatformLocation] class is used directly by all implementations of -/// [LocationStrategy] when they need to interact with the DOM apis like -/// pushState, popState, etc... -abstract class PlatformLocation { - const PlatformLocation(); - - void onPopState(html.EventListener fn); - void offPopState(html.EventListener fn); - - void onHashChange(html.EventListener fn); - void offHashChange(html.EventListener fn); - - String get pathname; - String get search; - String? get hash; - dynamic get state; - - void pushState(dynamic state, String title, String url); - void replaceState(dynamic state, String title, String url); - void back(int count); -} - -/// An implementation of [PlatformLocation] for the browser. -class BrowserPlatformLocation extends PlatformLocation { - html.Location get _location => html.window.location; - html.History get _history => html.window.history; - - const BrowserPlatformLocation(); - - @override - void onPopState(html.EventListener fn) { - html.window.addEventListener('popstate', fn); - } - - @override - void offPopState(html.EventListener fn) { - html.window.removeEventListener('popstate', fn); - } - - @override - void onHashChange(html.EventListener fn) { - html.window.addEventListener('hashchange', fn); - } - - @override - void offHashChange(html.EventListener fn) { - html.window.removeEventListener('hashchange', fn); - } - - @override - String get pathname => _location.pathname!; - - @override - String get search => _location.search!; - - @override - String get hash => _location.hash; - - @override - dynamic get state => _history.state; - - @override - void pushState(dynamic state, String title, String url) { - _history.pushState(state, title, url); - } - - @override - void replaceState(dynamic state, String title, String url) { - _history.replaceState(state, title, url); - } - - @override - void back(int count) { - _history.go(-count); - } -} diff --git a/lib/web_ui/lib/src/engine/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart similarity index 69% rename from lib/web_ui/lib/src/engine/history.dart rename to lib/web_ui/lib/src/engine/navigation/history.dart index 59e1ba5fddf6d..0a578162a9096 100644 --- a/lib/web_ui/lib/src/engine/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -25,64 +25,39 @@ abstract class BrowserHistory { late ui.VoidCallback _unsubscribe; /// The strategy to interact with html browser history. - LocationStrategy? get locationStrategy => _locationStrategy; - LocationStrategy? _locationStrategy; - /// Updates the strategy. - /// - /// This method will also remove any previous modifications to the html - /// browser history and start anew. - Future setLocationStrategy(LocationStrategy? strategy) async { - if (strategy != _locationStrategy) { - await _tearoffStrategy(_locationStrategy); - _locationStrategy = strategy; - await _setupStrategy(_locationStrategy); - } - } + UrlStrategy? get urlStrategy; - Future _setupStrategy(LocationStrategy? strategy) async { - if (strategy == null) { - return; - } - _unsubscribe = strategy.onPopState(onPopState as dynamic Function(html.Event)); - await setup(); - } + bool _isDisposed = false; - Future _tearoffStrategy(LocationStrategy? strategy) async { - if (strategy == null) { - return; - } - _unsubscribe(); - - await tearDown(); + void _setupStrategy(UrlStrategy strategy) { + _unsubscribe = strategy.addPopStateListener( + onPopState as html.EventListener, + ); } /// Exit this application and return to the previous page. Future exit() async { - if (_locationStrategy != null) { - await _tearoffStrategy(_locationStrategy); + if (urlStrategy != null) { + await tearDown(); // Now the history should be in the original state, back one more time to // exit the application. - await _locationStrategy!.back(); - _locationStrategy = null; + await urlStrategy!.go(-1); } } /// This method does the same thing as the browser back button. - Future back() { - if (locationStrategy != null) { - return locationStrategy!.back(); - } - return Future.value(); + Future back() async { + return urlStrategy?.go(-1); } /// The path of the current location of the user's browser. - String get currentPath => locationStrategy?.path ?? '/'; + String get currentPath => urlStrategy?.getPath() ?? '/'; /// The state of the current location of the user's browser. - dynamic get currentState => locationStrategy?.state; + Object? get currentState => urlStrategy?.getState(); /// Update the url with the given [routeName] and [state]. - void setRouteName(String? routeName, {dynamic? state}); + void setRouteName(String? routeName, {Object? state}); /// A callback method to handle browser backward or forward buttons. /// @@ -90,12 +65,9 @@ abstract class BrowserHistory { /// applications accordingly. void onPopState(covariant html.PopStateEvent event); - /// Sets up any prerequisites to use this browser history class. - Future setup() => Future.value(); - /// Restore any modifications to the html browser history during the lifetime /// of this class. - Future tearDown() => Future.value(); + Future tearDown(); } /// A browser history class that creates a set of browser history entries to @@ -113,31 +85,51 @@ abstract class BrowserHistory { /// * [SingleEntryBrowserHistory], which is used when the framework does not use /// a Router for routing. class MultiEntriesBrowserHistory extends BrowserHistory { + MultiEntriesBrowserHistory({required this.urlStrategy}) { + final UrlStrategy? strategy = urlStrategy; + if (strategy == null) { + return; + } + + _setupStrategy(strategy); + if (!_hasSerialCount(currentState)) { + strategy.replaceState( + _tagWithSerialCount(currentState, 0), 'flutter', currentPath); + } + // If we restore from a page refresh, the _currentSerialCount may not be 0. + _lastSeenSerialCount = _currentSerialCount; + } + + @override + final UrlStrategy? urlStrategy; + late int _lastSeenSerialCount; int get _currentSerialCount { if (_hasSerialCount(currentState)) { - return currentState['serialCount'] as int; + final Map stateMap = + currentState as Map; + return stateMap['serialCount'] as int; } return 0; } - dynamic _tagWithSerialCount(dynamic originialState, int count) { - return { + Object _tagWithSerialCount(Object? originialState, int count) { + return { 'serialCount': count, 'state': originialState, }; } - bool _hasSerialCount(dynamic state) { + bool _hasSerialCount(Object? state) { return state is Map && state['serialCount'] != null; } @override - void setRouteName(String? routeName, {dynamic? state}) { - if (locationStrategy != null) { + void setRouteName(String? routeName, {Object? state}) { + if (urlStrategy != null) { assert(routeName != null); _lastSeenSerialCount += 1; - locationStrategy!.pushState( + urlStrategy!.pushState( _tagWithSerialCount(state, _lastSeenSerialCount), 'flutter', routeName!, @@ -147,58 +139,51 @@ class MultiEntriesBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { - assert(locationStrategy != null); + assert(urlStrategy != null); // May be a result of direct url access while the flutter application is // already running. if (!_hasSerialCount(event.state)) { // In this case we assume this will be the next history entry from the // last seen entry. - locationStrategy!.replaceState( - _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), - 'flutter', - currentPath); + urlStrategy!.replaceState( + _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), + 'flutter', + currentPath); } _lastSeenSerialCount = _currentSerialCount; if (window._onPlatformMessage != null) { window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( - MethodCall('pushRouteInformation', { - 'location': currentPath, - 'state': event.state?['state'], - }) - ), + MethodCall('pushRouteInformation', { + 'location': currentPath, + 'state': event.state?['state'], + })), (_) {}, ); } } @override - Future setup() { - if (!_hasSerialCount(currentState)) { - locationStrategy!.replaceState( - _tagWithSerialCount(currentState, 0), - 'flutter', - currentPath - ); + Future tearDown() async { + if (_isDisposed || urlStrategy == null) { + return; } - // If we retore from a page refresh, the _currentSerialCount may not be 0. - _lastSeenSerialCount = _currentSerialCount; - return Future.value(); - } + _isDisposed = true; + _unsubscribe(); - @override - Future tearDown() async { // Restores the html browser history. assert(_hasSerialCount(currentState)); int backCount = _currentSerialCount; if (backCount > 0) { - await locationStrategy!.back(count: backCount); + await urlStrategy!.go(-backCount); } // Unwrap state. assert(_hasSerialCount(currentState) && _currentSerialCount == 0); - locationStrategy!.replaceState( - currentState['state'], + final Map stateMap = + currentState as Map; + urlStrategy!.replaceState( + stateMap['state'], 'flutter', currentPath, ); @@ -222,37 +207,61 @@ class MultiEntriesBrowserHistory extends BrowserHistory { /// * [MultiEntriesBrowserHistory], which is used when the framework uses a /// Router for routing. class SingleEntryBrowserHistory extends BrowserHistory { + SingleEntryBrowserHistory({required this.urlStrategy}) { + final UrlStrategy? strategy = urlStrategy; + if (strategy == null) { + return; + } + + _setupStrategy(strategy); + + final String path = currentPath; + if (!_isFlutterEntry(html.window.history.state)) { + // An entry may not have come from Flutter, for example, when the user + // refreshes the page. They land directly on the "flutter" entry, so + // there's no need to setup the "origin" and "flutter" entries, we can + // safely assume they are already setup. + _setupOriginEntry(strategy); + _setupFlutterEntry(strategy, replace: false, path: path); + } + } + + @override + final UrlStrategy? urlStrategy; + static const MethodCall _popRouteMethodCall = MethodCall('popRoute'); static const String _kFlutterTag = 'flutter'; static const String _kOriginTag = 'origin'; - Map _wrapOriginState(dynamic state) { + Map _wrapOriginState(Object? state) { return {_kOriginTag: true, 'state': state}; } - dynamic _unwrapOriginState(dynamic state) { + + Object? _unwrapOriginState(Object? state) { assert(_isOriginEntry(state)); final Map originState = state as Map; return originState['state']; } + Map _flutterState = {_kFlutterTag: true}; /// The origin entry is the history entry that the Flutter app landed on. It's /// created by the browser when the user navigates to the url of the app. - bool _isOriginEntry(dynamic state) { + bool _isOriginEntry(Object? state) { return state is Map && state[_kOriginTag] == true; } /// The flutter entry is a history entry that we maintain on top of the origin /// entry. It allows us to catch popstate events when the user hits the back /// button. - bool _isFlutterEntry(dynamic state) { + bool _isFlutterEntry(Object? state) { return state is Map && state[_kFlutterTag] == true; } @override - void setRouteName(String? routeName, {dynamic? state}) { - if (locationStrategy != null) { - _setupFlutterEntry(locationStrategy!, replace: true, path: routeName); + void setRouteName(String? routeName, {Object? state}) { + if (urlStrategy != null) { + _setupFlutterEntry(urlStrategy!, replace: true, path: routeName); } } @@ -260,7 +269,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { if (_isOriginEntry(event.state)) { - _setupFlutterEntry(_locationStrategy!); + _setupFlutterEntry(urlStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. if (window._onPlatformMessage != null) { @@ -302,14 +311,14 @@ class SingleEntryBrowserHistory extends BrowserHistory { // 2. Then we remove the new entry. // This will take us back to our "flutter" entry and it causes a new // popstate event that will be handled in the "else if" section above. - _locationStrategy!.back(); + urlStrategy!.go(-1); } } /// This method should be called when the Origin Entry is active. It just /// replaces the state of the entry so that we can recognize it later using /// [_isOriginEntry] inside [_popStateListener]. - void _setupOriginEntry(LocationStrategy strategy) { + void _setupOriginEntry(UrlStrategy strategy) { assert(strategy != null); // ignore: unnecessary_null_comparison strategy.replaceState(_wrapOriginState(currentState), 'origin', ''); } @@ -317,7 +326,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { /// This method is used manipulate the Flutter Entry which is always the /// active entry while the Flutter app is running. void _setupFlutterEntry( - LocationStrategy strategy, { + UrlStrategy strategy, { bool replace = false, String? path, }) { @@ -330,28 +339,18 @@ class SingleEntryBrowserHistory extends BrowserHistory { } } - @override - Future setup() { - final String path = currentPath; - if (_isFlutterEntry(html.window.history.state)) { - // This could happen if the user, for example, refreshes the page. They - // will land directly on the "flutter" entry, so there's no need to setup - // the "origin" and "flutter" entries, we can safely assume they are - // already setup. - } else { - _setupOriginEntry(locationStrategy!); - _setupFlutterEntry(locationStrategy!, replace: false, path: path); - } - return Future.value(); - } - @override Future tearDown() async { - if (locationStrategy != null) { - // We need to remove the flutter entry that we pushed in setup. - await locationStrategy!.back(); - // Restores original state. - locationStrategy!.replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); + if (_isDisposed || urlStrategy == null) { + return; } + _isDisposed = true; + _unsubscribe(); + + // We need to remove the flutter entry that we pushed in setup. + await urlStrategy!.go(-1); + // Restores original state. + urlStrategy! + .replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); } } diff --git a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart new file mode 100644 index 0000000000000..decb7c249d44d --- /dev/null +++ b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +typedef _PathGetter = String Function(); + +typedef _StateGetter = Object? Function(); + +typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener); + +typedef _StringToString = String Function(String); + +typedef _StateOperation = void Function( + Object? state, String title, String url); + +typedef _HistoryMove = Future Function(int count); + +/// The JavaScript representation of a URL strategy. +/// +/// This is used to pass URL strategy implementations across a JS-interop +/// bridge from the app to the engine. +@JS() +@anonymous +abstract class JsUrlStrategy { + /// Creates an instance of [JsUrlStrategy] from a bag of URL strategy + /// functions. + external factory JsUrlStrategy({ + required _PathGetter getPath, + required _StateGetter getState, + required _AddPopStateListener addPopStateListener, + required _StringToString prepareExternalUrl, + required _StateOperation pushState, + required _StateOperation replaceState, + required _HistoryMove go, + }); + + /// Adds a listener to the `popstate` event and returns a function that, when + /// invoked, removes the listener. + external ui.VoidCallback addPopStateListener(html.EventListener fn); + + /// Returns the active path in the browser. + external String getPath(); + + /// Returns the history state in the browser. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + external Object? getState(); + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + external String prepareExternalUrl(String internalUrl); + + /// Push a new history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + external void pushState(Object? state, String title, String url); + + /// Replace the currently active history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + external void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + external Future go(int count); +} diff --git a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart new file mode 100644 index 0000000000000..fcf2cecfd0b8e --- /dev/null +++ b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart @@ -0,0 +1,296 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +/// Represents and reads route state from the browser's URL. +/// +/// By default, the [HashUrlStrategy] subclass is used if the app doesn't +/// specify one. +abstract class UrlStrategy { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const UrlStrategy(); + + /// Adds a listener to the `popstate` event and returns a function that, when + /// invoked, removes the listener. + ui.VoidCallback addPopStateListener(html.EventListener fn); + + /// Returns the active path in the browser. + String getPath(); + + /// The state of the current browser history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + Object? getState(); + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + String prepareExternalUrl(String internalUrl); + + /// Push a new history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + void pushState(Object? state, String title, String url); + + /// Replace the currently active history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + Future go(int count); +} + +/// This is an implementation of [UrlStrategy] that uses the browser URL's +/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) +/// to represent its state. +/// +/// In order to use this [UrlStrategy] for an app, it needs to be set like this: +/// +/// ```dart +/// import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +/// +/// // Somewhere before calling `runApp()` do: +/// setUrlStrategy(const HashUrlStrategy()); +/// ``` +class HashUrlStrategy extends UrlStrategy { + /// Creates an instance of [HashUrlStrategy]. + /// + /// The [PlatformLocation] parameter is useful for testing to mock out browser + /// interations. + const HashUrlStrategy( + [this._platformLocation = const BrowserPlatformLocation()]); + + final PlatformLocation _platformLocation; + + @override + ui.VoidCallback addPopStateListener(html.EventListener fn) { + _platformLocation.addPopStateListener(fn); + return () => _platformLocation.removePopStateListener(fn); + } + + @override + String getPath() { + // the hash value is always prefixed with a `#` + // and if it is empty then it will stay empty + final String path = _platformLocation.hash ?? ''; + assert(path.isEmpty || path.startsWith('#')); + + // We don't want to return an empty string as a path. Instead we default to "/". + if (path.isEmpty || path == '#') { + return '/'; + } + // At this point, we know [path] starts with "#" and isn't empty. + return path.substring(1); + } + + @override + Object? getState() => _platformLocation.state; + + @override + String prepareExternalUrl(String internalUrl) { + // It's convention that if the hash path is empty, we omit the `#`; however, + // if the empty URL is pushed it won't replace any existing fragment. So + // when the hash path is empty, we instead return the location's path and + // query. + return internalUrl.isEmpty + ? '${_platformLocation.pathname}${_platformLocation.search}' + : '#$internalUrl'; + } + + @override + void pushState(Object? state, String title, String url) { + _platformLocation.pushState(state, title, prepareExternalUrl(url)); + } + + @override + void replaceState(Object? state, String title, String url) { + _platformLocation.replaceState(state, title, prepareExternalUrl(url)); + } + + @override + Future go(int count) { + _platformLocation.go(count); + return _waitForPopState(); + } + + /// Waits until the next popstate event is fired. + /// + /// This is useful, for example, to wait until the browser has handled the + /// `history.back` transition. + Future _waitForPopState() { + final Completer completer = Completer(); + late ui.VoidCallback unsubscribe; + unsubscribe = addPopStateListener((_) { + unsubscribe(); + completer.complete(); + }); + return completer.future; + } +} + +/// Wraps a custom implementation of [UrlStrategy] that was previously converted +/// to a [JsUrlStrategy]. +class CustomUrlStrategy extends UrlStrategy { + /// Wraps the [delegate] in a [CustomUrlStrategy] instance. + CustomUrlStrategy.fromJs(this.delegate); + + final JsUrlStrategy delegate; + + @override + ui.VoidCallback addPopStateListener(html.EventListener fn) => + delegate.addPopStateListener(fn); + + @override + String getPath() => delegate.getPath(); + + @override + Object? getState() => delegate.getState(); + + @override + String prepareExternalUrl(String internalUrl) => + delegate.prepareExternalUrl(internalUrl); + + @override + void pushState(Object? state, String title, String url) => + delegate.pushState(state, title, url); + + @override + void replaceState(Object? state, String title, String url) => + delegate.replaceState(state, title, url); + + @override + Future go(int count) => delegate.go(count); +} + +/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes +/// to be platform agnostic and testable. +/// +/// For convenience, the [PlatformLocation] class can be used by implementations +/// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc. +abstract class PlatformLocation { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const PlatformLocation(); + + /// Registers an event listener for the `popstate` event. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate + void addPopStateListener(html.EventListener fn); + + /// Unregisters the given listener (added by [addPopStateListener]) from the + /// `popstate` event. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate + void removePopStateListener(html.EventListener fn); + + /// The `pathname` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname + String get pathname; + + /// The `query` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/search + String get search; + + /// The `hash]` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/hash + String? get hash; + + /// The `state` in the current history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + Object? get state; + + /// Adds a new entry to the browser history stack. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + void pushState(Object? state, String title, String url); + + /// Replaces the current entry in the browser history stack. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + void go(int count); + + /// The base href where the Flutter app is being served. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + String? getBaseHref(); +} + +/// Delegates to real browser APIs to provide platform location functionality. +class BrowserPlatformLocation extends PlatformLocation { + /// Default constructor for [BrowserPlatformLocation]. + const BrowserPlatformLocation(); + + html.Location get _location => html.window.location; + html.History get _history => html.window.history; + + @override + void addPopStateListener(html.EventListener fn) { + html.window.addEventListener('popstate', fn); + } + + @override + void removePopStateListener(html.EventListener fn) { + html.window.removeEventListener('popstate', fn); + } + + @override + String get pathname => _location.pathname!; + + @override + String get search => _location.search!; + + @override + String get hash => _location.hash; + + @override + Object? get state => _history.state; + + @override + void pushState(Object? state, String title, String url) { + _history.pushState(state, title, url); + } + + @override + void replaceState(Object? state, String title, String url) { + _history.replaceState(state, title, url); + } + + @override + void go(int count) { + _history.go(count); + } + + @override + String? getBaseHref() => html.document.baseUri; +} diff --git a/lib/web_ui/lib/src/engine/test_embedding.dart b/lib/web_ui/lib/src/engine/test_embedding.dart index f0d3a4291dbad..0255e5fb19601 100644 --- a/lib/web_ui/lib/src/engine/test_embedding.dart +++ b/lib/web_ui/lib/src/engine/test_embedding.dart @@ -20,29 +20,27 @@ class TestHistoryEntry { } } -/// This location strategy mimics the browser's history as closely as possible +/// This URL strategy mimics the browser's history as closely as possible /// while doing it all in memory with no interaction with the browser. /// /// It keeps a list of history entries and event listeners in memory and /// manipulates them in order to achieve the desired functionality. -class TestLocationStrategy extends LocationStrategy { - /// Creates a instance of [TestLocationStrategy] with an empty string as the +class TestUrlStrategy extends UrlStrategy { + /// Creates a instance of [TestUrlStrategy] with an empty string as the /// path. - factory TestLocationStrategy() => TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '')); + factory TestUrlStrategy() => TestUrlStrategy.fromEntry(TestHistoryEntry(null, null, '')); - /// Creates an instance of [TestLocationStrategy] and populates it with a list + /// Creates an instance of [TestUrlStrategy] and populates it with a list /// that has [initialEntry] as the only item. - TestLocationStrategy.fromEntry(TestHistoryEntry initialEntry) + TestUrlStrategy.fromEntry(TestHistoryEntry initialEntry) : _currentEntryIndex = 0, history = [initialEntry]; @override - String get path => currentEntry.url; + String getPath() => currentEntry.url; @override - dynamic get state { - return currentEntry.state; - } + dynamic getState() => currentEntry.state; int _currentEntryIndex; int get currentEntryIndex => _currentEntryIndex; @@ -105,12 +103,12 @@ class TestLocationStrategy extends LocationStrategy { } @override - Future back({int count = 1}) { + Future go(int count) { assert(withinAppHistory); - // Browsers don't move back in history immediately. They do it at the next + // Browsers don't move in history immediately. They do it at the next // event loop. So let's simulate that. return _nextEventLoop(() { - _currentEntryIndex = _currentEntryIndex - count; + _currentEntryIndex = _currentEntryIndex + count; if (withinAppHistory) { _firePopStateEvent(); } @@ -124,7 +122,7 @@ class TestLocationStrategy extends LocationStrategy { final List listeners = []; @override - ui.VoidCallback onPopState(html.EventListener fn) { + ui.VoidCallback addPopStateListener(html.EventListener fn) { listeners.add(fn); return () { // Schedule a micro task here to avoid removing the listener during diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 94d73f6c7d717..757d85ee4151e 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -13,20 +13,27 @@ const bool _debugPrintPlatformMessages = false; /// This may be overridden in tests, for example, to pump fake frames. ui.VoidCallback? scheduleFrameCallback; +typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); + +/// A JavaScript hook to customize the URL strategy of a Flutter app. +// +// Keep this js name in sync with flutter_web_plugins. Find it at: +// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +// +// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 +@JS('_flutter_web_set_location_strategy') +external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); + /// The Web implementation of [ui.Window]. class EngineWindow extends ui.Window { EngineWindow() { _addBrightnessMediaQueryListener(); - js.context['_flutter_web_set_location_strategy'] = (LocationStrategy strategy) { - locationStrategy = strategy; - }; - registerHotRestartListener(() { - js.context['_flutter_web_set_location_strategy'] = null; - }); + _addUrlStrategyListener(); } @override - double get devicePixelRatio => _debugDevicePixelRatio ?? browserDevicePixelRatio; + double get devicePixelRatio => + _debugDevicePixelRatio ?? browserDevicePixelRatio; /// Returns device pixel ratio returned by browser. static double get browserDevicePixelRatio { @@ -117,7 +124,8 @@ class EngineWindow extends ui.Window { double height = 0; double width = 0; if (html.window.visualViewport != null) { - height = html.window.visualViewport!.height!.toDouble() * devicePixelRatio; + height = + html.window.visualViewport!.height!.toDouble() * devicePixelRatio; width = html.window.visualViewport!.width!.toDouble() * devicePixelRatio; } else { height = html.window.innerHeight! * devicePixelRatio; @@ -126,7 +134,7 @@ class EngineWindow extends ui.Window { // This method compares the new dimensions with the previous ones. // Return false if the previous dimensions are not set. - if(_physicalSize != null) { + if (_physicalSize != null) { // First confirm both height and width are effected. if (_physicalSize!.height != height && _physicalSize!.width != width) { // If prior to rotation height is bigger than width it should be the @@ -154,78 +162,41 @@ class EngineWindow extends ui.Window { /// Handles the browser history integration to allow users to use the back /// button, etc. @visibleForTesting - BrowserHistory get browserHistory => _browserHistory; - BrowserHistory _browserHistory = MultiEntriesBrowserHistory(); - - @visibleForTesting - Future debugSwitchBrowserHistory({required bool useSingle}) async { - if (useSingle) - await _useSingleEntryBrowserHistory(); - else - await _useMultiEntryBrowserHistory(); - } - - /// This function should only be used for test setup. In real application, we - /// only allow one time switch from the MultiEntriesBrowserHistory to - /// the SingleEntryBrowserHistory to prevent the application to switch back - /// forth between router and non-router. - Future _useMultiEntryBrowserHistory() async { - if (_browserHistory is MultiEntriesBrowserHistory) { - return; - } - final LocationStrategy? strategy = _browserHistory.locationStrategy; - if (strategy != null) - await _browserHistory.setLocationStrategy(null); - _browserHistory = MultiEntriesBrowserHistory(); - if (strategy != null) - await _browserHistory.setLocationStrategy(strategy); + BrowserHistory get browserHistory { + return _browserHistory ??= + MultiEntriesBrowserHistory(urlStrategy: const HashUrlStrategy()); } + BrowserHistory? _browserHistory; + Future _useSingleEntryBrowserHistory() async { if (_browserHistory is SingleEntryBrowserHistory) { return; } - final LocationStrategy? strategy = _browserHistory.locationStrategy; - if (strategy != null) - await _browserHistory.setLocationStrategy(null); - _browserHistory = SingleEntryBrowserHistory(); - if (strategy != null) - await _browserHistory.setLocationStrategy(strategy); + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); } - /// Simulates clicking the browser's back button. - Future webOnlyBack() => _browserHistory.back(); - /// Lazily initialized when the `defaultRouteName` getter is invoked. /// - /// The reason for the lazy initialization is to give enough time for the app to set [locationStrategy] + /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] /// in `lib/src/ui/initialization.dart`. String? _defaultRouteName; @override - String get defaultRouteName => _defaultRouteName ??= _browserHistory.currentPath; + String get defaultRouteName { + return _defaultRouteName ??= browserHistory.currentPath; + } @override void scheduleFrame() { if (scheduleFrameCallback == null) { - throw new Exception( - 'scheduleFrameCallback must be initialized first.'); + throw new Exception('scheduleFrameCallback must be initialized first.'); } scheduleFrameCallback!(); } - /// Change the strategy to use for handling browser history location. - /// Setting this member will automatically update [_browserHistory]. - /// - /// By setting this to null, the browser history will be disabled. - set locationStrategy(LocationStrategy? strategy) { - _browserHistory.setLocationStrategy(strategy); - } - - /// Returns the currently active location strategy. - @visibleForTesting - LocationStrategy? get locationStrategy => _browserHistory.locationStrategy; - @override ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; ui.VoidCallback? _onTextScaleFactorChanged; @@ -477,8 +448,8 @@ class EngineWindow extends ui.Window { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. - void invokeOnPlatformMessage( - String name, ByteData? data, ui.PlatformMessageResponseCallback callback) { + void invokeOnPlatformMessage(String name, ByteData? data, + ui.PlatformMessageResponseCallback callback) { _invoke3( _onPlatformMessage, _onPlatformMessageZone, @@ -500,7 +471,9 @@ class EngineWindow extends ui.Window { /// Wraps the given [callback] in another callback that ensures that the /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(ui.PlatformMessageResponseCallback? callback) { + static ui.PlatformMessageResponseCallback? + _zonedPlatformMessageResponseCallback( + ui.PlatformMessageResponseCallback? callback) { if (callback == null) { return null; } @@ -564,7 +537,7 @@ class EngineWindow extends ui.Window { final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { case 'SystemNavigator.pop': - _browserHistory.exit().then((_) { + browserHistory.exit().then((_) { _replyToPlatformMessage( callback, codec.encodeSuccessEnvelope(true)); }); @@ -585,8 +558,8 @@ class EngineWindow extends ui.Window { case 'SystemChrome.setPreferredOrientations': final List? arguments = decoded.arguments; domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage(callback, - codec.encodeSuccessEnvelope(success)); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(success)); }); return; case 'SystemSound.play': @@ -632,7 +605,8 @@ class EngineWindow extends ui.Window { case 'flutter/platform_views': if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder.handlePlatformViewCall(data, callback); + rasterizer!.surface.viewEmbedder + .handlePlatformViewCall(data, callback); } else { ui.handlePlatformViewCall(data!, callback!); } @@ -646,27 +620,11 @@ class EngineWindow extends ui.Window { return; case 'flutter/navigation': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map message = decoded.arguments as Map; - switch (decoded.method) { - case 'routeUpdated': - _useSingleEntryBrowserHistory().then((void data) { - _browserHistory.setRouteName(message['routeName']); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - }); - break; - case 'routeInformationUpdated': - assert(_browserHistory is MultiEntriesBrowserHistory); - _browserHistory.setRouteName( - message['location'], - state: message['state'], - ); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - break; - } + _handleNavigationMessage(data, callback).then((handled) { + if (!handled && callback != null) { + callback(null); + } + }); // As soon as Flutter starts taking control of the app navigation, we // should reset [_defaultRouteName] to "/" so it doesn't have any // further effect after this point. @@ -685,6 +643,51 @@ class EngineWindow extends ui.Window { _replyToPlatformMessage(callback, null); } + @visibleForTesting + Future debugInitializeHistory( + UrlStrategy? strategy, { + required bool useSingle, + }) async { + await _browserHistory?.tearDown(); + if (useSingle) { + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } else { + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } + } + + @visibleForTesting + Future debugResetHistory() async { + await _browserHistory?.tearDown(); + _browserHistory = null; + } + + Future _handleNavigationMessage( + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) async { + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map arguments = decoded.arguments; + + switch (decoded.method) { + case 'routeUpdated': + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + case 'routeInformationUpdated': + assert(browserHistory is MultiEntriesBrowserHistory); + browserHistory.setRouteName( + arguments['location'], + state: arguments['state'], + ); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + } + return false; + } + int _getHapticFeedbackDuration(String? type) { switch (type) { case 'HapticFeedbackType.lightImpact': @@ -746,7 +749,8 @@ class EngineWindow extends ui.Window { : ui.Brightness.light); _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = event as html.MediaQueryListEvent; + final html.MediaQueryListEvent mqEvent = + event as html.MediaQueryListEvent; _updatePlatformBrightness( mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); }; @@ -756,6 +760,21 @@ class EngineWindow extends ui.Window { }); } + void _addUrlStrategyListener() { + _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { + assert( + _browserHistory == null, + 'Cannot set URL strategy more than once.', + ); + final UrlStrategy? strategy = + jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + }); + registerHotRestartListener(() { + _jsSetUrlStrategy = null; + }); + } + /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. void _removeBrightnessMediaQueryListener() { _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); @@ -785,7 +804,8 @@ class EngineWindow extends ui.Window { } @visibleForTesting - late Rasterizer? rasterizer = experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; + late Rasterizer? rasterizer = + experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; } bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { @@ -831,8 +851,8 @@ void _invoke1(void callback(A a)?, Zone? zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3( - void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, A1 arg1, A2 arg2, A3 arg3) { +void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, + A1 arg1, A2 arg2, A3 arg3) { if (callback == null) { return; } diff --git a/lib/web_ui/lib/src/ui/initialization.dart b/lib/web_ui/lib/src/ui/initialization.dart index a7b06b3586def..ca317304ec79b 100644 --- a/lib/web_ui/lib/src/ui/initialization.dart +++ b/lib/web_ui/lib/src/ui/initialization.dart @@ -21,10 +21,6 @@ Future webOnlyInitializePlatform({ Future _initializePlatform({ engine.AssetManager? assetManager, }) async { - if (!debugEmulateFlutterTesterEnvironment) { - engine.window.locationStrategy = const engine.HashLocationStrategy(); - } - engine.initializeEngine(); // This needs to be after `webOnlyInitializeEngine` because that is where the diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index 4c11ed0033636..f5be42ae61db7 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -16,11 +16,6 @@ import 'package:ui/src/engine.dart'; import '../spy.dart'; -TestLocationStrategy get strategy => window.browserHistory.locationStrategy; -Future setStrategy(TestLocationStrategy newStrategy) async { - await window.browserHistory.setLocationStrategy(newStrategy); -} - Map _wrapOriginState(dynamic state) { return {'origin': true, 'state': state}; } @@ -48,18 +43,19 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: true); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await setStrategy(null); + await window.debugResetHistory(); }); test('basic setup works', () async { - await setStrategy(TestLocationStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); // There should be two entries: origin and flutter. expect(strategy.history, hasLength(2)); @@ -82,7 +78,11 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button pops routes correctly', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); + // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(2)); expect(strategy.currentEntry.state, flutterState); @@ -98,7 +98,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.back(); + await strategy.go(-1); // First, the framework should've received a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -115,7 +115,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await routeUpdated('/page1'); await routeUpdated('/page2'); @@ -127,7 +130,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -143,7 +146,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -161,8 +164,8 @@ void testMain() { // The next browser back will exit the app. We store the strategy locally // because it will be remove from the browser history class once it exits // the app. - TestLocationStrategy originalStrategy = strategy; - await originalStrategy.back(); + TestUrlStrategy originalStrategy = strategy; + await originalStrategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -181,7 +184,10 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -202,7 +208,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -221,7 +227,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('user types unknown url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await strategy.simulateUserTypingUrl('/unknown'); // This delay is necessary to wait for [BrowserHistory] because it @@ -248,18 +257,19 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: false); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await setStrategy(null); + await window.debugResetHistory(); }); test('basic setup works', () async { - await setStrategy(TestLocationStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); // There should be only one entry. expect(strategy.history, hasLength(1)); @@ -273,7 +283,11 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button push route infromation correctly', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); + // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); @@ -289,7 +303,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.back(); + await strategy.go(-1); // First, the framework should've received a `pushRouteInformation` // platform message. expect(spy.messages, hasLength(1)); @@ -310,7 +324,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -322,7 +339,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -338,7 +355,7 @@ void testMain() { expect(strategy.currentEntry.state, _tagStateWithSerialCount('page1 state', 1)); expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -359,7 +376,10 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -381,7 +401,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -401,7 +421,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('forward button works', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -413,7 +436,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -430,7 +453,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Forward to page2 - await strategy.back(count: -1); + await strategy.go(1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -450,7 +473,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); }); - group('$HashLocationStrategy', () { + group('$HashUrlStrategy', () { TestPlatformLocation location; setUp(() { @@ -462,26 +485,26 @@ void testMain() { }); test('leading slash is optional', () { - final HashLocationStrategy strategy = HashLocationStrategy(location); + final HashUrlStrategy strategy = HashUrlStrategy(location); location.hash = '#/'; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); location.hash = '#/foo'; - expect(strategy.path, '/foo'); + expect(strategy.getPath(), '/foo'); location.hash = '#foo'; - expect(strategy.path, 'foo'); + expect(strategy.getPath(), 'foo'); }); test('path should not be empty', () { - final HashLocationStrategy strategy = HashLocationStrategy(location); + final HashUrlStrategy strategy = HashUrlStrategy(location); location.hash = ''; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); location.hash = '#'; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); }); }); } @@ -529,31 +552,31 @@ class TestPlatformLocation extends PlatformLocation { String hash; dynamic state; - void onPopState(html.EventListener fn) { + @override + void addPopStateListener(html.EventListener fn) { throw UnimplementedError(); } - void offPopState(html.EventListener fn) { - throw UnimplementedError(); - } - - void onHashChange(html.EventListener fn) { - throw UnimplementedError(); - } - - void offHashChange(html.EventListener fn) { + @override + void removePopStateListener(html.EventListener fn) { throw UnimplementedError(); } + @override void pushState(dynamic state, String title, String url) { throw UnimplementedError(); } + @override void replaceState(dynamic state, String title, String url) { throw UnimplementedError(); } - void back(int count) { + @override + void go(int count) { throw UnimplementedError(); } + + @override + String getBaseHref() => '/'; } diff --git a/lib/web_ui/test/engine/navigation_test.dart b/lib/web_ui/test/engine/navigation_test.dart index 44d3bf2939e95..99bae818f54a7 100644 --- a/lib/web_ui/test/engine/navigation_test.dart +++ b/lib/web_ui/test/engine/navigation_test.dart @@ -10,7 +10,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart' as engine; -engine.TestLocationStrategy _strategy; +engine.TestUrlStrategy _strategy; const engine.MethodCodec codec = engine.JSONMethodCodec(); @@ -21,12 +21,14 @@ void main() { } void testMain() { - setUp(() { - engine.window.locationStrategy = _strategy = engine.TestLocationStrategy(); + setUp(() async { + _strategy = engine.TestUrlStrategy(); + await engine.window.debugInitializeHistory(_strategy, useSingle: true); }); - tearDown(() { - engine.window.locationStrategy = _strategy = null; + tearDown(() async { + _strategy = null; + await engine.window.debugResetHistory(); }); test('Tracks pushed, replaced and popped routes', () async { @@ -40,6 +42,6 @@ void testMain() { (_) => completer.complete(), ); await completer.future; - expect(_strategy.path, '/foo'); + expect(_strategy.getPath(), '/foo'); }); } diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index b83849bffc8d6..ef0a755f550cf 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. // @dart = 2.6 -import 'dart:async'; import 'dart:html' as html; import 'dart:js_util' as js_util; import 'dart:typed_data'; @@ -12,34 +11,39 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -const MethodCodec codec = JSONMethodCodec(); +import 'engine/history_test.dart'; +import 'matchers.dart'; -void emptyCallback(ByteData date) {} +const MethodCodec codec = JSONMethodCodec(); -Future setStrategy(TestLocationStrategy newStrategy) async { - await window.browserHistory.setLocationStrategy(newStrategy); -} +void emptyCallback(ByteData data) {} void main() { internalBootstrapBrowserTest(() => testMain); } void testMain() { - setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: true); + tearDown(() async { + await window.debugResetHistory(); }); test('window.defaultRouteName should not change', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. - window.locationStrategy.replaceState(null, null, '/newpath'); + strategy.replaceState(null, null, '/newpath'); expect(window.defaultRouteName, '/initial'); }); - test('window.defaultRouteName should reset after navigation platform message', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); + test('window.defaultRouteName should reset after navigation platform message', + () async { + await window.debugInitializeHistory(TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ), useSingle: true); // Reading it multiple times should return the same value. expect(window.defaultRouteName, '/initial'); expect(window.defaultRouteName, '/initial'); @@ -57,45 +61,45 @@ void testMain() { }); test('can disable location strategy', () async { - await window.debugSwitchBrowserHistory(useSingle: true); - final testStrategy = TestLocationStrategy.fromEntry( + // Disable URL strategy. + expect(() => jsSetUrlStrategy(null), returnsNormally); + // History should be initialized. + expect(window.browserHistory, isNotNull); + // But without a URL strategy. + expect(window.browserHistory.urlStrategy, isNull); + // Current path is always "/" in this case. + expect(window.browserHistory.currentPath, '/'); + + // Perform some navigation operations. + routeInfomrationUpdated('/foo/bar', null); + // Path should not be updated because URL strategy is disabled. + expect(window.browserHistory.currentPath, '/'); + }); + + test('js interop throws on wrong type', () { + expect(() => jsSetUrlStrategy(123), throwsA(anything)); + expect(() => jsSetUrlStrategy('foo'), throwsA(anything)); + expect(() => jsSetUrlStrategy(false), throwsA(anything)); + }); + + test('cannot set url strategy after it is initialized', () async { + final testStrategy = TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/'), ); - await setStrategy(testStrategy); - - expect(window.locationStrategy, testStrategy); - // A single listener should've been setup. - expect(testStrategy.listeners, hasLength(1)); - // The initial entry should be there, plus another "flutter" entry. - expect(testStrategy.history, hasLength(2)); - expect(testStrategy.history[0].state, {'origin': true, 'state': 'initial state'}); - expect(testStrategy.history[1].state, {'flutter': true}); - expect(testStrategy.currentEntry, testStrategy.history[1]); - - // Now, let's disable location strategy and make sure things get cleaned up. - expect(() => jsSetLocationStrategy(null), returnsNormally); - // The locationStrategy is teared down asynchronously. - await Future.delayed(Duration.zero); - expect(window.locationStrategy, isNull); - - // The listener is removed asynchronously. - await Future.delayed(const Duration(milliseconds: 10)); - - // No more listeners. - expect(testStrategy.listeners, isEmpty); - // History should've moved back to the initial state. - expect(testStrategy.history[0].state, "initial state"); - expect(testStrategy.currentEntry, testStrategy.history[0]); + await window.debugInitializeHistory(testStrategy, useSingle: true); + + expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); }); - test('js interop throws on wrong type', () { - expect(() => jsSetLocationStrategy(123), throwsA(anything)); - expect(() => jsSetLocationStrategy('foo'), throwsA(anything)); - expect(() => jsSetLocationStrategy(false), throwsA(anything)); + test('cannot set url strategy more than once', () async { + // First time is okay. + expect(() => jsSetUrlStrategy(null), returnsNormally); + // Second time is not allowed. + expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); }); } -void jsSetLocationStrategy(dynamic strategy) { +void jsSetUrlStrategy(dynamic strategy) { js_util.callMethod( html.window, '_flutter_web_set_location_strategy', From 85ed6567226e7c4d6f6ea456090ab43249c48682 Mon Sep 17 00:00:00 2001 From: Pieter van Loon Date: Thu, 8 Oct 2020 02:47:03 +0200 Subject: [PATCH 007/219] Enabled metal on ios simulator (#17881) --- shell/common/switches.h | 2 +- shell/gpu/gpu_surface_metal.h | 3 +- .../ios/framework/Source/FlutterEngine.mm | 5 ++- .../darwin/ios/framework/Source/FlutterView.h | 3 +- .../ios/framework/Source/FlutterView.mm | 13 ++++++- .../framework/Source/FlutterViewController.mm | 6 ++++ shell/platform/darwin/ios/ios_surface.mm | 18 ++++++---- shell/platform/darwin/ios/ios_surface_metal.h | 4 ++- .../darwin/ios/rendering_api_selection.h | 18 ++++++++-- .../darwin/ios/rendering_api_selection.mm | 36 +++++++++++++------ .../Scenarios.xcodeproj/project.pbxproj | 4 +++ .../ios/Scenarios/Scenarios/AppDelegate.m | 13 ++++++- .../GoldenPlatformViewTests.m | 2 +- .../PlatformViewGestureRecognizerTests.m | 3 +- .../ScenariosUITests/RenderingSelectionTest.m | 34 ++++++++++++++++++ .../UnobstructedPlatformViewTests.m | 3 +- tools/gn | 4 +-- 17 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/RenderingSelectionTest.m diff --git a/shell/common/switches.h b/shell/common/switches.h index f33e2722403df..260214db4e8a0 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -97,7 +97,7 @@ DEF_SWITCH(EnableSoftwareRendering, "enable-software-rendering", "Enable rendering using the Skia software backend. This is useful " "when testing Flutter on emulators. By default, Flutter will " - "attempt to either use OpenGL or Vulkan.") + "attempt to either use OpenGL, Metal, or Vulkan.") DEF_SWITCH(SkiaDeterministicRendering, "skia-deterministic-rendering", "Skips the call to SkGraphics::Init(), thus avoiding swapping out " diff --git a/shell/gpu/gpu_surface_metal.h b/shell/gpu/gpu_surface_metal.h index 42d9851d534c6..5a91307af4da7 100644 --- a/shell/gpu/gpu_surface_metal.h +++ b/shell/gpu/gpu_surface_metal.h @@ -12,12 +12,13 @@ #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/shell/gpu/gpu_surface_delegate.h" #include "third_party/skia/include/gpu/GrDirectContext.h" +#include "third_party/skia/include/gpu/mtl/GrMtlTypes.h" @class CAMetalLayer; namespace flutter { -class GPUSurfaceMetal : public Surface { +class SK_API_AVAILABLE_CA_METAL_LAYER GPUSurfaceMetal : public Surface { public: GPUSurfaceMetal(GPUSurfaceDelegate* delegate, fml::scoped_nsobject layer, diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 347b853270e89..2592537749efa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -474,6 +474,8 @@ - (BOOL)createShell:(NSString*)entrypoint self.initialRoute = initialRoute; auto settings = [_dartProject.get() settings]; + FlutterView.forceSoftwareRendering = settings.enable_software_rendering; + auto platformData = [_dartProject.get() defaultPlatformData]; if (libraryURI) { @@ -513,7 +515,8 @@ - (BOOL)createShell:(NSString*)entrypoint flutter::Shell::CreateCallback on_create_platform_view = [](flutter::Shell& shell) { return std::make_unique( - shell, flutter::GetRenderingAPIForProcess(), shell.GetTaskRunners()); + shell, flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering), + shell.GetTaskRunners()); }; flutter::Shell::CreateCallback on_create_rasterizer = diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.h b/shell/platform/darwin/ios/framework/Source/FlutterView.h index 2806c3359f6c3..3a9c138de02aa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -21,7 +21,6 @@ asBase64Encoded:(BOOL)base64Encode; - (flutter::FlutterPlatformViewsController*)platformViewsController; - @end @interface FlutterView : UIView @@ -35,6 +34,8 @@ opaque:(BOOL)opaque NS_DESIGNATED_INITIALIZER; - (std::unique_ptr)createSurface:(std::shared_ptr)context; +// Set by FlutterEngine or FlutterViewController to override software rendering. +@property(class, nonatomic) BOOL forceSoftwareRendering; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTER_VIEW_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 8fb97f68335ef..3f6b19decdac3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -68,8 +68,19 @@ - (void)layoutSubviews { [super layoutSubviews]; } +static BOOL _forceSoftwareRendering; + ++ (BOOL)forceSoftwareRendering { + return _forceSoftwareRendering; +} + ++ (void)setForceSoftwareRendering:(BOOL)forceSoftwareRendering { + _forceSoftwareRendering = forceSoftwareRendering; +} + + (Class)layerClass { - return flutter::GetCoreAnimationLayerClassForRenderingAPI(); + return flutter::GetCoreAnimationLayerClassForRenderingAPI( + flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering)); } - (std::unique_ptr)createSurface: diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 792256082fbf7..ed7e1d79fd56d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -157,6 +157,12 @@ - (instancetype)init { - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project initialRoute:(nullable NSString*)initialRoute { + // Need the project to get settings for the view. Initializing it here means + // the Engine class won't initialize it later. + if (!project) { + project = [[[FlutterDartProject alloc] init] autorelease]; + } + FlutterView.forceSoftwareRendering = project.settings.enable_software_rendering; auto engine = fml::scoped_nsobject{[[FlutterEngine alloc] initWithName:@"io.flutter" project:project diff --git a/shell/platform/darwin/ios/ios_surface.mm b/shell/platform/darwin/ios/ios_surface.mm index 976c71ee45a49..331cbeb8ac77e 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -7,6 +7,8 @@ #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" #import "flutter/shell/platform/darwin/ios/ios_surface_software.h" +#include "flutter/shell/platform/darwin/ios/rendering_api_selection.h" + #if FLUTTER_SHELL_ENABLE_METAL #import "flutter/shell/platform/darwin/ios/ios_surface_metal.h" #endif // FLUTTER_SHELL_ENABLE_METAL @@ -30,13 +32,15 @@ } #if FLUTTER_SHELL_ENABLE_METAL - if ([layer.get() isKindOfClass:[CAMetalLayer class]]) { - return std::make_unique( - fml::scoped_nsobject( - reinterpret_cast([layer.get() retain])), // Metal layer - std::move(context), // context - platform_views_controller // platform views controller - ); + if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { + if ([layer.get() isKindOfClass:[CAMetalLayer class]]) { + return std::make_unique( + fml::scoped_nsobject( + reinterpret_cast([layer.get() retain])), // Metal layer + std::move(context), // context + platform_views_controller // platform views controller + ); + } } #endif // FLUTTER_SHELL_ENABLE_METAL diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index 7d1e4d5d159a8..c449248eda633 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -8,12 +8,14 @@ #include "flutter/fml/macros.h" #include "flutter/shell/gpu/gpu_surface_delegate.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#include "third_party/skia/include/gpu/mtl/GrMtlTypes.h" @class CAMetalLayer; namespace flutter { -class IOSSurfaceMetal final : public IOSSurface, public GPUSurfaceDelegate { +class SK_API_AVAILABLE_CA_METAL_LAYER IOSSurfaceMetal final : public IOSSurface, + public GPUSurfaceDelegate { public: IOSSurfaceMetal(fml::scoped_nsobject layer, std::shared_ptr context, diff --git a/shell/platform/darwin/ios/rendering_api_selection.h b/shell/platform/darwin/ios/rendering_api_selection.h index f30a49a091e21..07fe13ccb198d 100644 --- a/shell/platform/darwin/ios/rendering_api_selection.h +++ b/shell/platform/darwin/ios/rendering_api_selection.h @@ -17,11 +17,23 @@ enum class IOSRenderingAPI { kMetal, }; -IOSRenderingAPI GetRenderingAPIForProcess(); +// Pass force_software to force software rendering. This is only respected on +// simulators. +IOSRenderingAPI GetRenderingAPIForProcess(bool force_software); -Class GetCoreAnimationLayerClassForRenderingAPI( - IOSRenderingAPI rendering_api = GetRenderingAPIForProcess()); +Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api); } // namespace flutter +// Flutter supports Metal on all devices with Apple A7 SoC or above that have +// been updated to or past iOS 10.0. The processor was selected as it is the +// first version at which Metal was supported. The iOS version floor was +// selected due to the availability of features used by Skia. +// Support for Metal on simulators was added by Apple in the SDK for iOS 13. +#if TARGET_OS_SIMULATOR +#define METAL_IOS_VERSION_BASELINE 13.0 +#else +#define METAL_IOS_VERSION_BASELINE 10.0 +#endif // TARGET_OS_SIMULATOR + #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_RENDERING_API_SELECTION_H_ diff --git a/shell/platform/darwin/ios/rendering_api_selection.mm b/shell/platform/darwin/ios/rendering_api_selection.mm index 6ee84bcc69edd..2cf23bcccd6d5 100644 --- a/shell/platform/darwin/ios/rendering_api_selection.mm +++ b/shell/platform/darwin/ios/rendering_api_selection.mm @@ -10,6 +10,7 @@ #if FLUTTER_SHELL_ENABLE_METAL #include #endif // FLUTTER_SHELL_ENABLE_METAL +#import #include "flutter/fml/logging.h" @@ -17,11 +18,8 @@ #if FLUTTER_SHELL_ENABLE_METAL bool ShouldUseMetalRenderer() { - // Flutter supports Metal on all devices with Apple A7 SoC or above that have been updated to or - // past iOS 10.0. The processor was selected as it is the first version at which Metal was - // supported. The iOS version floor was selected due to the availability of features used by Skia. bool ios_version_supports_metal = false; - if (@available(iOS 10.0, *)) { + if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { auto device = MTLCreateSystemDefaultDevice(); ios_version_supports_metal = [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v3]; } @@ -29,10 +27,17 @@ bool ShouldUseMetalRenderer() { } #endif // FLUTTER_SHELL_ENABLE_METAL -IOSRenderingAPI GetRenderingAPIForProcess() { -#if TARGET_IPHONE_SIMULATOR - return IOSRenderingAPI::kSoftware; -#endif // TARGET_IPHONE_SIMULATOR +IOSRenderingAPI GetRenderingAPIForProcess(bool force_software) { +#if TARGET_OS_SIMULATOR + if (force_software) { + return IOSRenderingAPI::kSoftware; + } +#else + if (force_software) { + FML_LOG(WARNING) << "The --enable-software-rendering is only supported on Simulator targets " + "and will be ignored."; + } +#endif // TARGET_OS_SIMULATOR #if FLUTTER_SHELL_ENABLE_METAL static bool should_use_metal = ShouldUseMetalRenderer(); @@ -40,7 +45,14 @@ IOSRenderingAPI GetRenderingAPIForProcess() { return IOSRenderingAPI::kMetal; } #endif // FLUTTER_SHELL_ENABLE_METAL + + // OpenGL will be emulated using software rendering by Apple on the simulator, so we use the + // Skia software rendering since it performs a little better than the emulated OpenGL. +#if TARGET_OS_SIMULATOR + return IOSRenderingAPI::kSoftware; +#else return IOSRenderingAPI::kOpenGLES; +#endif // TARGET_OS_SIMULATOR } Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api) { @@ -49,10 +61,12 @@ Class GetCoreAnimationLayerClassForRenderingAPI(IOSRenderingAPI rendering_api) { return [CALayer class]; case IOSRenderingAPI::kOpenGLES: return [CAEAGLLayer class]; -#if !TARGET_IPHONE_SIMULATOR case IOSRenderingAPI::kMetal: - return [CAMetalLayer class]; -#endif // !TARGET_IPHONE_SIMULATOR + if (@available(iOS METAL_IOS_VERSION_BASELINE, *)) { + return [CAMetalLayer class]; + } + FML_CHECK(false) << "Metal availability should already have been checked"; + break; default: break; } diff --git a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj index cc4462541c2d5..71d8cb57f989d 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj +++ b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 242F37A222E636DE001E83D4 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4522E3B61000073EBF /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 242F37A322E636DE001E83D4 /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4122E3B5F700073EBF /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 244EA6D0230DBE8900B2D26E /* golden_platform_view_D21AP.png in Resources */ = {isa = PBXBuildFile; fileRef = 244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */; }; + 246A6611252E693A00EAB0F3 /* RenderingSelectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 246A6610252E693A00EAB0F3 /* RenderingSelectionTest.m */; }; 246B4E4222E3B5F700073EBF /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4122E3B5F700073EBF /* App.framework */; }; 246B4E4622E3B61000073EBF /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4522E3B61000073EBF /* Flutter.framework */; }; 248D76CC22E388370012F0C1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 248D76CB22E388370012F0C1 /* AppDelegate.m */; }; @@ -126,6 +127,7 @@ 0D8470A3240F0B1F0030B565 /* StatusBarTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StatusBarTest.m; sourceTree = ""; }; 0DB781FC22EA2C0300E9B371 /* FlutterViewControllerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlutterViewControllerTest.m; sourceTree = ""; }; 244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = golden_platform_view_D21AP.png; sourceTree = ""; }; + 246A6610252E693A00EAB0F3 /* RenderingSelectionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RenderingSelectionTest.m; sourceTree = ""; }; 246B4E4122E3B5F700073EBF /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = App.framework; sourceTree = ""; }; 246B4E4522E3B61000073EBF /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Flutter.framework; sourceTree = ""; }; 248D76C722E388370012F0C1 /* Scenarios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Scenarios.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -291,6 +293,7 @@ 0D8470A3240F0B1F0030B565 /* StatusBarTest.m */, 0A42BFB32447E179007E212E /* TextSemanticsFocusTest.m */, 0A42BFB52447E19F007E212E /* TextSemanticsFocusTest.h */, + 246A6610252E693A00EAB0F3 /* RenderingSelectionTest.m */, ); path = ScenariosUITests; sourceTree = ""; @@ -488,6 +491,7 @@ 6816DBA42318358200A51400 /* PlatformViewGoldenTestManager.m in Sources */, 248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */, 0D8470A4240F0B1F0030B565 /* StatusBarTest.m in Sources */, + 246A6611252E693A00EAB0F3 /* RenderingSelectionTest.m in Sources */, 4F06F1B32473296E000AF246 /* LocalizationInitializationTest.m in Sources */, 0A42BFB42447E179007E212E /* TextSemanticsFocusTest.m in Sources */, ); diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index b981938a05bfc..96ddd0c734f6d 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -50,7 +50,8 @@ - (BOOL)application:(UIApplication*)application @"--gesture-reject-eager" : @"platform_view_gesture_reject_eager", @"--gesture-accept" : @"platform_view_gesture_accept", @"--tap-status-bar" : @"tap_status_bar", - @"--text-semantics-focus" : @"text_semantics_focus" + @"--text-semantics-focus" : @"text_semantics_focus", + @"--animated-color-square" : @"animated_color_square", }; __block NSString* flutterViewControllerTestName = nil; [launchArgsMap @@ -119,6 +120,16 @@ - (void)setupFlutterViewControllerTest:(NSString*)scenarioIdentifier { gestureRecognizersBlockingPolicy: FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded]; self.window.rootViewController = flutterViewController; + + if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--assert-ca-layer-type"]) { + if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--enable-software-rendering"]) { + NSAssert([flutterViewController.view.layer isKindOfClass:[CALayer class]], + @"Expected CALayer for software rendering."); + } else { + NSAssert([flutterViewController.view.layer isKindOfClass:[CAMetalLayer class]], + @"Expected CAMetalLayer for non-software rendering."); + } + } } @end diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenPlatformViewTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenPlatformViewTests.m index 78d65163b8a5a..29f1a472aa094 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenPlatformViewTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenPlatformViewTests.m @@ -32,7 +32,7 @@ - (void)setUp { self.continueAfterFailure = NO; self.application = [[XCUIApplication alloc] init]; - self.application.launchArguments = @[ self.manager.launchArg ]; + self.application.launchArguments = @[ self.manager.launchArg, @"--enable-software-rendering" ]; [self.application launch]; } diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m index 6771454c40318..b37a0ad14b14b 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m @@ -18,7 +18,8 @@ - (void)setUp { - (void)testRejectPolicyUtilTouchesEnded { XCUIApplication* app = [[XCUIApplication alloc] init]; - app.launchArguments = @[ @"--gesture-reject-after-touches-ended" ]; + app.launchArguments = + @[ @"--gesture-reject-after-touches-ended", @"--enable-software-rendering" ]; [app launch]; NSPredicate* predicateToFindPlatformView = diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/RenderingSelectionTest.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/RenderingSelectionTest.m new file mode 100644 index 0000000000000..0f52816b60e5c --- /dev/null +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/RenderingSelectionTest.m @@ -0,0 +1,34 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +@interface RenderingSelectionTest : XCTestCase +@property(nonatomic, strong) XCUIApplication* application; +@end + +@implementation RenderingSelectionTest + +- (void)setUp { + [super setUp]; + self.continueAfterFailure = NO; + self.application = [[XCUIApplication alloc] init]; +} + +- (void)testSoftwareRendering { + self.application.launchArguments = + @[ @"--animated-color-square", @"--assert-ca-layer-type", @"--enable-software-rendering" ]; + [self.application launch]; + + // App asserts that the rendering API is CALayer +} + +- (void)testMetalRendering { + self.application.launchArguments = @[ @"--animated-color-square", @"--assert-ca-layer-type" ]; + [self.application launch]; + + // App asserts that the rendering API is CAMetalLayer +} +@end diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m index 73186b0ba00a2..fa335a338c6a5 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m @@ -21,7 +21,8 @@ - (void)setUp { // +---+ - (void)testNoOverlay { XCUIApplication* app = [[XCUIApplication alloc] init]; - app.launchArguments = @[ @"--platform-view-no-overlay-intersection" ]; + app.launchArguments = + @[ @"--platform-view-no-overlay-intersection", @"--enable-software-rendering" ]; [app launch]; XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; diff --git a/tools/gn b/tools/gn index dd2dedbd0bb3f..c0ecc2bf430f4 100755 --- a/tools/gn +++ b/tools/gn @@ -222,8 +222,8 @@ def to_gn_args(args): gn_args['use_goma'] = False gn_args['goma_dir'] = None - # Enable Metal on non-simulator iOS builds. - if args.target_os == 'ios' and not args.simulator: + # Enable Metal on iOS builds. + if args.target_os == 'ios': gn_args['skia_use_metal'] = True gn_args['shell_enable_metal'] = True # Bitcode enabled builds using the current version of the toolchain leak From 669bafe552b4732675d0dd5258baa2809057276f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 7 Oct 2020 20:52:01 -0400 Subject: [PATCH 008/219] Roll Dart SDK from 9560a32779fc to 8f1a96317589 (12 revisions) (#21678) --- DEPS | 6 +++--- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index a4baefbdc7ce6..5b2619bcdd0cd 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '9560a32779fc966349dd2d719b76aee49fb6a1de', + 'dart_revision': '8f1a96317589c23685b2f7a097953cea0396ac23', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -63,7 +63,7 @@ vars = { 'dart_stagehand_tag': 'v3.3.9', 'dart_stream_channel_tag': 'c446774fd077c9bdbd6235a7aadc661ef60a9727', 'dart_test_reflective_loader_tag': '0.1.9', - 'dart_tflite_native_rev': '3c777c40608a2a9f1427bfe0028ab48e7116b4c1', + 'dart_tflite_native_rev': '0.4.0+1', 'dart_typed_data_tag': 'f94fc57b8e8c0e4fe4ff6cfd8290b94af52d3719', 'dart_usage_tag': '3.4.0', 'dart_watcher_rev': 'fc3c9aae5d31d707b3013b42634dde8d8a1161b4', @@ -193,7 +193,7 @@ deps = { Var('dart_git') + '/dart2js_info.git@0632a623b08e1f601c7eba99e0186a581ae799e9', 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@2bef0f260594b822f55c8c8f777d9c4c1ea8f76c', + Var('dart_git') + '/dartdoc.git@v0.35.0', 'src/third_party/dart/third_party/pkg/ffi': Var('dart_git') + '/ffi.git@454ab0f9ea6bd06942a983238d8a6818b1357edb', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index b1c841f527adb..85d5369533240 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 5188b32b8a99778d79838f3d3d67036d +Signature: 726c29a5daea48a4e2ab4901e613432d UNUSED LICENSES: From a068e459aecb51abac2f86ad1895ae844fdf1bb2 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 7 Oct 2020 17:57:04 -0700 Subject: [PATCH 009/219] Avoid leaking the FlutterEngineAOTData structure in FlutterEngineCollectAOTData. (#21680) --- shell/platform/embedder/embedder.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 506a5418cff27..971ef22024f59 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -651,13 +651,13 @@ FlutterEngineResult FlutterEngineCreateAOTData( } FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { - if (data) { - data->loaded_elf = nullptr; - data->vm_snapshot_data = nullptr; - data->vm_snapshot_instrs = nullptr; - data->vm_isolate_data = nullptr; - data->vm_isolate_instrs = nullptr; + if (!data) { + // Deleting a null object should be a no-op. + return kSuccess; } + + // Created in a unique pointer in `FlutterEngineCreateAOTData`. + delete data; return kSuccess; } From fbe685910659364513f60c26cc618208abeab306 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 7 Oct 2020 18:31:17 -0700 Subject: [PATCH 010/219] Store selection base/extent as integers (#21663) Previously, the selection base and extent were stored internally as iterators over text_. Since iterators must be treated as invalidated whenever the underlying container changes, this requires that selection_base_ and selection_extent_ be re-assigned after every change to text_. This is not currently particularly problematic, but once we add fields to track the base and extent of the composing region for multi-step input method support, as well as support for the sub-range within the composing region to which edits/completions apply, we end up having to regenerate a lot of iterators with each change, many of which are logically unchanged in position. A side benefit is that this simplifies inspection of these fields when debugging. --- shell/platform/common/cpp/text_input_model.cc | 79 ++++++++++--------- shell/platform/common/cpp/text_input_model.h | 23 +++--- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index 17f3beb01d84c..c30ff61f4dfe5 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -30,8 +30,7 @@ bool IsTrailingSurrogate(char32_t code_point) { } // namespace -TextInputModel::TextInputModel() - : selection_base_(text_.begin()), selection_extent_(text_.begin()) {} +TextInputModel::TextInputModel() = default; TextInputModel::~TextInputModel() = default; @@ -39,21 +38,23 @@ void TextInputModel::SetText(const std::string& text) { std::wstring_convert, char16_t> utf16_converter; text_ = utf16_converter.from_bytes(text); - selection_base_ = text_.begin(); - selection_extent_ = selection_base_; + selection_base_ = 0; + selection_extent_ = 0; } bool TextInputModel::SetSelection(size_t base, size_t extent) { - if (base > text_.size() || extent > text_.size()) { + auto max_pos = text_.length(); + if (base > max_pos || extent > max_pos) { return false; } - selection_base_ = text_.begin() + base; - selection_extent_ = text_.begin() + extent; + selection_base_ = base; + selection_extent_ = extent; return true; } void TextInputModel::DeleteSelected() { - selection_base_ = text_.erase(selection_start(), selection_end()); + text_.erase(selection_start(), selection_end() - selection_start()); + selection_base_ = selection_start(); selection_extent_ = selection_base_; } @@ -75,7 +76,7 @@ void TextInputModel::AddText(const std::u16string& text) { if (selection_base_ != selection_extent_) { DeleteSelected(); } - selection_extent_ = text_.insert(selection_extent_, text.begin(), text.end()); + text_.insert(selection_extent_, text); selection_extent_ += text.length(); selection_base_ = selection_extent_; } @@ -87,27 +88,32 @@ void TextInputModel::AddText(const std::string& text) { } bool TextInputModel::Backspace() { + // If there's a selection, delete it. if (selection_base_ != selection_extent_) { DeleteSelected(); return true; } - if (selection_base_ != text_.begin()) { - int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1; - selection_base_ = text_.erase(selection_base_ - count, selection_base_); + // There's no selection; delete the preceding codepoint. + if (selection_base_ != 0) { + int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1; + text_.erase(selection_base_ - count, count); + selection_base_ -= count; selection_extent_ = selection_base_; return true; } - return false; // No edits happened. + return false; } bool TextInputModel::Delete() { + // If there's a selection, delete it. if (selection_base_ != selection_extent_) { DeleteSelected(); return true; } - if (selection_base_ != text_.end()) { - int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1; - selection_base_ = text_.erase(selection_base_, selection_base_ + count); + // There's no selection; delete the following codepoint. + if (selection_base_ != text_.length()) { + int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1; + text_.erase(selection_base_, count); selection_extent_ = selection_base_; return true; } @@ -120,32 +126,32 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { for (int i = 0; i < -offset_from_cursor; i++) { // If requested start is before the available text then reduce the // number of characters to delete. - if (start == text_.begin()) { + if (start == 0) { count = i; break; } - start -= IsTrailingSurrogate(*(start - 1)) ? 2 : 1; + start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1; } } else { - for (int i = 0; i < offset_from_cursor && start != text_.end(); i++) { - start += IsLeadingSurrogate(*start) ? 2 : 1; + for (int i = 0; i < offset_from_cursor && start != text_.length(); i++) { + start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } } auto end = start; - for (int i = 0; i < count && end != text_.end(); i++) { - end += IsLeadingSurrogate(*start) ? 2 : 1; + for (int i = 0; i < count && end != text_.length(); i++) { + end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } if (start == end) { return false; } - auto new_base = text_.erase(start, end); + text_.erase(start, end - start); // Cursor moves only if deleted area is before it. if (offset_from_cursor <= 0) { - selection_base_ = new_base; + selection_base_ = start; } // Clear selection. @@ -155,22 +161,21 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { } bool TextInputModel::MoveCursorToBeginning() { - if (selection_base_ == text_.begin() && selection_extent_ == text_.begin()) + if (selection_base_ == 0 && selection_extent_ == 0) return false; - selection_base_ = text_.begin(); - selection_extent_ = text_.begin(); - + selection_base_ = 0; + selection_extent_ = 0; return true; } bool TextInputModel::MoveCursorToEnd() { - if (selection_base_ == text_.end() && selection_extent_ == text_.end()) + auto max_pos = text_.length(); + if (selection_base_ == max_pos && selection_extent_ == max_pos) return false; - selection_base_ = text_.end(); - selection_extent_ = text_.end(); - + selection_base_ = max_pos; + selection_extent_ = max_pos; return true; } @@ -182,8 +187,8 @@ bool TextInputModel::MoveCursorForward() { return true; } // If not at the end, move the extent forward. - if (selection_extent_ != text_.end()) { - int count = IsLeadingSurrogate(*selection_base_) ? 2 : 1; + if (selection_extent_ != text_.length()) { + int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1; selection_base_ += count; selection_extent_ = selection_base_; return true; @@ -200,8 +205,8 @@ bool TextInputModel::MoveCursorBack() { return true; } // If not at the start, move the beginning backward. - if (selection_base_ != text_.begin()) { - int count = IsTrailingSurrogate(*(selection_base_ - 1)) ? 2 : 1; + if (selection_base_ != 0) { + int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1; selection_base_ -= count; selection_extent_ = selection_base_; return true; @@ -218,7 +223,7 @@ std::string TextInputModel::GetText() const { int TextInputModel::GetCursorOffset() const { // Measure the length of the current text up to the cursor. // There is probably a much more efficient way of doing this. - auto leading_text = text_.substr(0, selection_extent_ - text_.begin()); + auto leading_text = text_.substr(0, selection_extent_); std::wstring_convert, char16_t> utf8_converter; return utf8_converter.to_bytes(leading_text).size(); diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index 2de5379551061..27aef63cdd1c6 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_ #define FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_ +#include #include #include @@ -103,32 +104,26 @@ class TextInputModel { int GetCursorOffset() const; // The position where the selection starts. - int selection_base() const { - return static_cast(selection_base_ - text_.begin()); - } + int selection_base() const { return selection_base_; } // The position of the cursor. - int selection_extent() const { - return static_cast(selection_extent_ - text_.begin()); - } + int selection_extent() const { return selection_extent_; } private: void DeleteSelected(); std::u16string text_; - std::u16string::iterator selection_base_; - std::u16string::iterator selection_extent_; + size_t selection_base_ = 0; + size_t selection_extent_ = 0; // Returns the left hand side of the selection. - std::u16string::iterator selection_start() { - return selection_base_ < selection_extent_ ? selection_base_ - : selection_extent_; + size_t selection_start() { + return std::min(selection_base_, selection_extent_); } // Returns the right hand side of the selection. - std::u16string::iterator selection_end() { - return selection_base_ > selection_extent_ ? selection_base_ - : selection_extent_; + size_t selection_end() { + return std::max(selection_base_, selection_extent_); } }; From 3905b9ec39ae63fee9ad16fed70350c1532f52fb Mon Sep 17 00:00:00 2001 From: Dan Field Date: Wed, 7 Oct 2020 21:42:20 -0700 Subject: [PATCH 011/219] Revert "[web] Support custom url strategies (#19134)" (#21687) This reverts commit 02324994a3f44a2777ade96b3d69aa61901fb9b4. --- ci/licenses_golden/licenses_flutter | 5 +- lib/web_ui/lib/src/engine.dart | 5 +- .../lib/src/engine/browser_location.dart | 211 +++++++++++++ .../src/engine/{navigation => }/history.dart | 213 ++++++------- .../engine/navigation/js_url_strategy.dart | 78 ----- .../src/engine/navigation/url_strategy.dart | 296 ------------------ lib/web_ui/lib/src/engine/test_embedding.dart | 26 +- lib/web_ui/lib/src/engine/window.dart | 202 ++++++------ lib/web_ui/lib/src/ui/initialization.dart | 4 + lib/web_ui/test/engine/history_test.dart | 127 +++----- lib/web_ui/test/engine/navigation_test.dart | 14 +- lib/web_ui/test/window_test.dart | 92 +++--- 12 files changed, 533 insertions(+), 740 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/browser_location.dart rename lib/web_ui/lib/src/engine/{navigation => }/history.dart (69%) delete mode 100644 lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart delete mode 100644 lib/web_ui/lib/src/engine/navigation/url_strategy.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 72bfafac4c963..ed74366279e94 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -426,6 +426,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvas_pool.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -462,9 +463,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/history.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/js_url_strategy.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/url_strategy.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/clip.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index dcb19c8a3165f..d8c01373840f8 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -26,6 +26,7 @@ part 'engine/alarm_clock.dart'; part 'engine/assets.dart'; part 'engine/bitmap_canvas.dart'; part 'engine/browser_detection.dart'; +part 'engine/browser_location.dart'; part 'engine/canvaskit/canvas.dart'; part 'engine/canvaskit/canvaskit_canvas.dart'; part 'engine/canvaskit/canvaskit_api.dart'; @@ -62,9 +63,7 @@ part 'engine/dom_canvas.dart'; part 'engine/dom_renderer.dart'; part 'engine/engine_canvas.dart'; part 'engine/frame_reference.dart'; -part 'engine/navigation/history.dart'; -part 'engine/navigation/js_url_strategy.dart'; -part 'engine/navigation/url_strategy.dart'; +part 'engine/history.dart'; part 'engine/html/backdrop_filter.dart'; part 'engine/html/canvas.dart'; part 'engine/html/clip.dart'; diff --git a/lib/web_ui/lib/src/engine/browser_location.dart b/lib/web_ui/lib/src/engine/browser_location.dart new file mode 100644 index 0000000000000..a9701cd99060f --- /dev/null +++ b/lib/web_ui/lib/src/engine/browser_location.dart @@ -0,0 +1,211 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +// TODO(mdebbar): add other strategies. + +// Some parts of this file were inspired/copied from the AngularDart router. + +/// [LocationStrategy] is responsible for representing and reading route state +/// from the browser's URL. +/// +/// At the moment, only one strategy is implemented: [HashLocationStrategy]. +/// +/// This is used by [BrowserHistory] to interact with browser history APIs. +abstract class LocationStrategy { + const LocationStrategy(); + + /// Subscribes to popstate events and returns a function that could be used to + /// unsubscribe from popstate events. + ui.VoidCallback onPopState(html.EventListener fn); + + /// The active path in the browser history. + String get path; + + /// The state of the current browser history entry. + dynamic get state; + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + String prepareExternalUrl(String internalUrl); + + /// Push a new history entry. + void pushState(dynamic state, String title, String url); + + /// Replace the currently active history entry. + void replaceState(dynamic state, String title, String url); + + /// Go to the previous history entry. + Future back({int count = 1}); +} + +/// This is an implementation of [LocationStrategy] that uses the browser URL's +/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) +/// to represent its state. +/// +/// In order to use this [LocationStrategy] for an app, it needs to be set in +/// [ui.window.locationStrategy]: +/// +/// ```dart +/// import 'package:flutter_web/material.dart'; +/// import 'package:flutter_web/ui.dart' as ui; +/// +/// void main() { +/// ui.window.locationStrategy = const ui.HashLocationStrategy(); +/// runApp(MyApp()); +/// } +/// ``` +class HashLocationStrategy extends LocationStrategy { + final PlatformLocation _platformLocation; + + const HashLocationStrategy( + [this._platformLocation = const BrowserPlatformLocation()]); + + @override + ui.VoidCallback onPopState(html.EventListener fn) { + _platformLocation.onPopState(fn); + return () => _platformLocation.offPopState(fn); + } + + @override + String get path { + // the hash value is always prefixed with a `#` + // and if it is empty then it will stay empty + String path = _platformLocation.hash ?? ''; + assert(path.isEmpty || path.startsWith('#')); + + // We don't want to return an empty string as a path. Instead we default to "/". + if (path.isEmpty || path == '#') { + return '/'; + } + // At this point, we know [path] starts with "#" and isn't empty. + return path.substring(1); + } + + @override + dynamic get state => _platformLocation.state; + + @override + String prepareExternalUrl(String internalUrl) { + // It's convention that if the hash path is empty, we omit the `#`; however, + // if the empty URL is pushed it won't replace any existing fragment. So + // when the hash path is empty, we instead return the location's path and + // query. + return internalUrl.isEmpty + ? '${_platformLocation.pathname}${_platformLocation.search}' + : '#$internalUrl'; + } + + @override + void pushState(dynamic state, String title, String url) { + _platformLocation.pushState(state, title, prepareExternalUrl(url)); + } + + @override + void replaceState(dynamic state, String title, String url) { + _platformLocation.replaceState(state, title, prepareExternalUrl(url)); + } + + @override + Future back({int count = 1}) { + _platformLocation.back(count); + return _waitForPopState(); + } + + /// Waits until the next popstate event is fired. + /// + /// This is useful for example to wait until the browser has handled the + /// `history.back` transition. + Future _waitForPopState() { + final Completer completer = Completer(); + late ui.VoidCallback unsubscribe; + unsubscribe = onPopState((_) { + unsubscribe(); + completer.complete(); + }); + return completer.future; + } +} + +/// [PlatformLocation] encapsulates all calls to DOM apis, which allows the +/// [LocationStrategy] classes to be platform agnostic and testable. +/// +/// The [PlatformLocation] class is used directly by all implementations of +/// [LocationStrategy] when they need to interact with the DOM apis like +/// pushState, popState, etc... +abstract class PlatformLocation { + const PlatformLocation(); + + void onPopState(html.EventListener fn); + void offPopState(html.EventListener fn); + + void onHashChange(html.EventListener fn); + void offHashChange(html.EventListener fn); + + String get pathname; + String get search; + String? get hash; + dynamic get state; + + void pushState(dynamic state, String title, String url); + void replaceState(dynamic state, String title, String url); + void back(int count); +} + +/// An implementation of [PlatformLocation] for the browser. +class BrowserPlatformLocation extends PlatformLocation { + html.Location get _location => html.window.location; + html.History get _history => html.window.history; + + const BrowserPlatformLocation(); + + @override + void onPopState(html.EventListener fn) { + html.window.addEventListener('popstate', fn); + } + + @override + void offPopState(html.EventListener fn) { + html.window.removeEventListener('popstate', fn); + } + + @override + void onHashChange(html.EventListener fn) { + html.window.addEventListener('hashchange', fn); + } + + @override + void offHashChange(html.EventListener fn) { + html.window.removeEventListener('hashchange', fn); + } + + @override + String get pathname => _location.pathname!; + + @override + String get search => _location.search!; + + @override + String get hash => _location.hash; + + @override + dynamic get state => _history.state; + + @override + void pushState(dynamic state, String title, String url) { + _history.pushState(state, title, url); + } + + @override + void replaceState(dynamic state, String title, String url) { + _history.replaceState(state, title, url); + } + + @override + void back(int count) { + _history.go(-count); + } +} diff --git a/lib/web_ui/lib/src/engine/navigation/history.dart b/lib/web_ui/lib/src/engine/history.dart similarity index 69% rename from lib/web_ui/lib/src/engine/navigation/history.dart rename to lib/web_ui/lib/src/engine/history.dart index 0a578162a9096..59e1ba5fddf6d 100644 --- a/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/lib/web_ui/lib/src/engine/history.dart @@ -25,39 +25,64 @@ abstract class BrowserHistory { late ui.VoidCallback _unsubscribe; /// The strategy to interact with html browser history. - UrlStrategy? get urlStrategy; + LocationStrategy? get locationStrategy => _locationStrategy; + LocationStrategy? _locationStrategy; + /// Updates the strategy. + /// + /// This method will also remove any previous modifications to the html + /// browser history and start anew. + Future setLocationStrategy(LocationStrategy? strategy) async { + if (strategy != _locationStrategy) { + await _tearoffStrategy(_locationStrategy); + _locationStrategy = strategy; + await _setupStrategy(_locationStrategy); + } + } - bool _isDisposed = false; + Future _setupStrategy(LocationStrategy? strategy) async { + if (strategy == null) { + return; + } + _unsubscribe = strategy.onPopState(onPopState as dynamic Function(html.Event)); + await setup(); + } - void _setupStrategy(UrlStrategy strategy) { - _unsubscribe = strategy.addPopStateListener( - onPopState as html.EventListener, - ); + Future _tearoffStrategy(LocationStrategy? strategy) async { + if (strategy == null) { + return; + } + _unsubscribe(); + + await tearDown(); } /// Exit this application and return to the previous page. Future exit() async { - if (urlStrategy != null) { - await tearDown(); + if (_locationStrategy != null) { + await _tearoffStrategy(_locationStrategy); // Now the history should be in the original state, back one more time to // exit the application. - await urlStrategy!.go(-1); + await _locationStrategy!.back(); + _locationStrategy = null; } } /// This method does the same thing as the browser back button. - Future back() async { - return urlStrategy?.go(-1); + Future back() { + if (locationStrategy != null) { + return locationStrategy!.back(); + } + return Future.value(); } /// The path of the current location of the user's browser. - String get currentPath => urlStrategy?.getPath() ?? '/'; + String get currentPath => locationStrategy?.path ?? '/'; /// The state of the current location of the user's browser. - Object? get currentState => urlStrategy?.getState(); + dynamic get currentState => locationStrategy?.state; /// Update the url with the given [routeName] and [state]. - void setRouteName(String? routeName, {Object? state}); + void setRouteName(String? routeName, {dynamic? state}); /// A callback method to handle browser backward or forward buttons. /// @@ -65,9 +90,12 @@ abstract class BrowserHistory { /// applications accordingly. void onPopState(covariant html.PopStateEvent event); + /// Sets up any prerequisites to use this browser history class. + Future setup() => Future.value(); + /// Restore any modifications to the html browser history during the lifetime /// of this class. - Future tearDown(); + Future tearDown() => Future.value(); } /// A browser history class that creates a set of browser history entries to @@ -85,51 +113,31 @@ abstract class BrowserHistory { /// * [SingleEntryBrowserHistory], which is used when the framework does not use /// a Router for routing. class MultiEntriesBrowserHistory extends BrowserHistory { - MultiEntriesBrowserHistory({required this.urlStrategy}) { - final UrlStrategy? strategy = urlStrategy; - if (strategy == null) { - return; - } - - _setupStrategy(strategy); - if (!_hasSerialCount(currentState)) { - strategy.replaceState( - _tagWithSerialCount(currentState, 0), 'flutter', currentPath); - } - // If we restore from a page refresh, the _currentSerialCount may not be 0. - _lastSeenSerialCount = _currentSerialCount; - } - - @override - final UrlStrategy? urlStrategy; - late int _lastSeenSerialCount; int get _currentSerialCount { if (_hasSerialCount(currentState)) { - final Map stateMap = - currentState as Map; - return stateMap['serialCount'] as int; + return currentState['serialCount'] as int; } return 0; } - Object _tagWithSerialCount(Object? originialState, int count) { - return { + dynamic _tagWithSerialCount(dynamic originialState, int count) { + return { 'serialCount': count, 'state': originialState, }; } - bool _hasSerialCount(Object? state) { + bool _hasSerialCount(dynamic state) { return state is Map && state['serialCount'] != null; } @override - void setRouteName(String? routeName, {Object? state}) { - if (urlStrategy != null) { + void setRouteName(String? routeName, {dynamic? state}) { + if (locationStrategy != null) { assert(routeName != null); _lastSeenSerialCount += 1; - urlStrategy!.pushState( + locationStrategy!.pushState( _tagWithSerialCount(state, _lastSeenSerialCount), 'flutter', routeName!, @@ -139,51 +147,58 @@ class MultiEntriesBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { - assert(urlStrategy != null); + assert(locationStrategy != null); // May be a result of direct url access while the flutter application is // already running. if (!_hasSerialCount(event.state)) { // In this case we assume this will be the next history entry from the // last seen entry. - urlStrategy!.replaceState( - _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), - 'flutter', - currentPath); + locationStrategy!.replaceState( + _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), + 'flutter', + currentPath); } _lastSeenSerialCount = _currentSerialCount; if (window._onPlatformMessage != null) { window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( - MethodCall('pushRouteInformation', { - 'location': currentPath, - 'state': event.state?['state'], - })), + MethodCall('pushRouteInformation', { + 'location': currentPath, + 'state': event.state?['state'], + }) + ), (_) {}, ); } } @override - Future tearDown() async { - if (_isDisposed || urlStrategy == null) { - return; + Future setup() { + if (!_hasSerialCount(currentState)) { + locationStrategy!.replaceState( + _tagWithSerialCount(currentState, 0), + 'flutter', + currentPath + ); } - _isDisposed = true; - _unsubscribe(); + // If we retore from a page refresh, the _currentSerialCount may not be 0. + _lastSeenSerialCount = _currentSerialCount; + return Future.value(); + } + @override + Future tearDown() async { // Restores the html browser history. assert(_hasSerialCount(currentState)); int backCount = _currentSerialCount; if (backCount > 0) { - await urlStrategy!.go(-backCount); + await locationStrategy!.back(count: backCount); } // Unwrap state. assert(_hasSerialCount(currentState) && _currentSerialCount == 0); - final Map stateMap = - currentState as Map; - urlStrategy!.replaceState( - stateMap['state'], + locationStrategy!.replaceState( + currentState['state'], 'flutter', currentPath, ); @@ -207,61 +222,37 @@ class MultiEntriesBrowserHistory extends BrowserHistory { /// * [MultiEntriesBrowserHistory], which is used when the framework uses a /// Router for routing. class SingleEntryBrowserHistory extends BrowserHistory { - SingleEntryBrowserHistory({required this.urlStrategy}) { - final UrlStrategy? strategy = urlStrategy; - if (strategy == null) { - return; - } - - _setupStrategy(strategy); - - final String path = currentPath; - if (!_isFlutterEntry(html.window.history.state)) { - // An entry may not have come from Flutter, for example, when the user - // refreshes the page. They land directly on the "flutter" entry, so - // there's no need to setup the "origin" and "flutter" entries, we can - // safely assume they are already setup. - _setupOriginEntry(strategy); - _setupFlutterEntry(strategy, replace: false, path: path); - } - } - - @override - final UrlStrategy? urlStrategy; - static const MethodCall _popRouteMethodCall = MethodCall('popRoute'); static const String _kFlutterTag = 'flutter'; static const String _kOriginTag = 'origin'; - Map _wrapOriginState(Object? state) { + Map _wrapOriginState(dynamic state) { return {_kOriginTag: true, 'state': state}; } - - Object? _unwrapOriginState(Object? state) { + dynamic _unwrapOriginState(dynamic state) { assert(_isOriginEntry(state)); final Map originState = state as Map; return originState['state']; } - Map _flutterState = {_kFlutterTag: true}; /// The origin entry is the history entry that the Flutter app landed on. It's /// created by the browser when the user navigates to the url of the app. - bool _isOriginEntry(Object? state) { + bool _isOriginEntry(dynamic state) { return state is Map && state[_kOriginTag] == true; } /// The flutter entry is a history entry that we maintain on top of the origin /// entry. It allows us to catch popstate events when the user hits the back /// button. - bool _isFlutterEntry(Object? state) { + bool _isFlutterEntry(dynamic state) { return state is Map && state[_kFlutterTag] == true; } @override - void setRouteName(String? routeName, {Object? state}) { - if (urlStrategy != null) { - _setupFlutterEntry(urlStrategy!, replace: true, path: routeName); + void setRouteName(String? routeName, {dynamic? state}) { + if (locationStrategy != null) { + _setupFlutterEntry(locationStrategy!, replace: true, path: routeName); } } @@ -269,7 +260,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { if (_isOriginEntry(event.state)) { - _setupFlutterEntry(urlStrategy!); + _setupFlutterEntry(_locationStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. if (window._onPlatformMessage != null) { @@ -311,14 +302,14 @@ class SingleEntryBrowserHistory extends BrowserHistory { // 2. Then we remove the new entry. // This will take us back to our "flutter" entry and it causes a new // popstate event that will be handled in the "else if" section above. - urlStrategy!.go(-1); + _locationStrategy!.back(); } } /// This method should be called when the Origin Entry is active. It just /// replaces the state of the entry so that we can recognize it later using /// [_isOriginEntry] inside [_popStateListener]. - void _setupOriginEntry(UrlStrategy strategy) { + void _setupOriginEntry(LocationStrategy strategy) { assert(strategy != null); // ignore: unnecessary_null_comparison strategy.replaceState(_wrapOriginState(currentState), 'origin', ''); } @@ -326,7 +317,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { /// This method is used manipulate the Flutter Entry which is always the /// active entry while the Flutter app is running. void _setupFlutterEntry( - UrlStrategy strategy, { + LocationStrategy strategy, { bool replace = false, String? path, }) { @@ -340,17 +331,27 @@ class SingleEntryBrowserHistory extends BrowserHistory { } @override - Future tearDown() async { - if (_isDisposed || urlStrategy == null) { - return; + Future setup() { + final String path = currentPath; + if (_isFlutterEntry(html.window.history.state)) { + // This could happen if the user, for example, refreshes the page. They + // will land directly on the "flutter" entry, so there's no need to setup + // the "origin" and "flutter" entries, we can safely assume they are + // already setup. + } else { + _setupOriginEntry(locationStrategy!); + _setupFlutterEntry(locationStrategy!, replace: false, path: path); } - _isDisposed = true; - _unsubscribe(); + return Future.value(); + } - // We need to remove the flutter entry that we pushed in setup. - await urlStrategy!.go(-1); - // Restores original state. - urlStrategy! - .replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); + @override + Future tearDown() async { + if (locationStrategy != null) { + // We need to remove the flutter entry that we pushed in setup. + await locationStrategy!.back(); + // Restores original state. + locationStrategy!.replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); + } } } diff --git a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart deleted file mode 100644 index decb7c249d44d..0000000000000 --- a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of engine; - -typedef _PathGetter = String Function(); - -typedef _StateGetter = Object? Function(); - -typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener); - -typedef _StringToString = String Function(String); - -typedef _StateOperation = void Function( - Object? state, String title, String url); - -typedef _HistoryMove = Future Function(int count); - -/// The JavaScript representation of a URL strategy. -/// -/// This is used to pass URL strategy implementations across a JS-interop -/// bridge from the app to the engine. -@JS() -@anonymous -abstract class JsUrlStrategy { - /// Creates an instance of [JsUrlStrategy] from a bag of URL strategy - /// functions. - external factory JsUrlStrategy({ - required _PathGetter getPath, - required _StateGetter getState, - required _AddPopStateListener addPopStateListener, - required _StringToString prepareExternalUrl, - required _StateOperation pushState, - required _StateOperation replaceState, - required _HistoryMove go, - }); - - /// Adds a listener to the `popstate` event and returns a function that, when - /// invoked, removes the listener. - external ui.VoidCallback addPopStateListener(html.EventListener fn); - - /// Returns the active path in the browser. - external String getPath(); - - /// Returns the history state in the browser. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state - external Object? getState(); - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - external String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState - external void pushState(Object? state, String title, String url); - - /// Replace the currently active history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - external void replaceState(Object? state, String title, String url); - - /// Moves forwards or backwards through the history stack. - /// - /// A negative [count] value causes a backward move in the history stack. And - /// a positive [count] value causs a forward move. - /// - /// Examples: - /// - /// * `go(-2)` moves back 2 steps in history. - /// * `go(3)` moves forward 3 steps in hisotry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go - external Future go(int count); -} diff --git a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart deleted file mode 100644 index fcf2cecfd0b8e..0000000000000 --- a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of engine; - -/// Represents and reads route state from the browser's URL. -/// -/// By default, the [HashUrlStrategy] subclass is used if the app doesn't -/// specify one. -abstract class UrlStrategy { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const UrlStrategy(); - - /// Adds a listener to the `popstate` event and returns a function that, when - /// invoked, removes the listener. - ui.VoidCallback addPopStateListener(html.EventListener fn); - - /// Returns the active path in the browser. - String getPath(); - - /// The state of the current browser history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state - Object? getState(); - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState - void pushState(Object? state, String title, String url); - - /// Replace the currently active history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - void replaceState(Object? state, String title, String url); - - /// Moves forwards or backwards through the history stack. - /// - /// A negative [count] value causes a backward move in the history stack. And - /// a positive [count] value causs a forward move. - /// - /// Examples: - /// - /// * `go(-2)` moves back 2 steps in history. - /// * `go(3)` moves forward 3 steps in hisotry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go - Future go(int count); -} - -/// This is an implementation of [UrlStrategy] that uses the browser URL's -/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) -/// to represent its state. -/// -/// In order to use this [UrlStrategy] for an app, it needs to be set like this: -/// -/// ```dart -/// import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -/// -/// // Somewhere before calling `runApp()` do: -/// setUrlStrategy(const HashUrlStrategy()); -/// ``` -class HashUrlStrategy extends UrlStrategy { - /// Creates an instance of [HashUrlStrategy]. - /// - /// The [PlatformLocation] parameter is useful for testing to mock out browser - /// interations. - const HashUrlStrategy( - [this._platformLocation = const BrowserPlatformLocation()]); - - final PlatformLocation _platformLocation; - - @override - ui.VoidCallback addPopStateListener(html.EventListener fn) { - _platformLocation.addPopStateListener(fn); - return () => _platformLocation.removePopStateListener(fn); - } - - @override - String getPath() { - // the hash value is always prefixed with a `#` - // and if it is empty then it will stay empty - final String path = _platformLocation.hash ?? ''; - assert(path.isEmpty || path.startsWith('#')); - - // We don't want to return an empty string as a path. Instead we default to "/". - if (path.isEmpty || path == '#') { - return '/'; - } - // At this point, we know [path] starts with "#" and isn't empty. - return path.substring(1); - } - - @override - Object? getState() => _platformLocation.state; - - @override - String prepareExternalUrl(String internalUrl) { - // It's convention that if the hash path is empty, we omit the `#`; however, - // if the empty URL is pushed it won't replace any existing fragment. So - // when the hash path is empty, we instead return the location's path and - // query. - return internalUrl.isEmpty - ? '${_platformLocation.pathname}${_platformLocation.search}' - : '#$internalUrl'; - } - - @override - void pushState(Object? state, String title, String url) { - _platformLocation.pushState(state, title, prepareExternalUrl(url)); - } - - @override - void replaceState(Object? state, String title, String url) { - _platformLocation.replaceState(state, title, prepareExternalUrl(url)); - } - - @override - Future go(int count) { - _platformLocation.go(count); - return _waitForPopState(); - } - - /// Waits until the next popstate event is fired. - /// - /// This is useful, for example, to wait until the browser has handled the - /// `history.back` transition. - Future _waitForPopState() { - final Completer completer = Completer(); - late ui.VoidCallback unsubscribe; - unsubscribe = addPopStateListener((_) { - unsubscribe(); - completer.complete(); - }); - return completer.future; - } -} - -/// Wraps a custom implementation of [UrlStrategy] that was previously converted -/// to a [JsUrlStrategy]. -class CustomUrlStrategy extends UrlStrategy { - /// Wraps the [delegate] in a [CustomUrlStrategy] instance. - CustomUrlStrategy.fromJs(this.delegate); - - final JsUrlStrategy delegate; - - @override - ui.VoidCallback addPopStateListener(html.EventListener fn) => - delegate.addPopStateListener(fn); - - @override - String getPath() => delegate.getPath(); - - @override - Object? getState() => delegate.getState(); - - @override - String prepareExternalUrl(String internalUrl) => - delegate.prepareExternalUrl(internalUrl); - - @override - void pushState(Object? state, String title, String url) => - delegate.pushState(state, title, url); - - @override - void replaceState(Object? state, String title, String url) => - delegate.replaceState(state, title, url); - - @override - Future go(int count) => delegate.go(count); -} - -/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes -/// to be platform agnostic and testable. -/// -/// For convenience, the [PlatformLocation] class can be used by implementations -/// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc. -abstract class PlatformLocation { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const PlatformLocation(); - - /// Registers an event listener for the `popstate` event. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate - void addPopStateListener(html.EventListener fn); - - /// Unregisters the given listener (added by [addPopStateListener]) from the - /// `popstate` event. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate - void removePopStateListener(html.EventListener fn); - - /// The `pathname` part of the URL in the browser address bar. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname - String get pathname; - - /// The `query` part of the URL in the browser address bar. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/search - String get search; - - /// The `hash]` part of the URL in the browser address bar. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/hash - String? get hash; - - /// The `state` in the current history entry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state - Object? get state; - - /// Adds a new entry to the browser history stack. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState - void pushState(Object? state, String title, String url); - - /// Replaces the current entry in the browser history stack. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - void replaceState(Object? state, String title, String url); - - /// Moves forwards or backwards through the history stack. - /// - /// A negative [count] value causes a backward move in the history stack. And - /// a positive [count] value causs a forward move. - /// - /// Examples: - /// - /// * `go(-2)` moves back 2 steps in history. - /// * `go(3)` moves forward 3 steps in hisotry. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go - void go(int count); - - /// The base href where the Flutter app is being served. - /// - /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base - String? getBaseHref(); -} - -/// Delegates to real browser APIs to provide platform location functionality. -class BrowserPlatformLocation extends PlatformLocation { - /// Default constructor for [BrowserPlatformLocation]. - const BrowserPlatformLocation(); - - html.Location get _location => html.window.location; - html.History get _history => html.window.history; - - @override - void addPopStateListener(html.EventListener fn) { - html.window.addEventListener('popstate', fn); - } - - @override - void removePopStateListener(html.EventListener fn) { - html.window.removeEventListener('popstate', fn); - } - - @override - String get pathname => _location.pathname!; - - @override - String get search => _location.search!; - - @override - String get hash => _location.hash; - - @override - Object? get state => _history.state; - - @override - void pushState(Object? state, String title, String url) { - _history.pushState(state, title, url); - } - - @override - void replaceState(Object? state, String title, String url) { - _history.replaceState(state, title, url); - } - - @override - void go(int count) { - _history.go(count); - } - - @override - String? getBaseHref() => html.document.baseUri; -} diff --git a/lib/web_ui/lib/src/engine/test_embedding.dart b/lib/web_ui/lib/src/engine/test_embedding.dart index 0255e5fb19601..f0d3a4291dbad 100644 --- a/lib/web_ui/lib/src/engine/test_embedding.dart +++ b/lib/web_ui/lib/src/engine/test_embedding.dart @@ -20,27 +20,29 @@ class TestHistoryEntry { } } -/// This URL strategy mimics the browser's history as closely as possible +/// This location strategy mimics the browser's history as closely as possible /// while doing it all in memory with no interaction with the browser. /// /// It keeps a list of history entries and event listeners in memory and /// manipulates them in order to achieve the desired functionality. -class TestUrlStrategy extends UrlStrategy { - /// Creates a instance of [TestUrlStrategy] with an empty string as the +class TestLocationStrategy extends LocationStrategy { + /// Creates a instance of [TestLocationStrategy] with an empty string as the /// path. - factory TestUrlStrategy() => TestUrlStrategy.fromEntry(TestHistoryEntry(null, null, '')); + factory TestLocationStrategy() => TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '')); - /// Creates an instance of [TestUrlStrategy] and populates it with a list + /// Creates an instance of [TestLocationStrategy] and populates it with a list /// that has [initialEntry] as the only item. - TestUrlStrategy.fromEntry(TestHistoryEntry initialEntry) + TestLocationStrategy.fromEntry(TestHistoryEntry initialEntry) : _currentEntryIndex = 0, history = [initialEntry]; @override - String getPath() => currentEntry.url; + String get path => currentEntry.url; @override - dynamic getState() => currentEntry.state; + dynamic get state { + return currentEntry.state; + } int _currentEntryIndex; int get currentEntryIndex => _currentEntryIndex; @@ -103,12 +105,12 @@ class TestUrlStrategy extends UrlStrategy { } @override - Future go(int count) { + Future back({int count = 1}) { assert(withinAppHistory); - // Browsers don't move in history immediately. They do it at the next + // Browsers don't move back in history immediately. They do it at the next // event loop. So let's simulate that. return _nextEventLoop(() { - _currentEntryIndex = _currentEntryIndex + count; + _currentEntryIndex = _currentEntryIndex - count; if (withinAppHistory) { _firePopStateEvent(); } @@ -122,7 +124,7 @@ class TestUrlStrategy extends UrlStrategy { final List listeners = []; @override - ui.VoidCallback addPopStateListener(html.EventListener fn) { + ui.VoidCallback onPopState(html.EventListener fn) { listeners.add(fn); return () { // Schedule a micro task here to avoid removing the listener during diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 757d85ee4151e..94d73f6c7d717 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -13,27 +13,20 @@ const bool _debugPrintPlatformMessages = false; /// This may be overridden in tests, for example, to pump fake frames. ui.VoidCallback? scheduleFrameCallback; -typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); - -/// A JavaScript hook to customize the URL strategy of a Flutter app. -// -// Keep this js name in sync with flutter_web_plugins. Find it at: -// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart -// -// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 -@JS('_flutter_web_set_location_strategy') -external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); - /// The Web implementation of [ui.Window]. class EngineWindow extends ui.Window { EngineWindow() { _addBrightnessMediaQueryListener(); - _addUrlStrategyListener(); + js.context['_flutter_web_set_location_strategy'] = (LocationStrategy strategy) { + locationStrategy = strategy; + }; + registerHotRestartListener(() { + js.context['_flutter_web_set_location_strategy'] = null; + }); } @override - double get devicePixelRatio => - _debugDevicePixelRatio ?? browserDevicePixelRatio; + double get devicePixelRatio => _debugDevicePixelRatio ?? browserDevicePixelRatio; /// Returns device pixel ratio returned by browser. static double get browserDevicePixelRatio { @@ -124,8 +117,7 @@ class EngineWindow extends ui.Window { double height = 0; double width = 0; if (html.window.visualViewport != null) { - height = - html.window.visualViewport!.height!.toDouble() * devicePixelRatio; + height = html.window.visualViewport!.height!.toDouble() * devicePixelRatio; width = html.window.visualViewport!.width!.toDouble() * devicePixelRatio; } else { height = html.window.innerHeight! * devicePixelRatio; @@ -134,7 +126,7 @@ class EngineWindow extends ui.Window { // This method compares the new dimensions with the previous ones. // Return false if the previous dimensions are not set. - if (_physicalSize != null) { + if(_physicalSize != null) { // First confirm both height and width are effected. if (_physicalSize!.height != height && _physicalSize!.width != width) { // If prior to rotation height is bigger than width it should be the @@ -162,41 +154,78 @@ class EngineWindow extends ui.Window { /// Handles the browser history integration to allow users to use the back /// button, etc. @visibleForTesting - BrowserHistory get browserHistory { - return _browserHistory ??= - MultiEntriesBrowserHistory(urlStrategy: const HashUrlStrategy()); - } + BrowserHistory get browserHistory => _browserHistory; + BrowserHistory _browserHistory = MultiEntriesBrowserHistory(); - BrowserHistory? _browserHistory; + @visibleForTesting + Future debugSwitchBrowserHistory({required bool useSingle}) async { + if (useSingle) + await _useSingleEntryBrowserHistory(); + else + await _useMultiEntryBrowserHistory(); + } + + /// This function should only be used for test setup. In real application, we + /// only allow one time switch from the MultiEntriesBrowserHistory to + /// the SingleEntryBrowserHistory to prevent the application to switch back + /// forth between router and non-router. + Future _useMultiEntryBrowserHistory() async { + if (_browserHistory is MultiEntriesBrowserHistory) { + return; + } + final LocationStrategy? strategy = _browserHistory.locationStrategy; + if (strategy != null) + await _browserHistory.setLocationStrategy(null); + _browserHistory = MultiEntriesBrowserHistory(); + if (strategy != null) + await _browserHistory.setLocationStrategy(strategy); + } Future _useSingleEntryBrowserHistory() async { if (_browserHistory is SingleEntryBrowserHistory) { return; } - final UrlStrategy? strategy = _browserHistory?.urlStrategy; - await _browserHistory?.tearDown(); - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + final LocationStrategy? strategy = _browserHistory.locationStrategy; + if (strategy != null) + await _browserHistory.setLocationStrategy(null); + _browserHistory = SingleEntryBrowserHistory(); + if (strategy != null) + await _browserHistory.setLocationStrategy(strategy); } + /// Simulates clicking the browser's back button. + Future webOnlyBack() => _browserHistory.back(); + /// Lazily initialized when the `defaultRouteName` getter is invoked. /// - /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] + /// The reason for the lazy initialization is to give enough time for the app to set [locationStrategy] /// in `lib/src/ui/initialization.dart`. String? _defaultRouteName; @override - String get defaultRouteName { - return _defaultRouteName ??= browserHistory.currentPath; - } + String get defaultRouteName => _defaultRouteName ??= _browserHistory.currentPath; @override void scheduleFrame() { if (scheduleFrameCallback == null) { - throw new Exception('scheduleFrameCallback must be initialized first.'); + throw new Exception( + 'scheduleFrameCallback must be initialized first.'); } scheduleFrameCallback!(); } + /// Change the strategy to use for handling browser history location. + /// Setting this member will automatically update [_browserHistory]. + /// + /// By setting this to null, the browser history will be disabled. + set locationStrategy(LocationStrategy? strategy) { + _browserHistory.setLocationStrategy(strategy); + } + + /// Returns the currently active location strategy. + @visibleForTesting + LocationStrategy? get locationStrategy => _browserHistory.locationStrategy; + @override ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; ui.VoidCallback? _onTextScaleFactorChanged; @@ -448,8 +477,8 @@ class EngineWindow extends ui.Window { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. - void invokeOnPlatformMessage(String name, ByteData? data, - ui.PlatformMessageResponseCallback callback) { + void invokeOnPlatformMessage( + String name, ByteData? data, ui.PlatformMessageResponseCallback callback) { _invoke3( _onPlatformMessage, _onPlatformMessageZone, @@ -471,9 +500,7 @@ class EngineWindow extends ui.Window { /// Wraps the given [callback] in another callback that ensures that the /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? - _zonedPlatformMessageResponseCallback( - ui.PlatformMessageResponseCallback? callback) { + static ui.PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(ui.PlatformMessageResponseCallback? callback) { if (callback == null) { return null; } @@ -537,7 +564,7 @@ class EngineWindow extends ui.Window { final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { case 'SystemNavigator.pop': - browserHistory.exit().then((_) { + _browserHistory.exit().then((_) { _replyToPlatformMessage( callback, codec.encodeSuccessEnvelope(true)); }); @@ -558,8 +585,8 @@ class EngineWindow extends ui.Window { case 'SystemChrome.setPreferredOrientations': final List? arguments = decoded.arguments; domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(success)); + _replyToPlatformMessage(callback, + codec.encodeSuccessEnvelope(success)); }); return; case 'SystemSound.play': @@ -605,8 +632,7 @@ class EngineWindow extends ui.Window { case 'flutter/platform_views': if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder - .handlePlatformViewCall(data, callback); + rasterizer!.surface.viewEmbedder.handlePlatformViewCall(data, callback); } else { ui.handlePlatformViewCall(data!, callback!); } @@ -620,11 +646,27 @@ class EngineWindow extends ui.Window { return; case 'flutter/navigation': - _handleNavigationMessage(data, callback).then((handled) { - if (!handled && callback != null) { - callback(null); - } - }); + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map message = decoded.arguments as Map; + switch (decoded.method) { + case 'routeUpdated': + _useSingleEntryBrowserHistory().then((void data) { + _browserHistory.setRouteName(message['routeName']); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + }); + break; + case 'routeInformationUpdated': + assert(_browserHistory is MultiEntriesBrowserHistory); + _browserHistory.setRouteName( + message['location'], + state: message['state'], + ); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + break; + } // As soon as Flutter starts taking control of the app navigation, we // should reset [_defaultRouteName] to "/" so it doesn't have any // further effect after this point. @@ -643,51 +685,6 @@ class EngineWindow extends ui.Window { _replyToPlatformMessage(callback, null); } - @visibleForTesting - Future debugInitializeHistory( - UrlStrategy? strategy, { - required bool useSingle, - }) async { - await _browserHistory?.tearDown(); - if (useSingle) { - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } else { - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - } - } - - @visibleForTesting - Future debugResetHistory() async { - await _browserHistory?.tearDown(); - _browserHistory = null; - } - - Future _handleNavigationMessage( - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) async { - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map arguments = decoded.arguments; - - switch (decoded.method) { - case 'routeUpdated': - await _useSingleEntryBrowserHistory(); - browserHistory.setRouteName(arguments['routeName']); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - case 'routeInformationUpdated': - assert(browserHistory is MultiEntriesBrowserHistory); - browserHistory.setRouteName( - arguments['location'], - state: arguments['state'], - ); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - } - return false; - } - int _getHapticFeedbackDuration(String? type) { switch (type) { case 'HapticFeedbackType.lightImpact': @@ -749,8 +746,7 @@ class EngineWindow extends ui.Window { : ui.Brightness.light); _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = - event as html.MediaQueryListEvent; + final html.MediaQueryListEvent mqEvent = event as html.MediaQueryListEvent; _updatePlatformBrightness( mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); }; @@ -760,21 +756,6 @@ class EngineWindow extends ui.Window { }); } - void _addUrlStrategyListener() { - _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { - assert( - _browserHistory == null, - 'Cannot set URL strategy more than once.', - ); - final UrlStrategy? strategy = - jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - }); - registerHotRestartListener(() { - _jsSetUrlStrategy = null; - }); - } - /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. void _removeBrightnessMediaQueryListener() { _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); @@ -804,8 +785,7 @@ class EngineWindow extends ui.Window { } @visibleForTesting - late Rasterizer? rasterizer = - experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; + late Rasterizer? rasterizer = experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; } bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { @@ -851,8 +831,8 @@ void _invoke1(void callback(A a)?, Zone? zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, - A1 arg1, A2 arg2, A3 arg3) { +void _invoke3( + void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, A1 arg1, A2 arg2, A3 arg3) { if (callback == null) { return; } diff --git a/lib/web_ui/lib/src/ui/initialization.dart b/lib/web_ui/lib/src/ui/initialization.dart index ca317304ec79b..a7b06b3586def 100644 --- a/lib/web_ui/lib/src/ui/initialization.dart +++ b/lib/web_ui/lib/src/ui/initialization.dart @@ -21,6 +21,10 @@ Future webOnlyInitializePlatform({ Future _initializePlatform({ engine.AssetManager? assetManager, }) async { + if (!debugEmulateFlutterTesterEnvironment) { + engine.window.locationStrategy = const engine.HashLocationStrategy(); + } + engine.initializeEngine(); // This needs to be after `webOnlyInitializeEngine` because that is where the diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index f5be42ae61db7..4c11ed0033636 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -16,6 +16,11 @@ import 'package:ui/src/engine.dart'; import '../spy.dart'; +TestLocationStrategy get strategy => window.browserHistory.locationStrategy; +Future setStrategy(TestLocationStrategy newStrategy) async { + await window.browserHistory.setLocationStrategy(newStrategy); +} + Map _wrapOriginState(dynamic state) { return {'origin': true, 'state': state}; } @@ -43,19 +48,18 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { + await window.debugSwitchBrowserHistory(useSingle: true); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await window.debugResetHistory(); + await setStrategy(null); }); test('basic setup works', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); + await setStrategy(TestLocationStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'))); // There should be two entries: origin and flutter. expect(strategy.history, hasLength(2)); @@ -78,11 +82,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button pops routes correctly', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry(null, null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); - + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(2)); expect(strategy.currentEntry.state, flutterState); @@ -98,7 +98,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.go(-1); + await strategy.back(); // First, the framework should've received a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -115,10 +115,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry(null, null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); await routeUpdated('/page1'); await routeUpdated('/page2'); @@ -130,7 +127,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -146,7 +143,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -164,8 +161,8 @@ void testMain() { // The next browser back will exit the app. We store the strategy locally // because it will be remove from the browser history class once it exits // the app. - TestUrlStrategy originalStrategy = strategy; - await originalStrategy.go(-1); + TestLocationStrategy originalStrategy = strategy; + await originalStrategy.back(); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -184,10 +181,7 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry(null, null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -208,7 +202,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -227,10 +221,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('user types unknown url', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry(null, null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); await strategy.simulateUserTypingUrl('/unknown'); // This delay is necessary to wait for [BrowserHistory] because it @@ -257,19 +248,18 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { + await window.debugSwitchBrowserHistory(useSingle: false); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await window.debugResetHistory(); + await setStrategy(null); }); test('basic setup works', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'), - ); - await window.debugInitializeHistory(strategy, useSingle: false); + await setStrategy(TestLocationStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'))); // There should be only one entry. expect(strategy.history, hasLength(1)); @@ -283,11 +273,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button push route infromation correctly', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: false); - + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); @@ -303,7 +289,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.go(-1); + await strategy.back(); // First, the framework should've received a `pushRouteInformation` // platform message. expect(spy.messages, hasLength(1)); @@ -324,10 +310,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: false); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -339,7 +322,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -355,7 +338,7 @@ void testMain() { expect(strategy.currentEntry.state, _tagStateWithSerialCount('page1 state', 1)); expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -376,10 +359,7 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: false); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -401,7 +381,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -421,10 +401,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('forward button works', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/home'), - ); - await window.debugInitializeHistory(strategy, useSingle: false); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -436,7 +413,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.go(-1); + await strategy.back(); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -453,7 +430,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Forward to page2 - await strategy.go(1); + await strategy.back(count: -1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -473,7 +450,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); }); - group('$HashUrlStrategy', () { + group('$HashLocationStrategy', () { TestPlatformLocation location; setUp(() { @@ -485,26 +462,26 @@ void testMain() { }); test('leading slash is optional', () { - final HashUrlStrategy strategy = HashUrlStrategy(location); + final HashLocationStrategy strategy = HashLocationStrategy(location); location.hash = '#/'; - expect(strategy.getPath(), '/'); + expect(strategy.path, '/'); location.hash = '#/foo'; - expect(strategy.getPath(), '/foo'); + expect(strategy.path, '/foo'); location.hash = '#foo'; - expect(strategy.getPath(), 'foo'); + expect(strategy.path, 'foo'); }); test('path should not be empty', () { - final HashUrlStrategy strategy = HashUrlStrategy(location); + final HashLocationStrategy strategy = HashLocationStrategy(location); location.hash = ''; - expect(strategy.getPath(), '/'); + expect(strategy.path, '/'); location.hash = '#'; - expect(strategy.getPath(), '/'); + expect(strategy.path, '/'); }); }); } @@ -552,31 +529,31 @@ class TestPlatformLocation extends PlatformLocation { String hash; dynamic state; - @override - void addPopStateListener(html.EventListener fn) { + void onPopState(html.EventListener fn) { throw UnimplementedError(); } - @override - void removePopStateListener(html.EventListener fn) { + void offPopState(html.EventListener fn) { + throw UnimplementedError(); + } + + void onHashChange(html.EventListener fn) { + throw UnimplementedError(); + } + + void offHashChange(html.EventListener fn) { throw UnimplementedError(); } - @override void pushState(dynamic state, String title, String url) { throw UnimplementedError(); } - @override void replaceState(dynamic state, String title, String url) { throw UnimplementedError(); } - @override - void go(int count) { + void back(int count) { throw UnimplementedError(); } - - @override - String getBaseHref() => '/'; } diff --git a/lib/web_ui/test/engine/navigation_test.dart b/lib/web_ui/test/engine/navigation_test.dart index 99bae818f54a7..44d3bf2939e95 100644 --- a/lib/web_ui/test/engine/navigation_test.dart +++ b/lib/web_ui/test/engine/navigation_test.dart @@ -10,7 +10,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart' as engine; -engine.TestUrlStrategy _strategy; +engine.TestLocationStrategy _strategy; const engine.MethodCodec codec = engine.JSONMethodCodec(); @@ -21,14 +21,12 @@ void main() { } void testMain() { - setUp(() async { - _strategy = engine.TestUrlStrategy(); - await engine.window.debugInitializeHistory(_strategy, useSingle: true); + setUp(() { + engine.window.locationStrategy = _strategy = engine.TestLocationStrategy(); }); - tearDown(() async { - _strategy = null; - await engine.window.debugResetHistory(); + tearDown(() { + engine.window.locationStrategy = _strategy = null; }); test('Tracks pushed, replaced and popped routes', () async { @@ -42,6 +40,6 @@ void testMain() { (_) => completer.complete(), ); await completer.future; - expect(_strategy.getPath(), '/foo'); + expect(_strategy.path, '/foo'); }); } diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index ef0a755f550cf..b83849bffc8d6 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. // @dart = 2.6 +import 'dart:async'; import 'dart:html' as html; import 'dart:js_util' as js_util; import 'dart:typed_data'; @@ -11,39 +12,34 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import 'engine/history_test.dart'; -import 'matchers.dart'; - const MethodCodec codec = JSONMethodCodec(); -void emptyCallback(ByteData data) {} +void emptyCallback(ByteData date) {} + +Future setStrategy(TestLocationStrategy newStrategy) async { + await window.browserHistory.setLocationStrategy(newStrategy); +} void main() { internalBootstrapBrowserTest(() => testMain); } void testMain() { - tearDown(() async { - await window.debugResetHistory(); + setUp(() async { + await window.debugSwitchBrowserHistory(useSingle: true); }); test('window.defaultRouteName should not change', () async { - final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'), - ); - await window.debugInitializeHistory(strategy, useSingle: true); + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. - strategy.replaceState(null, null, '/newpath'); + window.locationStrategy.replaceState(null, null, '/newpath'); expect(window.defaultRouteName, '/initial'); }); - test('window.defaultRouteName should reset after navigation platform message', - () async { - await window.debugInitializeHistory(TestUrlStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'), - ), useSingle: true); + test('window.defaultRouteName should reset after navigation platform message', () async { + await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); // Reading it multiple times should return the same value. expect(window.defaultRouteName, '/initial'); expect(window.defaultRouteName, '/initial'); @@ -61,45 +57,45 @@ void testMain() { }); test('can disable location strategy', () async { - // Disable URL strategy. - expect(() => jsSetUrlStrategy(null), returnsNormally); - // History should be initialized. - expect(window.browserHistory, isNotNull); - // But without a URL strategy. - expect(window.browserHistory.urlStrategy, isNull); - // Current path is always "/" in this case. - expect(window.browserHistory.currentPath, '/'); - - // Perform some navigation operations. - routeInfomrationUpdated('/foo/bar', null); - // Path should not be updated because URL strategy is disabled. - expect(window.browserHistory.currentPath, '/'); - }); - - test('js interop throws on wrong type', () { - expect(() => jsSetUrlStrategy(123), throwsA(anything)); - expect(() => jsSetUrlStrategy('foo'), throwsA(anything)); - expect(() => jsSetUrlStrategy(false), throwsA(anything)); - }); - - test('cannot set url strategy after it is initialized', () async { - final testStrategy = TestUrlStrategy.fromEntry( + await window.debugSwitchBrowserHistory(useSingle: true); + final testStrategy = TestLocationStrategy.fromEntry( TestHistoryEntry('initial state', null, '/'), ); - await window.debugInitializeHistory(testStrategy, useSingle: true); - - expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); + await setStrategy(testStrategy); + + expect(window.locationStrategy, testStrategy); + // A single listener should've been setup. + expect(testStrategy.listeners, hasLength(1)); + // The initial entry should be there, plus another "flutter" entry. + expect(testStrategy.history, hasLength(2)); + expect(testStrategy.history[0].state, {'origin': true, 'state': 'initial state'}); + expect(testStrategy.history[1].state, {'flutter': true}); + expect(testStrategy.currentEntry, testStrategy.history[1]); + + // Now, let's disable location strategy and make sure things get cleaned up. + expect(() => jsSetLocationStrategy(null), returnsNormally); + // The locationStrategy is teared down asynchronously. + await Future.delayed(Duration.zero); + expect(window.locationStrategy, isNull); + + // The listener is removed asynchronously. + await Future.delayed(const Duration(milliseconds: 10)); + + // No more listeners. + expect(testStrategy.listeners, isEmpty); + // History should've moved back to the initial state. + expect(testStrategy.history[0].state, "initial state"); + expect(testStrategy.currentEntry, testStrategy.history[0]); }); - test('cannot set url strategy more than once', () async { - // First time is okay. - expect(() => jsSetUrlStrategy(null), returnsNormally); - // Second time is not allowed. - expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); + test('js interop throws on wrong type', () { + expect(() => jsSetLocationStrategy(123), throwsA(anything)); + expect(() => jsSetLocationStrategy('foo'), throwsA(anything)); + expect(() => jsSetLocationStrategy(false), throwsA(anything)); }); } -void jsSetUrlStrategy(dynamic strategy) { +void jsSetLocationStrategy(dynamic strategy) { js_util.callMethod( html.window, '_flutter_web_set_location_strategy', From 21a6aa2c75c27d88973eb3607106958f7f6a28a5 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 7 Oct 2020 22:04:02 -0700 Subject: [PATCH 012/219] Make TextInputModel::selection_start/end const (#21685) Neither of these methods mutate the state of the model. --- shell/platform/common/cpp/text_input_model.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index 27aef63cdd1c6..866969fc885a6 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -117,12 +117,12 @@ class TextInputModel { size_t selection_extent_ = 0; // Returns the left hand side of the selection. - size_t selection_start() { + size_t selection_start() const { return std::min(selection_base_, selection_extent_); } // Returns the right hand side of the selection. - size_t selection_end() { + size_t selection_end() const { return std::max(selection_base_, selection_extent_); } }; From 07ba1f339a1d46d78eeec22b1e8051a67121309d Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 01:57:05 -0400 Subject: [PATCH 013/219] Roll Skia from 041fd0ad7d93 to 38e6d226f24e (1 revision) (#21683) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 5b2619bcdd0cd..713ed3da917ea 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '041fd0ad7d93f1bc87a2c5b7d2c4f2217a802341', + 'skia_revision': '38e6d226f24e44f3881933d76fb8fbf0a59edc64', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 6d2888e951614..be82e7ea1a4fc 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 2716e2054e1a67a28f842cd740815028 +Signature: 6d57fdde589906a2d96197e61b970ac6 UNUSED LICENSES: From 91c4492cfb8bb1cbcdc6ecc89241473d04290db7 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 02:02:01 -0400 Subject: [PATCH 014/219] Roll Fuchsia Linux SDK from kr1tNtZvZ... to ZJHmp3INU... (#21684) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 713ed3da917ea..315babb08100d 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'kr1tNtZvZ94LI14n82Mp5cR_xcAMiUcoCSJoCEkHPDsC' + 'version': 'ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 50f5061254d22..d6eb7b7290de5 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 6aa5794c7bfd21165215fadf82cc2df3 +Signature: e794e6ce4652a9fdd105bddcb6799529 UNUSED LICENSES: From c2c60514d63ea73d64c17fca35cc70d70229ed66 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 02:07:01 -0400 Subject: [PATCH 015/219] Roll Dart SDK from 8f1a96317589 to 8572b5c0f6dc (1 revision) (#21686) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 281 +++++++++++++++++++++++- 2 files changed, 281 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 315babb08100d..730e8b9d0387a 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '8f1a96317589c23685b2f7a097953cea0396ac23', + 'dart_revision': '8572b5c0f6dc864cda98f5b4d23ed07e4c89a231', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 85d5369533240..8e8c2fe3af8ef 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 726c29a5daea48a4e2ab4901e613432d +Signature: 2a8f5eb8828c74900faa11b3195356a4 UNUSED LICENSES: @@ -8071,6 +8071,8 @@ FILE: ../../../third_party/dart/runtime/include/dart_version.h FILE: ../../../third_party/dart/runtime/include/internal/dart_api_dl_impl.h FILE: ../../../third_party/dart/runtime/observatory/bin/heap_snapshot.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/process_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/bin/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/process_snapshot.dart FILE: ../../../third_party/dart/runtime/platform/leak_sanitizer.h FILE: ../../../third_party/dart/runtime/platform/unaligned.h FILE: ../../../third_party/dart/runtime/platform/undefined_behavior_sanitizer.h @@ -8249,6 +8251,14 @@ FILE: ../../../third_party/dart/runtime/observatory/web/favicon.ico FILE: ../../../third_party/dart/runtime/observatory/web/index.html FILE: ../../../third_party/dart/runtime/observatory/web/third_party/trace_viewer_full.html FILE: ../../../third_party/dart/runtime/observatory/web/timeline.html +FILE: ../../../third_party/dart/runtime/observatory_2/lib/elements.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/img/chromium_icon.png +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/img/dart_icon.png +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/img/isolate_icon.png +FILE: ../../../third_party/dart/runtime/observatory_2/web/favicon.ico +FILE: ../../../third_party/dart/runtime/observatory_2/web/index.html +FILE: ../../../third_party/dart/runtime/observatory_2/web/third_party/trace_viewer_full.html +FILE: ../../../third_party/dart/runtime/observatory_2/web/timeline.html FILE: ../../../third_party/dart/runtime/tools/wiki/styles/style.scss FILE: ../../../third_party/dart/runtime/tools/wiki/templates/includes/auto-refresh.html FILE: ../../../third_party/dart/runtime/tools/wiki/templates/includes/favicon.html @@ -8362,6 +8372,11 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/models/objects/isola FILE: ../../../third_party/dart/runtime/observatory/lib/src/models/repositories/isolate_group.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/isolate_group.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/timeline_base.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/tree_map.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/isolate_group.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/isolate_group.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/isolate_group.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/timeline_base.dart FILE: ../../../third_party/dart/runtime/platform/elf.h FILE: ../../../third_party/dart/runtime/platform/thread_sanitizer.h FILE: ../../../third_party/dart/runtime/tools/dartfuzz/dartfuzz_api_table.dart @@ -9019,6 +9034,26 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/timeline_pa FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/view_footer.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/sample_profile/sample_profile.dart FILE: ../../../third_party/dart/runtime/observatory/web/timeline.js +FILE: ../../../third_party/dart/runtime/observatory_2/lib/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/cli.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/debugger.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/allocation_profile/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/analytics.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/cli/command.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/debugger/debugger.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/debugger/debugger_location.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/logging.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/logging_list.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/megamorphiccache_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/objectpool_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/persistent_handles.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/ports.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/timeline_page.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/view_footer.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/sample_profile/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/web/timeline.js FILE: ../../../third_party/dart/runtime/vm/atomic_test.cc FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.cc FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.h @@ -9179,6 +9214,30 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/subtype FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/timeline.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/unlinked_call.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/vm.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/containers/search_bar.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/reload.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/singletargetcache_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/singletargetcache_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/subtypetestcache_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/subtypetestcache_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/timeline/dashboard.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/unlinkedcall_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/unlinkedcall_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/service.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/single_target_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/subtype_test_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/timeline.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/unlinked_call.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/single_target_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/subtype_test_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/timeline.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/unlinked_call.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/vm.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/single_target_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/subtype_test_cache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/timeline.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/unlinked_call.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/vm.dart FILE: ../../../third_party/dart/runtime/platform/allocation.h FILE: ../../../third_party/dart/runtime/platform/growable_array.h FILE: ../../../third_party/dart/runtime/vm/compilation_trace.cc @@ -9580,6 +9639,153 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/strongl FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/target.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/repositories/type_arguments.dart FILE: ../../../third_party/dart/runtime/observatory/web/timeline_message_handler.js +FILE: ../../../third_party/dart/runtime/observatory_2/lib/event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/models.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/repositories.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/notification.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/class_allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/containers/virtual_collection.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/containers/virtual_tree.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/error_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/general_error.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/custom_element.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/nav_bar.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/nav_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/rendering_queue.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/rendering_scheduler.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/tag.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/uris.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/inbound_references.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate/counter_chart.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate/location.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate/run_state.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate/shared_summary.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate/summary.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/metric/details.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/metric/graph.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/class_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/isolate_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/library_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/menu_item.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/notify.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/notify_event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/notify_exception.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/refresh.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/top_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/nav/vm_menu.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/retaining_path.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/source_link.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/strongly_reachable_instances.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/vm_connect_target.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/exceptions.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/breakpoint.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/class.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/code.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/context.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/error.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/extension_data.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/field.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/flag.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/frame.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/function.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/guarded.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/heap_space.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/icdata.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/inbound_references.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/instance.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/isolate.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/library.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/local_var_descriptors.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/map_association.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/megamorphiccache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/metric.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/notification.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/object.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/objectpool.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/objectstore.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/pc_descriptors.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/persistent_handles.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/ports.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/retaining_path.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/script.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/sentinel.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/source_location.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/target.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/thread.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/timeline_event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/type_arguments.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/unknown.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/vm.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/objects/zone.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/breakpoint.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/class.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/context.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/editor.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/eval.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/field.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/flag.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/function.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/icdata.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/inbound_references.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/instance.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/isolate.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/library.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/megamorphiccache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/metric.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/notification.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/object.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/objectpool.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/objectstore.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/persistent_handles.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/ports.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/reachable_size.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/retained_size.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/retaining_path.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/script.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/strongly_reachable_instances.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/target.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/models/repositories/type_arguments.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/breakpoint.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/class.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/context.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/editor.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/eval.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/event.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/field.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/flag.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/function.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/icdata.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/inbound_references.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/instance.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/isolate.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/library.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/megamorphiccache.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/metric.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/notification.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/object.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/objectpool.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/objectstore.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/persistent_handles.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/ports.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/reachable_size.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/retained_size.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/retaining_path.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/script.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/settings.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/strongly_reachable_instances.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/target.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/repositories/type_arguments.dart +FILE: ../../../third_party/dart/runtime/observatory_2/web/timeline_message_handler.js FILE: ../../../third_party/dart/runtime/platform/syslog_fuchsia.cc FILE: ../../../third_party/dart/runtime/platform/utils_fuchsia.cc FILE: ../../../third_party/dart/runtime/platform/utils_fuchsia.h @@ -9754,6 +9960,58 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/type_argume FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/unknown_ref.dart FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/vm_view.dart FILE: ../../../third_party/dart/runtime/observatory/lib/tracer.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/application.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/location_manager.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/class_instances.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/class_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/class_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/code_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/code_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/context_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/context_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/cpu_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/cpu_profile/virtual_tree.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/cpu_profile_table.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/curly_block.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/error_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/eval_box.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/field_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/field_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/flag_list.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/function_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/function_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/icdata_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/icdata_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/instance_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/instance_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/json_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/library_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/library_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/local_var_descriptors_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/megamorphiccache_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/native_memory_profiler.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/object_common.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/object_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/objectpool_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/objectstore_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/observatory_application.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/pc_descriptors_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/sample_buffer_control.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/script_inset.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/script_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/script_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/sentinel_value.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/sentinel_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/source_inset.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/stack_trace_tree_config.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/type_arguments_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/unknown_ref.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/vm_view.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/tracer.dart FILE: ../../../third_party/dart/runtime/platform/atomic.h FILE: ../../../third_party/dart/runtime/platform/signal_blocker.h FILE: ../../../third_party/dart/runtime/vm/allocation.h @@ -10138,6 +10396,26 @@ FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/vm_connect. FILE: ../../../third_party/dart/runtime/observatory/lib/src/service/object.dart FILE: ../../../third_party/dart/runtime/observatory/lib/utils.dart FILE: ../../../third_party/dart/runtime/observatory/web/main.dart +FILE: ../../../third_party/dart/runtime/observatory_2/bin/shell.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/app.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/object_graph.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/service.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/service_common.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/service_html.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/service_io.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/page.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/settings.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/app/view_model.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/class_tree.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/debugger.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/heap_map.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/isolate_reconnect.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/metrics.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/vm_connect.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/service/object.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/utils.dart +FILE: ../../../third_party/dart/runtime/observatory_2/web/main.dart FILE: ../../../third_party/dart/runtime/platform/address_sanitizer.h FILE: ../../../third_party/dart/runtime/platform/memory_sanitizer.h FILE: ../../../third_party/dart/runtime/platform/safe_stack.h @@ -10256,6 +10534,7 @@ LIBRARY: dart ORIGIN: ../../../third_party/dart/runtime/observatory/web/third_party/webcomponents.min.js TYPE: LicenseType.bsd FILE: ../../../third_party/dart/runtime/observatory/web/third_party/webcomponents.min.js +FILE: ../../../third_party/dart/runtime/observatory_2/web/third_party/webcomponents.min.js ---------------------------------------------------------------------------------------------------- Copyright (c) 2014 The Polymer Authors. All rights reserved. From 5e13767ce80ec499d87618ae3b017a0a63956d12 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 03:17:01 -0400 Subject: [PATCH 016/219] Roll Skia from 38e6d226f24e to ac0723a06b53 (3 revisions) (#21688) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 730e8b9d0387a..1f7ee20628d32 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '38e6d226f24e44f3881933d76fb8fbf0a59edc64', + 'skia_revision': 'ac0723a06b53f65aac04e3ce9e12f29b53f87315', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index be82e7ea1a4fc..b3ff3976698bb 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 6d57fdde589906a2d96197e61b970ac6 +Signature: 0a329613a3353c3bac2996b596f32311 UNUSED LICENSES: From 63c550a9bfba1ac20b20b976cfa69fc642ae7024 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 06:07:45 -0400 Subject: [PATCH 017/219] Roll Fuchsia Mac SDK from m6w8tDXMm... to zhRBO0hCr... (#21689) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1f7ee20628d32..5835c1fb0860c 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'm6w8tDXMmHQL489ST4B5AUN5EgLI4SfaptN1uq7rHk4C' + 'version': 'zhRBO0hCrBrirs6jHcTrbXyLo0d0PRygZnfrwKRHxR4C' } ], 'condition': 'host_os == "mac"', From 8b5c5aa2fadc10cf1b79a93df02aeafd30dd6d10 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 08:37:01 -0400 Subject: [PATCH 018/219] Roll Dart SDK from 8572b5c0f6dc to 98ea0b4971dd (1 revision) (#21691) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5835c1fb0860c..4f3bd224b3ff8 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '8572b5c0f6dc864cda98f5b4d23ed07e4c89a231', + 'dart_revision': '98ea0b4971dd2930c8fa19376ae3932574cfbc7f', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 4c6f2ad747ac836e9362f0d56c013fc16d82f621 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Thu, 8 Oct 2020 08:25:48 -0700 Subject: [PATCH 019/219] Skip flaky test (#21694) --- .../framework/Source/FlutterPluginAppLifeCycleDelegateTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m index da2cc200781c9..b5d9c153fef6f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.m @@ -51,7 +51,7 @@ - (void)testWillResignActive { OCMVerify([plugin applicationWillResignActive:[UIApplication sharedApplication]]); } -- (void)testDidBecomeActive { +- (void)skip_testDidBecomeActive { FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); [delegate addDelegate:plugin]; From 15c587404c061e802baebd4f140282d47a97db2f Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 8 Oct 2020 09:22:01 -0700 Subject: [PATCH 020/219] Preserve specified AssetResolvers when performing a hot restart or updating the asset directory (#21611) Follow up from #21436 . That PR works for all embeddings except for Android, which creates a special JNI AssetResolver. Since the shell cannot recreate this resolver, update the logic to preserve existing resolvers instead. --- assets/asset_manager.cc | 9 ++++ assets/asset_manager.h | 5 +++ assets/asset_resolver.h | 18 ++++++++ assets/directory_asset_bundle.cc | 10 ++++- assets/directory_asset_bundle.h | 7 ++- shell/common/engine.cc | 4 ++ shell/common/engine.h | 3 ++ shell/common/persistent_cache_unittests.cc | 7 +-- shell/common/run_configuration.cc | 9 ++-- shell/common/shell.cc | 46 ++++++++++++-------- shell/platform/android/apk_asset_provider.cc | 4 ++ shell/platform/android/apk_asset_provider.h | 3 ++ shell/testing/tester_main.cc | 9 ++-- 13 files changed, 103 insertions(+), 31 deletions(-) diff --git a/assets/asset_manager.cc b/assets/asset_manager.cc index 60d169a31ebb2..0fbc28702e9f7 100644 --- a/assets/asset_manager.cc +++ b/assets/asset_manager.cc @@ -29,6 +29,10 @@ void AssetManager::PushBack(std::unique_ptr resolver) { resolvers_.push_back(std::move(resolver)); } +std::deque> AssetManager::TakeResolvers() { + return std::move(resolvers_); +} + // |AssetResolver| std::unique_ptr AssetManager::GetAsMapping( const std::string& asset_name) const { @@ -52,4 +56,9 @@ bool AssetManager::IsValid() const { return resolvers_.size() > 0; } +// |AssetResolver| +bool AssetManager::IsValidAfterAssetManagerChange() const { + return false; +} + } // namespace flutter diff --git a/assets/asset_manager.h b/assets/asset_manager.h index 2278742f50113..0a0f0ff170d4c 100644 --- a/assets/asset_manager.h +++ b/assets/asset_manager.h @@ -25,9 +25,14 @@ class AssetManager final : public AssetResolver { void PushBack(std::unique_ptr resolver); + std::deque> TakeResolvers(); + // |AssetResolver| bool IsValid() const override; + // |AssetResolver| + bool IsValidAfterAssetManagerChange() const override; + // |AssetResolver| std::unique_ptr GetAsMapping( const std::string& asset_name) const override; diff --git a/assets/asset_resolver.h b/assets/asset_resolver.h index b6cdb88b84ac0..8b3e323218939 100644 --- a/assets/asset_resolver.h +++ b/assets/asset_resolver.h @@ -21,6 +21,24 @@ class AssetResolver { virtual bool IsValid() const = 0; + //---------------------------------------------------------------------------- + /// @brief Certain asset resolvers are still valid after the asset + /// manager is replaced before a hot reload, or after a new run + /// configuration is created during a hot restart. By preserving + /// these resolvers and re-inserting them into the new resolver or + /// run configuration, the tooling can avoid needing to sync all + /// application assets through the Dart devFS upon connecting to + /// the VM Service. Besides improving the startup performance of + /// running a Flutter application, it also reduces the occurance + /// of tool failures due to repeated network flakes caused by + /// damaged cables or hereto unknown bugs in the Dart HTTP server + /// implementation. + /// + /// @return Returns whether this resolver is valid after the asset manager + /// or run configuration is updated. + /// + virtual bool IsValidAfterAssetManagerChange() const = 0; + [[nodiscard]] virtual std::unique_ptr GetAsMapping( const std::string& asset_name) const = 0; diff --git a/assets/directory_asset_bundle.cc b/assets/directory_asset_bundle.cc index 5ad7297313c99..d15a7e7373c15 100644 --- a/assets/directory_asset_bundle.cc +++ b/assets/directory_asset_bundle.cc @@ -12,11 +12,14 @@ namespace flutter { -DirectoryAssetBundle::DirectoryAssetBundle(fml::UniqueFD descriptor) +DirectoryAssetBundle::DirectoryAssetBundle( + fml::UniqueFD descriptor, + bool is_valid_after_asset_manager_change) : descriptor_(std::move(descriptor)) { if (!fml::IsDirectory(descriptor_)) { return; } + is_valid_after_asset_manager_change_ = is_valid_after_asset_manager_change; is_valid_ = true; } @@ -27,6 +30,11 @@ bool DirectoryAssetBundle::IsValid() const { return is_valid_; } +// |AssetResolver| +bool DirectoryAssetBundle::IsValidAfterAssetManagerChange() const { + return is_valid_after_asset_manager_change_; +} + // |AssetResolver| std::unique_ptr DirectoryAssetBundle::GetAsMapping( const std::string& asset_name) const { diff --git a/assets/directory_asset_bundle.h b/assets/directory_asset_bundle.h index 0a0a94c7aba15..49b02cdd27c71 100644 --- a/assets/directory_asset_bundle.h +++ b/assets/directory_asset_bundle.h @@ -14,17 +14,22 @@ namespace flutter { class DirectoryAssetBundle : public AssetResolver { public: - explicit DirectoryAssetBundle(fml::UniqueFD descriptor); + DirectoryAssetBundle(fml::UniqueFD descriptor, + bool is_valid_after_asset_manager_change); ~DirectoryAssetBundle() override; private: const fml::UniqueFD descriptor_; bool is_valid_ = false; + bool is_valid_after_asset_manager_change_ = false; // |AssetResolver| bool IsValid() const override; + // |AssetResolver| + bool IsValidAfterAssetManagerChange() const override; + // |AssetResolver| std::unique_ptr GetAsMapping( const std::string& asset_name) const override; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index d2384c03579cf..348e69a00a9aa 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -106,6 +106,10 @@ void Engine::SetupDefaultFontManager() { font_collection_.SetupDefaultFontManager(); } +std::shared_ptr Engine::GetAssetManager() { + return asset_manager_; +} + bool Engine::UpdateAssetManager( std::shared_ptr new_asset_manager) { if (asset_manager_ == new_asset_manager) { diff --git a/shell/common/engine.h b/shell/common/engine.h index b8d98aa766291..c5bbfc42c9c74 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -734,6 +734,9 @@ class Engine final : public RuntimeDelegate, // |RuntimeDelegate| FontCollection& GetFontCollection() override; + // Return the asset manager associated with the current engine, or nullptr. + std::shared_ptr GetAssetManager(); + // |PointerDataDispatcher::Delegate| void DoDispatchPacket(std::unique_ptr packet, uint64_t trace_flow_id) override; diff --git a/shell/common/persistent_cache_unittests.cc b/shell/common/persistent_cache_unittests.cc index a9ecfb1b39adb..77c5a8a9ddf52 100644 --- a/shell/common/persistent_cache_unittests.cc +++ b/shell/common/persistent_cache_unittests.cc @@ -185,9 +185,10 @@ TEST_F(ShellTest, CanLoadSkSLsFromAsset) { ResetAssetManager(); auto asset_manager = std::make_shared(); RunConfiguration config(nullptr, asset_manager); - asset_manager->PushBack( - std::make_unique(fml::OpenDirectory( - asset_dir.path().c_str(), false, fml::FilePermission::kRead))); + asset_manager->PushBack(std::make_unique( + fml::OpenDirectory(asset_dir.path().c_str(), false, + fml::FilePermission::kRead), + false)); CheckTwoSkSLsAreLoaded(); // 3rd, test the content of the SkSLs in the asset. diff --git a/shell/common/run_configuration.cc b/shell/common/run_configuration.cc index 8f0966b6bc896..1b32d5c295fbd 100644 --- a/shell/common/run_configuration.cc +++ b/shell/common/run_configuration.cc @@ -21,12 +21,13 @@ RunConfiguration RunConfiguration::InferFromSettings( if (fml::UniqueFD::traits_type::IsValid(settings.assets_dir)) { asset_manager->PushBack(std::make_unique( - fml::Duplicate(settings.assets_dir))); + fml::Duplicate(settings.assets_dir), true)); } - asset_manager->PushBack( - std::make_unique(fml::OpenDirectory( - settings.assets_path.c_str(), false, fml::FilePermission::kRead))); + asset_manager->PushBack(std::make_unique( + fml::OpenDirectory(settings.assets_path.c_str(), false, + fml::FilePermission::kRead), + true)); return {IsolateConfiguration::InferFromSettings(settings, asset_manager, io_worker), diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 2da9e9786e385..c250ca25a8552 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1401,10 +1401,21 @@ bool Shell::OnServiceProtocolRunInView( configuration.SetEntrypointAndLibrary(engine_->GetLastEntrypoint(), engine_->GetLastEntrypointLibrary()); - configuration.AddAssetResolver( - std::make_unique(fml::OpenDirectory( - asset_directory_path.c_str(), false, fml::FilePermission::kRead))); - configuration.AddAssetResolver(RestoreOriginalAssetResolver()); + configuration.AddAssetResolver(std::make_unique( + fml::OpenDirectory(asset_directory_path.c_str(), false, + fml::FilePermission::kRead), + false)); + + // Preserve any original asset resolvers to avoid syncing unchanged assets + // over the DevFS connection. + auto old_asset_manager = engine_->GetAssetManager(); + if (old_asset_manager != nullptr) { + for (auto& old_resolver : old_asset_manager->TakeResolvers()) { + if (old_resolver->IsValidAfterAssetManagerChange()) { + configuration.AddAssetResolver(std::move(old_resolver)); + } + } + } auto& allocator = response->GetAllocator(); response->SetObject(); @@ -1517,10 +1528,21 @@ bool Shell::OnServiceProtocolSetAssetBundlePath( auto asset_manager = std::make_shared(); - asset_manager->PushFront(RestoreOriginalAssetResolver()); asset_manager->PushFront(std::make_unique( fml::OpenDirectory(params.at("assetDirectory").data(), false, - fml::FilePermission::kRead))); + fml::FilePermission::kRead), + false)); + + // Preserve any original asset resolvers to avoid syncing unchanged assets + // over the DevFS connection. + auto old_asset_manager = engine_->GetAssetManager(); + if (old_asset_manager != nullptr) { + for (auto& old_resolver : old_asset_manager->TakeResolvers()) { + if (old_resolver->IsValidAfterAssetManagerChange()) { + asset_manager->PushBack(std::move(old_resolver)); + } + } + } if (engine_->UpdateAssetManager(std::move(asset_manager))) { response->AddMember("type", "Success", allocator); @@ -1624,16 +1646,4 @@ void Shell::OnDisplayUpdates(DisplayUpdateType update_type, display_manager_->HandleDisplayUpdates(update_type, displays); } -// Add the original asset directory to the resolvers so that unmodified assets -// bundled with the application specific format (APK, IPA) can be used without -// syncing to the Dart devFS. -std::unique_ptr Shell::RestoreOriginalAssetResolver() { - if (fml::UniqueFD::traits_type::IsValid(settings_.assets_dir)) { - return std::make_unique( - fml::Duplicate(settings_.assets_dir)); - } - return std::make_unique(fml::OpenDirectory( - settings_.assets_path.c_str(), false, fml::FilePermission::kRead)); -}; - } // namespace flutter diff --git a/shell/platform/android/apk_asset_provider.cc b/shell/platform/android/apk_asset_provider.cc index 49af5ffa0182a..73a4a26bd1d2d 100644 --- a/shell/platform/android/apk_asset_provider.cc +++ b/shell/platform/android/apk_asset_provider.cc @@ -27,6 +27,10 @@ bool APKAssetProvider::IsValid() const { return true; } +bool APKAssetProvider::IsValidAfterAssetManagerChange() const { + return true; +} + class APKAssetMapping : public fml::Mapping { public: APKAssetMapping(AAsset* asset) : asset_(asset) {} diff --git a/shell/platform/android/apk_asset_provider.h b/shell/platform/android/apk_asset_provider.h index 6ff1e8da7b776..f457b6c4ecc16 100644 --- a/shell/platform/android/apk_asset_provider.h +++ b/shell/platform/android/apk_asset_provider.h @@ -29,6 +29,9 @@ class APKAssetProvider final : public AssetResolver { // |flutter::AssetResolver| bool IsValid() const override; + // |flutter::AssetResolver| + bool IsValidAfterAssetManagerChange() const override; + // |flutter::AssetResolver| std::unique_ptr GetAsMapping( const std::string& asset_name) const override; diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 0f0264ce9ebdb..464b51c147fdb 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -192,10 +192,11 @@ int RunTester(const flutter::Settings& settings, auto asset_manager = std::make_shared(); asset_manager->PushBack(std::make_unique( - fml::Duplicate(settings.assets_dir))); - asset_manager->PushBack( - std::make_unique(fml::OpenDirectory( - settings.assets_path.c_str(), false, fml::FilePermission::kRead))); + fml::Duplicate(settings.assets_dir), true)); + asset_manager->PushBack(std::make_unique( + fml::OpenDirectory(settings.assets_path.c_str(), false, + fml::FilePermission::kRead), + true)); RunConfiguration run_configuration(std::move(isolate_configuration), std::move(asset_manager)); From 57d58e6b699023cd92c743b19d588f3bd358b5f1 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 12:27:02 -0400 Subject: [PATCH 021/219] Roll Skia from ac0723a06b53 to 8d43858ed21a (1 revision) (#21692) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4f3bd224b3ff8..edc37276fe851 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'ac0723a06b53f65aac04e3ce9e12f29b53f87315', + 'skia_revision': '8d43858ed21a0a1b96f724f04eba510a2c44041f', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the From b474a508a7adc42d17a74373fd625e73ba11440c Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 8 Oct 2020 09:55:43 -0700 Subject: [PATCH 022/219] Remove dependencies on _product variants of libdart from the Fuchsia release mode build (#21668) --- shell/platform/fuchsia/BUILD.gn | 4 +--- shell/platform/fuchsia/dart_runner/BUILD.gn | 8 ++++---- shell/platform/fuchsia/flutter/BUILD.gn | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/shell/platform/fuchsia/BUILD.gn b/shell/platform/fuchsia/BUILD.gn index 45dc143af8aed..cf4493e51797d 100644 --- a/shell/platform/fuchsia/BUILD.gn +++ b/shell/platform/fuchsia/BUILD.gn @@ -10,11 +10,9 @@ import("//flutter/tools/fuchsia/dart.gni") import("//flutter/tools/fuchsia/fuchsia_host_bundle.gni") product_suffix = "" -is_product = false if (flutter_runtime_mode == "release") { product_suffix = "product_" - is_product = true } fuchsia_host_bundle("flutter_binaries") { @@ -35,7 +33,7 @@ fuchsia_host_bundle("dart_binaries") { name = "dart_binaries" _gen_snapshot_to_use = gen_snapshot + "($host_toolchain)" - if (is_product) { + if (flutter_runtime_mode == "release") { _gen_snapshot_to_use = gen_snapshot_product + "($host_toolchain)" } diff --git a/shell/platform/fuchsia/dart_runner/BUILD.gn b/shell/platform/fuchsia/dart_runner/BUILD.gn index 4e3f514608a3a..442ff4111c9f0 100644 --- a/shell/platform/fuchsia/dart_runner/BUILD.gn +++ b/shell/platform/fuchsia/dart_runner/BUILD.gn @@ -93,8 +93,8 @@ runner("dart_jit_product_runner_bin") { product = true extra_defines = [ "DART_PRODUCT" ] extra_deps = [ - "//third_party/dart/runtime:libdart_jit_product", - "//third_party/dart/runtime/platform:libdart_platform_jit_product", + "//third_party/dart/runtime:libdart_jit", + "//third_party/dart/runtime/platform:libdart_platform_jit", ] } @@ -121,8 +121,8 @@ runner("dart_aot_product_runner_bin") { ] extra_deps = [ "embedder:dart_aot_product_snapshot_cc", - "//third_party/dart/runtime:libdart_precompiled_runtime_product", - "//third_party/dart/runtime/platform:libdart_platform_precompiled_runtime_product", + "//third_party/dart/runtime:libdart_precompiled_runtime", + "//third_party/dart/runtime/platform:libdart_platform_precompiled_runtime", ] } diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 69b312f441573..6b38a9136f490 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -229,8 +229,8 @@ flutter_runner("jit_product") { product = true extra_deps = [ - "//third_party/dart/runtime:libdart_jit_product", - "//third_party/dart/runtime/platform:libdart_platform_jit_product", + "//third_party/dart/runtime:libdart_jit", + "//third_party/dart/runtime/platform:libdart_platform_jit", ] } @@ -249,8 +249,8 @@ flutter_runner("aot_product") { product = true extra_deps = [ - "//third_party/dart/runtime:libdart_precompiled_runtime_product", - "//third_party/dart/runtime/platform:libdart_platform_precompiled_runtime_product", + "//third_party/dart/runtime:libdart_precompiled_runtime", + "//third_party/dart/runtime/platform:libdart_platform_precompiled_runtime", ] } From eb939028f90feebbb1b32da7ca1532a0e4aa044f Mon Sep 17 00:00:00 2001 From: nturgut Date: Thu, 8 Oct 2020 10:10:12 -0700 Subject: [PATCH 023/219] fixing the autofill overlay problem (blue area for chrome) (#21610) * fixing the autofill overlay problem (blue area for chrome) * addression comments --- lib/web_ui/lib/src/engine/dom_renderer.dart | 15 ++++++++++++ .../src/engine/text_editing/text_editing.dart | 20 +++++++++++++++- lib/web_ui/test/text_editing_test.dart | 23 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 9e1a3b50ff375..415ea66c86190 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -307,6 +307,21 @@ flt-glass-pane * { ''', sheet.cssRules.length); } + // This css prevents an autofill overlay brought by the browser during + // text field autofill by delaying the transition effect. + // See: https://github.com/flutter/flutter/issues/61132. + if(browserHasAutofillOverlay()) { + sheet.insertRule(''' +.transparentTextEditing:-webkit-autofill, +.transparentTextEditing:-webkit-autofill:hover, +.transparentTextEditing:-webkit-autofill:focus, +.transparentTextEditing:-webkit-autofill:active { + -webkit-transition-delay: 99999s; +} +''', sheet.cssRules.length); + } + + final html.BodyElement bodyElement = html.document.body!; setElementStyle(bodyElement, 'position', 'fixed'); setElementStyle(bodyElement, 'top', '0'); 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 5d298bed3df66..ce8b31d5fd23a 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 @@ -11,6 +11,16 @@ bool _debugVisibleTextEditing = false; /// The `keyCode` of the "Enter" key. const int _kReturnKeyCode = 13; +/// Blink and Webkit engines, bring an overlay on top of the text field when it +/// is autofilled. +bool browserHasAutofillOverlay() => + browserEngine == BrowserEngine.blink || + browserEngine == BrowserEngine.webkit; + +/// `transparentTextEditing` class is configured to make the autofill overlay +/// transparent. +const String transparentTextEditingClass = 'transparentTextEditing'; + void _emptyCallback(dynamic _) {} /// These style attributes are constant throughout the life time of an input @@ -39,7 +49,11 @@ void _setStaticStyleAttributes(html.HtmlElement domElement) { ..overflow = 'hidden' ..transformOrigin = '0 0 0'; - /// This property makes the input's blinking cursor transparent. + if (browserHasAutofillOverlay()) { + domElement.classes.add(transparentTextEditingClass); + } + + // This property makes the input's blinking cursor transparent. elementStyle.setProperty('caret-color', 'transparent'); if (_debugVisibleTextEditing) { @@ -80,6 +94,10 @@ void _hideAutofillElements(html.HtmlElement domElement, ..left = '-9999px'; } + if (browserHasAutofillOverlay()) { + domElement.classes.add(transparentTextEditingClass); + } + /// This property makes the input's blinking cursor transparent. elementStyle.setProperty('caret-color', 'transparent'); } diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index a5bc7fa21fe70..c8efd8182414d 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/text_editing_test.dart @@ -1343,6 +1343,18 @@ void testMain() { '500 12px sans-serif', ); + // For `blink` and `webkit` browser engines the overlay would be hidden. + if (browserEngine == BrowserEngine.blink || + browserEngine == BrowserEngine.webkit) { + expect(textEditing.editingElement.domElement.classes, + contains('transparentTextEditing')); + } else { + expect( + textEditing.editingElement.domElement.classes.any( + (element) => element.toString() == 'transparentTextEditing'), + isFalse); + } + const MethodCall clearClient = MethodCall('TextInput.clearClient'); sendFrameworkMessage(codec.encodeMethodCall(clearClient)); }, @@ -1806,6 +1818,17 @@ void testMain() { final CssStyleDeclaration css = firstElement.style; expect(css.color, 'transparent'); expect(css.backgroundColor, 'transparent'); + + // For `blink` and `webkit` browser engines the overlay would be hidden. + if (browserEngine == BrowserEngine.blink || + browserEngine == BrowserEngine.webkit) { + expect(firstElement.classes, contains('transparentTextEditing')); + } else { + expect( + firstElement.classes.any( + (element) => element.toString() == 'transparentTextEditing'), + isFalse); + } }); test('validate multi element form ids sorted for form id', () { From 75bc9360d922ecdfa2d2990c9b25c625769dd036 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 8 Oct 2020 10:12:03 -0700 Subject: [PATCH 024/219] [macOS] flutter_desktop_darwin_unittests can be enabled for all runtime modes (#21681) --- shell/platform/darwin/macos/framework/Source/FlutterEngine.mm | 1 + testing/run_tests.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 663ccd21f2fb1..a27860f806fa6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -267,6 +267,7 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.command_line_argv = argv.size() > 0 ? argv.data() : nullptr; flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String; + flutterArguments.shutdown_dart_vm_when_done = true; static size_t sTaskRunnerIdentifiers = 0; const FlutterTaskRunnerDescription cocoa_task_runner_description = { .struct_size = sizeof(FlutterTaskRunnerDescription), diff --git a/testing/run_tests.py b/testing/run_tests.py index 6b2cf2f18cea3..cfcedd74f7816 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -147,10 +147,6 @@ def RunCCTests(build_dir, filter): # These unit-tests are Objective-C and can only run on Darwin. if IsMac(): RunEngineExecutable(build_dir, 'flutter_channels_unittests', filter, shuffle_flags) - - # These tests can only be run on Darwin on debug mode. - # See: https://github.com/flutter/flutter/issues/66664 - if IsMac() and ('debug' in build_dir): RunEngineExecutable(build_dir, 'flutter_desktop_darwin_unittests', filter, shuffle_flags) # https://github.com/flutter/flutter/issues/36296 From bb9683fb5418c0a738e530bd2669823195784fbd Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 14:27:02 -0400 Subject: [PATCH 025/219] Roll Dart SDK from 98ea0b4971dd to 44fa3b9e566c (1 revision) (#21695) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index edc37276fe851..fa62690fd5a0a 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '98ea0b4971dd2930c8fa19376ae3932574cfbc7f', + 'dart_revision': '44fa3b9e566c3aa66c4a0dd9608c435b043190e9', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From d1ea7a432bbf8d802910334e2f131f195157c55f Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 8 Oct 2020 11:32:05 -0700 Subject: [PATCH 026/219] Update PR template to include the presubmit flake form (#21697) --- .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9aab6bc2b8d1b..551401f5e4932 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,6 +32,12 @@ Before you create this PR confirm that it meets all requirements listed below by - [ ] All existing and new tests are passing. - [ ] I am willing to follow-up on review comments in a timely manner. + +## Reviewer Checklist + +- [ ] I have submitted any presubmit flakes in this PR using the [engine presubmit flakes form] before re-triggering the failure. + + ## Breaking Change Did any tests fail when you ran them? Please read [handling breaking changes]. @@ -50,3 +56,4 @@ Did any tests fail when you ran them? Please read [handling breaking changes]. [CLA]: https://cla.developers.google.com/ [tree hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [handling breaking changes]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes +[engine presubmit flakes form]: https://forms.gle/Wc1VyFRYJjQTH6w5A From 8b1799b6165fc3f74d88f48968cdc4ce1822b7b6 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 8 Oct 2020 11:34:02 -0700 Subject: [PATCH 027/219] [macOS] Fix docs for loadAOTData and minor refactor (#21699) --- .../macos/framework/Source/FlutterEngine.mm | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index a27860f806fa6..6d0de14a71cfa 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -29,6 +29,16 @@ static FlutterLocale FlutterLocaleFromNSLocale(NSLocale* locale) { return flutterLocale; } +namespace { + +struct AotDataDeleter { + void operator()(FlutterEngineAOTData aot_data) { FlutterEngineCollectAOTData(aot_data); } +}; + +using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>; + +} + /** * Private interface declaration for FlutterEngine. */ @@ -76,6 +86,12 @@ - (BOOL)populateTextureWithIdentifier:(int64_t)textureID */ - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)targetTime; +/** + * Loads the AOT snapshots and instructions from the elf bundle (app_elf_snapshot.so) if it is + * present in the assets directory. + */ +- (UniqueAotDataPtr)loadAOTData:(NSString*)assetsDir; + @end #pragma mark - @@ -163,16 +179,6 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, #pragma mark - -namespace { - -struct AotDataDeleter { - void operator()(FlutterEngineAOTData aot_data) { FlutterEngineCollectAOTData(aot_data); } -}; - -using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>; - -} - @implementation FlutterEngine { // The embedding-API-level engine object. FLUTTER_API_SYMBOL(FlutterEngine) _engine; @@ -289,7 +295,7 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { }; flutterArguments.custom_task_runners = &custom_task_runners; - _aotData = [self loadAotData:flutterArguments.assets_path]; + _aotData = [self loadAOTData:_project.assetsPath]; if (_aotData) { flutterArguments.aot_data = _aotData.get(); } @@ -313,12 +319,14 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { return YES; } -- (UniqueAotDataPtr)loadAotData:(std::string)assetsDir { +- (UniqueAotDataPtr)loadAOTData:(NSString*)assetsDir { if (!FlutterEngineRunsAOTCompiledDartCode()) { return nullptr; } - std::filesystem::path assetsFsDir(assetsDir); + // This is the location where the test fixture places the snapshot file. + // For applications built by Flutter tool, this is in "App.framework". + std::filesystem::path assetsFsDir(assetsDir.UTF8String); std::filesystem::path elfFile("app_elf_snapshot.so"); auto fullElfPath = assetsFsDir / elfFile; From 3cb0b1a4552b72638847bcee772b1ea44b02f6b9 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 14:37:02 -0400 Subject: [PATCH 028/219] Roll Skia from 8d43858ed21a to 9c0b79a35489 (14 revisions) (#21698) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index fa62690fd5a0a..3b1349a891fb9 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '8d43858ed21a0a1b96f724f04eba510a2c44041f', + 'skia_revision': '9c0b79a3548993e5883287238405f7734110dc2c', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index b3ff3976698bb..fe507c5c76724 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 0a329613a3353c3bac2996b596f32311 +Signature: b4a6293d0c7db2f2b8d7883e4977328c UNUSED LICENSES: @@ -1307,6 +1307,7 @@ FILE: ../../../third_party/skia/modules/canvaskit/debug.js FILE: ../../../third_party/skia/modules/canvaskit/externs.js FILE: ../../../third_party/skia/modules/canvaskit/font.js FILE: ../../../third_party/skia/modules/canvaskit/fonts/NotoMono-Regular.ttf +FILE: ../../../third_party/skia/modules/canvaskit/gm.js FILE: ../../../third_party/skia/modules/canvaskit/gpu.js FILE: ../../../third_party/skia/modules/canvaskit/helper.js FILE: ../../../third_party/skia/modules/canvaskit/htmlcanvas/_namedcolors.js @@ -1347,6 +1348,8 @@ FILE: ../../../third_party/skia/modules/canvaskit/release.js FILE: ../../../third_party/skia/modules/canvaskit/rt_shader.js FILE: ../../../third_party/skia/modules/canvaskit/skottie.js FILE: ../../../third_party/skia/modules/canvaskit/skp.js +FILE: ../../../third_party/skia/modules/canvaskit/wasm_tools/gmtests.html +FILE: ../../../third_party/skia/modules/canvaskit/wasm_tools/viewer.html FILE: ../../../third_party/skia/modules/pathkit/chaining.js FILE: ../../../third_party/skia/modules/pathkit/externs.js FILE: ../../../third_party/skia/modules/pathkit/helper.js @@ -3909,6 +3912,7 @@ FILE: ../../../third_party/skia/include/private/GrD3DTypesPriv.h FILE: ../../../third_party/skia/include/private/SkIDChangeListener.h FILE: ../../../third_party/skia/include/private/SkSLSampleUsage.h FILE: ../../../third_party/skia/include/utils/SkCustomTypeface.h +FILE: ../../../third_party/skia/modules/canvaskit/gm_bindings.cpp FILE: ../../../third_party/skia/modules/canvaskit/viewer_bindings.cpp FILE: ../../../third_party/skia/modules/canvaskit/wasm_tools/SIMD/simd_float_capabilities.cpp FILE: ../../../third_party/skia/modules/canvaskit/wasm_tools/SIMD/simd_int_capabilities.cpp @@ -6602,7 +6606,6 @@ FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/types/canvaskit-wasm FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/types/index.d.ts FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/types/tsconfig.json FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/types/tslint.json -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/viewer.html FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/example.html FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/package.json FILE: ../../../third_party/skia/modules/pathkit/npm-wasm/example.html From e0bc31b1f7840ea3abe4e06245de85662e141efe Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 8 Oct 2020 11:40:16 -0700 Subject: [PATCH 029/219] Fix engine Xcode projection for newer versions of Xcode. (#21701) --- tools/gn | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/gn b/tools/gn index c0ecc2bf430f4..7514665b2dd6e 100755 --- a/tools/gn +++ b/tools/gn @@ -398,6 +398,8 @@ def main(argv): elif sys.platform == 'darwin': # On the Mac, generate an Xcode project by default. command.append('--ide=xcode') + command.append('--xcode-project=flutter_engine') + command.append('--xcode-build-system=new') elif sys.platform.startswith('win'): # On Windows, generate a Visual Studio project. command.append('--ide=vs') From 83e18142e83402990eea42267da9f1ab31ecaf71 Mon Sep 17 00:00:00 2001 From: nturgut Date: Thu, 8 Oct 2020 13:41:05 -0700 Subject: [PATCH 030/219] chrome driver for chrome 86 (#21705) --- lib/web_ui/dev/driver_version.yaml | 1 + lib/web_ui/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/dev/driver_version.yaml b/lib/web_ui/dev/driver_version.yaml index f2c3baf9391f8..7da5245013348 100644 --- a/lib/web_ui/dev/driver_version.yaml +++ b/lib/web_ui/dev/driver_version.yaml @@ -1,6 +1,7 @@ ## Map for driver versions to use for each browser version. ## See: https://chromedriver.chromium.org/downloads chrome: + 86: '86.0.4240.22' 85: '85.0.4183.38' 84: '84.0.4147.30' 83: '83.0.4103.39' diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 84e6d33eccde1..35f5af4d3fea5 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -34,4 +34,4 @@ dev_dependencies: git: url: git://github.com/flutter/web_installers.git path: packages/web_drivers/ - ref: 1cea0d79cad1ebc217c4bcbeba1be41470674a49 + ref: 58081a8b2fbf234e9c8da86fce28adfefe3c2093 From 7e20575b6bbf5d59d09344ff45b2d36c99b87d41 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 8 Oct 2020 13:53:04 -0700 Subject: [PATCH 031/219] Clear the Minikin layout cache during engine destruction (#21473) --- third_party/txt/src/txt/font_collection.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/third_party/txt/src/txt/font_collection.cc b/third_party/txt/src/txt/font_collection.cc index 31c3ba6d25ac1..5f895ef41afee 100644 --- a/third_party/txt/src/txt/font_collection.cc +++ b/third_party/txt/src/txt/font_collection.cc @@ -27,6 +27,7 @@ #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "font_skia.h" +#include "minikin/Layout.h" #include "txt/platform.h" #include "txt/text_style.h" @@ -82,7 +83,9 @@ class TxtFallbackFontProvider FontCollection::FontCollection() : enable_font_fallback_(true) {} -FontCollection::~FontCollection() = default; +FontCollection::~FontCollection() { + minikin::Layout::purgeCaches(); +} size_t FontCollection::GetFontManagersCount() const { return GetFontManagerOrder().size(); From aa155eed3b36802c3bde5de43be295f554244ad1 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 17:17:01 -0400 Subject: [PATCH 032/219] Roll Skia from 9c0b79a35489 to e17b0501963a (15 revisions) (#21707) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 33 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/DEPS b/DEPS index 3b1349a891fb9..6169533db00e5 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '9c0b79a3548993e5883287238405f7734110dc2c', + 'skia_revision': 'e17b0501963aac4840e280c7ac175fd38e4a0c9d', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index fe507c5c76724..14935a6911c8c 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: b4a6293d0c7db2f2b8d7883e4977328c +Signature: c05ecdf25a4f555ea48de1e26068b217 UNUSED LICENSES: @@ -1694,6 +1694,8 @@ FILE: ../../../third_party/skia/src/effects/SkColorMatrixFilter.cpp FILE: ../../../third_party/skia/src/effects/SkLayerDrawLooper.cpp FILE: ../../../third_party/skia/src/effects/SkPackBits.cpp FILE: ../../../third_party/skia/src/effects/SkTableMaskFilter.cpp +FILE: ../../../third_party/skia/src/gpu/GrAttachment.cpp +FILE: ../../../third_party/skia/src/gpu/GrAttachment.h FILE: ../../../third_party/skia/src/gpu/GrContext.cpp FILE: ../../../third_party/skia/src/gpu/GrGpu.h FILE: ../../../third_party/skia/src/gpu/GrGpuResource.cpp @@ -1705,14 +1707,14 @@ FILE: ../../../third_party/skia/src/gpu/GrPathRendererChain.cpp FILE: ../../../third_party/skia/src/gpu/GrPathRendererChain.h FILE: ../../../third_party/skia/src/gpu/GrRenderTarget.cpp FILE: ../../../third_party/skia/src/gpu/GrRenderTarget.h -FILE: ../../../third_party/skia/src/gpu/GrStencilAttachment.cpp -FILE: ../../../third_party/skia/src/gpu/GrStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/GrStencilSettings.cpp FILE: ../../../third_party/skia/src/gpu/GrTexture.cpp FILE: ../../../third_party/skia/src/gpu/GrTexture.h FILE: ../../../third_party/skia/src/gpu/SkGpuDevice.cpp FILE: ../../../third_party/skia/src/gpu/geometry/GrPathUtils.cpp FILE: ../../../third_party/skia/src/gpu/geometry/GrPathUtils.h +FILE: ../../../third_party/skia/src/gpu/gl/GrGLAttachment.cpp +FILE: ../../../third_party/skia/src/gpu/gl/GrGLAttachment.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLDefines.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLGLSL.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLGLSL.h @@ -1725,8 +1727,6 @@ FILE: ../../../third_party/skia/src/gpu/gl/GrGLProgram.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLProgram.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLRenderTarget.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLRenderTarget.h -FILE: ../../../third_party/skia/src/gpu/gl/GrGLStencilAttachment.cpp -FILE: ../../../third_party/skia/src/gpu/gl/GrGLStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLTexture.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLTexture.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLUtil.cpp @@ -2555,6 +2555,8 @@ FILE: ../../../third_party/skia/src/gpu/text/GrTextBlob.cpp FILE: ../../../third_party/skia/src/gpu/text/GrTextBlob.h FILE: ../../../third_party/skia/src/gpu/text/GrTextBlobCache.cpp FILE: ../../../third_party/skia/src/gpu/text/GrTextBlobCache.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkAttachment.cpp +FILE: ../../../third_party/skia/src/gpu/vk/GrVkAttachment.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkBuffer.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkBuffer.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkCaps.cpp @@ -2575,8 +2577,6 @@ FILE: ../../../third_party/skia/src/gpu/vk/GrVkRenderPass.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkRenderPass.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkRenderTarget.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkRenderTarget.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkStencilAttachment.cpp -FILE: ../../../third_party/skia/src/gpu/vk/GrVkStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkTexture.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkTexture.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkTextureRenderTarget.cpp @@ -3141,6 +3141,8 @@ FILE: ../../../third_party/skia/src/gpu/ccpr/GrOctoBounds.cpp FILE: ../../../third_party/skia/src/gpu/ccpr/GrOctoBounds.h FILE: ../../../third_party/skia/src/gpu/ccpr/GrStencilAtlasOp.cpp FILE: ../../../third_party/skia/src/gpu/ccpr/GrVSCoverageProcessor.h +FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnAttachment.cpp +FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnAttachment.h FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnBuffer.cpp FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnBuffer.h FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnCaps.cpp @@ -3157,8 +3159,6 @@ FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnRenderTarget.cpp FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnRenderTarget.h FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnRingBuffer.cpp FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnRingBuffer.h -FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnStencilAttachment.cpp -FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnTexture.cpp FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnTexture.h FILE: ../../../third_party/skia/src/gpu/dawn/GrDawnTextureRenderTarget.cpp @@ -3459,6 +3459,8 @@ FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGra FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlAttachment.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlAttachment.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlCppUtil.h @@ -3475,8 +3477,6 @@ FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUniformHandler.h @@ -3778,12 +3778,12 @@ FILE: ../../../third_party/skia/src/gpu/gl/GrGLSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLSemaphore.h FILE: ../../../third_party/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp FILE: ../../../third_party/skia/src/gpu/glsl/GrGLSLVertexGeoBuilder.h +FILE: ../../../third_party/skia/src/gpu/mock/GrMockAttachment.h FILE: ../../../third_party/skia/src/gpu/mock/GrMockBuffer.h FILE: ../../../third_party/skia/src/gpu/mock/GrMockCaps.h FILE: ../../../third_party/skia/src/gpu/mock/GrMockGpu.cpp FILE: ../../../third_party/skia/src/gpu/mock/GrMockGpu.h FILE: ../../../third_party/skia/src/gpu/mock/GrMockOpsRenderPass.h -FILE: ../../../third_party/skia/src/gpu/mock/GrMockStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/mock/GrMockTexture.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlCaps.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlCaps.mm @@ -3944,6 +3944,8 @@ FILE: ../../../third_party/skia/src/gpu/GrUnrefDDLTask.h FILE: ../../../third_party/skia/src/gpu/GrUtil.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.h +FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAttachment.cpp +FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAttachment.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DBuffer.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DBuffer.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DCaps.cpp @@ -3976,8 +3978,6 @@ FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DRootSignature.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DRootSignature.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DSemaphore.h -FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DStencilAttachment.cpp -FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DStencilAttachment.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DTexture.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DTexture.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DTextureRenderTarget.cpp @@ -4011,7 +4011,6 @@ FILE: ../../../third_party/skia/src/sksl/SkSLInliner.h FILE: ../../../third_party/skia/src/sksl/SkSLSPIRVtoHLSL.cpp FILE: ../../../third_party/skia/src/sksl/SkSLSPIRVtoHLSL.h FILE: ../../../third_party/skia/src/sksl/SkSLSampleUsage.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLTinyUnorderedMap.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLInlineMarker.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLSymbolAlias.h FILE: ../../../third_party/skia/src/utils/SkCustomTypeface.cpp @@ -4150,8 +4149,8 @@ FILE: ../../../third_party/skia/src/gpu/GrEagerVertexAllocator.h FILE: ../../../third_party/skia/src/gpu/GrHashMapWithCache.h FILE: ../../../third_party/skia/src/gpu/GrRecordingContextPriv.cpp FILE: ../../../third_party/skia/src/gpu/GrSTArenaList.h -FILE: ../../../third_party/skia/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.cpp -FILE: ../../../third_party/skia/src/gpu/GrThreadSafeUniquelyKeyedProxyViewCache.h +FILE: ../../../third_party/skia/src/gpu/GrThreadSafeCache.cpp +FILE: ../../../third_party/skia/src/gpu/GrThreadSafeCache.h FILE: ../../../third_party/skia/src/gpu/ccpr/GrAutoMapVertexBuffer.h FILE: ../../../third_party/skia/src/gpu/effects/GrArithmeticProcessor.fp FILE: ../../../third_party/skia/src/gpu/effects/GrDitherEffect.fp From 9ab40c2809f2563aef0d00296156a930efbeb80b Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 8 Oct 2020 14:30:02 -0700 Subject: [PATCH 033/219] [web] Reland Support custom url strategies (#21702) --- ci/licenses_golden/licenses_flutter | 5 +- lib/web_ui/lib/src/engine.dart | 5 +- .../lib/src/engine/browser_location.dart | 211 ------------- .../src/engine/{ => navigation}/history.dart | 213 +++++++------ .../engine/navigation/js_url_strategy.dart | 78 +++++ .../src/engine/navigation/url_strategy.dart | 296 ++++++++++++++++++ lib/web_ui/lib/src/engine/test_embedding.dart | 26 +- lib/web_ui/lib/src/engine/window.dart | 208 ++++++------ lib/web_ui/lib/src/ui/initialization.dart | 4 - lib/web_ui/test/engine/history_test.dart | 127 +++++--- lib/web_ui/test/engine/navigation_test.dart | 14 +- lib/web_ui/test/window_test.dart | 92 +++--- 12 files changed, 746 insertions(+), 533 deletions(-) delete mode 100644 lib/web_ui/lib/src/engine/browser_location.dart rename lib/web_ui/lib/src/engine/{ => navigation}/history.dart (69%) create mode 100644 lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart create mode 100644 lib/web_ui/lib/src/engine/navigation/url_strategy.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ed74366279e94..72bfafac4c963 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -426,7 +426,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvas_pool.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -463,7 +462,9 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/history.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/history.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/js_url_strategy.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/history/url_strategy.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/clip.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index d8c01373840f8..dcb19c8a3165f 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -26,7 +26,6 @@ part 'engine/alarm_clock.dart'; part 'engine/assets.dart'; part 'engine/bitmap_canvas.dart'; part 'engine/browser_detection.dart'; -part 'engine/browser_location.dart'; part 'engine/canvaskit/canvas.dart'; part 'engine/canvaskit/canvaskit_canvas.dart'; part 'engine/canvaskit/canvaskit_api.dart'; @@ -63,7 +62,9 @@ part 'engine/dom_canvas.dart'; part 'engine/dom_renderer.dart'; part 'engine/engine_canvas.dart'; part 'engine/frame_reference.dart'; -part 'engine/history.dart'; +part 'engine/navigation/history.dart'; +part 'engine/navigation/js_url_strategy.dart'; +part 'engine/navigation/url_strategy.dart'; part 'engine/html/backdrop_filter.dart'; part 'engine/html/canvas.dart'; part 'engine/html/clip.dart'; diff --git a/lib/web_ui/lib/src/engine/browser_location.dart b/lib/web_ui/lib/src/engine/browser_location.dart deleted file mode 100644 index a9701cd99060f..0000000000000 --- a/lib/web_ui/lib/src/engine/browser_location.dart +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of engine; - -// TODO(mdebbar): add other strategies. - -// Some parts of this file were inspired/copied from the AngularDart router. - -/// [LocationStrategy] is responsible for representing and reading route state -/// from the browser's URL. -/// -/// At the moment, only one strategy is implemented: [HashLocationStrategy]. -/// -/// This is used by [BrowserHistory] to interact with browser history APIs. -abstract class LocationStrategy { - const LocationStrategy(); - - /// Subscribes to popstate events and returns a function that could be used to - /// unsubscribe from popstate events. - ui.VoidCallback onPopState(html.EventListener fn); - - /// The active path in the browser history. - String get path; - - /// The state of the current browser history entry. - dynamic get state; - - /// Given a path that's internal to the app, create the external url that - /// will be used in the browser. - String prepareExternalUrl(String internalUrl); - - /// Push a new history entry. - void pushState(dynamic state, String title, String url); - - /// Replace the currently active history entry. - void replaceState(dynamic state, String title, String url); - - /// Go to the previous history entry. - Future back({int count = 1}); -} - -/// This is an implementation of [LocationStrategy] that uses the browser URL's -/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) -/// to represent its state. -/// -/// In order to use this [LocationStrategy] for an app, it needs to be set in -/// [ui.window.locationStrategy]: -/// -/// ```dart -/// import 'package:flutter_web/material.dart'; -/// import 'package:flutter_web/ui.dart' as ui; -/// -/// void main() { -/// ui.window.locationStrategy = const ui.HashLocationStrategy(); -/// runApp(MyApp()); -/// } -/// ``` -class HashLocationStrategy extends LocationStrategy { - final PlatformLocation _platformLocation; - - const HashLocationStrategy( - [this._platformLocation = const BrowserPlatformLocation()]); - - @override - ui.VoidCallback onPopState(html.EventListener fn) { - _platformLocation.onPopState(fn); - return () => _platformLocation.offPopState(fn); - } - - @override - String get path { - // the hash value is always prefixed with a `#` - // and if it is empty then it will stay empty - String path = _platformLocation.hash ?? ''; - assert(path.isEmpty || path.startsWith('#')); - - // We don't want to return an empty string as a path. Instead we default to "/". - if (path.isEmpty || path == '#') { - return '/'; - } - // At this point, we know [path] starts with "#" and isn't empty. - return path.substring(1); - } - - @override - dynamic get state => _platformLocation.state; - - @override - String prepareExternalUrl(String internalUrl) { - // It's convention that if the hash path is empty, we omit the `#`; however, - // if the empty URL is pushed it won't replace any existing fragment. So - // when the hash path is empty, we instead return the location's path and - // query. - return internalUrl.isEmpty - ? '${_platformLocation.pathname}${_platformLocation.search}' - : '#$internalUrl'; - } - - @override - void pushState(dynamic state, String title, String url) { - _platformLocation.pushState(state, title, prepareExternalUrl(url)); - } - - @override - void replaceState(dynamic state, String title, String url) { - _platformLocation.replaceState(state, title, prepareExternalUrl(url)); - } - - @override - Future back({int count = 1}) { - _platformLocation.back(count); - return _waitForPopState(); - } - - /// Waits until the next popstate event is fired. - /// - /// This is useful for example to wait until the browser has handled the - /// `history.back` transition. - Future _waitForPopState() { - final Completer completer = Completer(); - late ui.VoidCallback unsubscribe; - unsubscribe = onPopState((_) { - unsubscribe(); - completer.complete(); - }); - return completer.future; - } -} - -/// [PlatformLocation] encapsulates all calls to DOM apis, which allows the -/// [LocationStrategy] classes to be platform agnostic and testable. -/// -/// The [PlatformLocation] class is used directly by all implementations of -/// [LocationStrategy] when they need to interact with the DOM apis like -/// pushState, popState, etc... -abstract class PlatformLocation { - const PlatformLocation(); - - void onPopState(html.EventListener fn); - void offPopState(html.EventListener fn); - - void onHashChange(html.EventListener fn); - void offHashChange(html.EventListener fn); - - String get pathname; - String get search; - String? get hash; - dynamic get state; - - void pushState(dynamic state, String title, String url); - void replaceState(dynamic state, String title, String url); - void back(int count); -} - -/// An implementation of [PlatformLocation] for the browser. -class BrowserPlatformLocation extends PlatformLocation { - html.Location get _location => html.window.location; - html.History get _history => html.window.history; - - const BrowserPlatformLocation(); - - @override - void onPopState(html.EventListener fn) { - html.window.addEventListener('popstate', fn); - } - - @override - void offPopState(html.EventListener fn) { - html.window.removeEventListener('popstate', fn); - } - - @override - void onHashChange(html.EventListener fn) { - html.window.addEventListener('hashchange', fn); - } - - @override - void offHashChange(html.EventListener fn) { - html.window.removeEventListener('hashchange', fn); - } - - @override - String get pathname => _location.pathname!; - - @override - String get search => _location.search!; - - @override - String get hash => _location.hash; - - @override - dynamic get state => _history.state; - - @override - void pushState(dynamic state, String title, String url) { - _history.pushState(state, title, url); - } - - @override - void replaceState(dynamic state, String title, String url) { - _history.replaceState(state, title, url); - } - - @override - void back(int count) { - _history.go(-count); - } -} diff --git a/lib/web_ui/lib/src/engine/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart similarity index 69% rename from lib/web_ui/lib/src/engine/history.dart rename to lib/web_ui/lib/src/engine/navigation/history.dart index 59e1ba5fddf6d..0a578162a9096 100644 --- a/lib/web_ui/lib/src/engine/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -25,64 +25,39 @@ abstract class BrowserHistory { late ui.VoidCallback _unsubscribe; /// The strategy to interact with html browser history. - LocationStrategy? get locationStrategy => _locationStrategy; - LocationStrategy? _locationStrategy; - /// Updates the strategy. - /// - /// This method will also remove any previous modifications to the html - /// browser history and start anew. - Future setLocationStrategy(LocationStrategy? strategy) async { - if (strategy != _locationStrategy) { - await _tearoffStrategy(_locationStrategy); - _locationStrategy = strategy; - await _setupStrategy(_locationStrategy); - } - } + UrlStrategy? get urlStrategy; - Future _setupStrategy(LocationStrategy? strategy) async { - if (strategy == null) { - return; - } - _unsubscribe = strategy.onPopState(onPopState as dynamic Function(html.Event)); - await setup(); - } + bool _isDisposed = false; - Future _tearoffStrategy(LocationStrategy? strategy) async { - if (strategy == null) { - return; - } - _unsubscribe(); - - await tearDown(); + void _setupStrategy(UrlStrategy strategy) { + _unsubscribe = strategy.addPopStateListener( + onPopState as html.EventListener, + ); } /// Exit this application and return to the previous page. Future exit() async { - if (_locationStrategy != null) { - await _tearoffStrategy(_locationStrategy); + if (urlStrategy != null) { + await tearDown(); // Now the history should be in the original state, back one more time to // exit the application. - await _locationStrategy!.back(); - _locationStrategy = null; + await urlStrategy!.go(-1); } } /// This method does the same thing as the browser back button. - Future back() { - if (locationStrategy != null) { - return locationStrategy!.back(); - } - return Future.value(); + Future back() async { + return urlStrategy?.go(-1); } /// The path of the current location of the user's browser. - String get currentPath => locationStrategy?.path ?? '/'; + String get currentPath => urlStrategy?.getPath() ?? '/'; /// The state of the current location of the user's browser. - dynamic get currentState => locationStrategy?.state; + Object? get currentState => urlStrategy?.getState(); /// Update the url with the given [routeName] and [state]. - void setRouteName(String? routeName, {dynamic? state}); + void setRouteName(String? routeName, {Object? state}); /// A callback method to handle browser backward or forward buttons. /// @@ -90,12 +65,9 @@ abstract class BrowserHistory { /// applications accordingly. void onPopState(covariant html.PopStateEvent event); - /// Sets up any prerequisites to use this browser history class. - Future setup() => Future.value(); - /// Restore any modifications to the html browser history during the lifetime /// of this class. - Future tearDown() => Future.value(); + Future tearDown(); } /// A browser history class that creates a set of browser history entries to @@ -113,31 +85,51 @@ abstract class BrowserHistory { /// * [SingleEntryBrowserHistory], which is used when the framework does not use /// a Router for routing. class MultiEntriesBrowserHistory extends BrowserHistory { + MultiEntriesBrowserHistory({required this.urlStrategy}) { + final UrlStrategy? strategy = urlStrategy; + if (strategy == null) { + return; + } + + _setupStrategy(strategy); + if (!_hasSerialCount(currentState)) { + strategy.replaceState( + _tagWithSerialCount(currentState, 0), 'flutter', currentPath); + } + // If we restore from a page refresh, the _currentSerialCount may not be 0. + _lastSeenSerialCount = _currentSerialCount; + } + + @override + final UrlStrategy? urlStrategy; + late int _lastSeenSerialCount; int get _currentSerialCount { if (_hasSerialCount(currentState)) { - return currentState['serialCount'] as int; + final Map stateMap = + currentState as Map; + return stateMap['serialCount'] as int; } return 0; } - dynamic _tagWithSerialCount(dynamic originialState, int count) { - return { + Object _tagWithSerialCount(Object? originialState, int count) { + return { 'serialCount': count, 'state': originialState, }; } - bool _hasSerialCount(dynamic state) { + bool _hasSerialCount(Object? state) { return state is Map && state['serialCount'] != null; } @override - void setRouteName(String? routeName, {dynamic? state}) { - if (locationStrategy != null) { + void setRouteName(String? routeName, {Object? state}) { + if (urlStrategy != null) { assert(routeName != null); _lastSeenSerialCount += 1; - locationStrategy!.pushState( + urlStrategy!.pushState( _tagWithSerialCount(state, _lastSeenSerialCount), 'flutter', routeName!, @@ -147,58 +139,51 @@ class MultiEntriesBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { - assert(locationStrategy != null); + assert(urlStrategy != null); // May be a result of direct url access while the flutter application is // already running. if (!_hasSerialCount(event.state)) { // In this case we assume this will be the next history entry from the // last seen entry. - locationStrategy!.replaceState( - _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), - 'flutter', - currentPath); + urlStrategy!.replaceState( + _tagWithSerialCount(event.state, _lastSeenSerialCount + 1), + 'flutter', + currentPath); } _lastSeenSerialCount = _currentSerialCount; if (window._onPlatformMessage != null) { window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( - MethodCall('pushRouteInformation', { - 'location': currentPath, - 'state': event.state?['state'], - }) - ), + MethodCall('pushRouteInformation', { + 'location': currentPath, + 'state': event.state?['state'], + })), (_) {}, ); } } @override - Future setup() { - if (!_hasSerialCount(currentState)) { - locationStrategy!.replaceState( - _tagWithSerialCount(currentState, 0), - 'flutter', - currentPath - ); + Future tearDown() async { + if (_isDisposed || urlStrategy == null) { + return; } - // If we retore from a page refresh, the _currentSerialCount may not be 0. - _lastSeenSerialCount = _currentSerialCount; - return Future.value(); - } + _isDisposed = true; + _unsubscribe(); - @override - Future tearDown() async { // Restores the html browser history. assert(_hasSerialCount(currentState)); int backCount = _currentSerialCount; if (backCount > 0) { - await locationStrategy!.back(count: backCount); + await urlStrategy!.go(-backCount); } // Unwrap state. assert(_hasSerialCount(currentState) && _currentSerialCount == 0); - locationStrategy!.replaceState( - currentState['state'], + final Map stateMap = + currentState as Map; + urlStrategy!.replaceState( + stateMap['state'], 'flutter', currentPath, ); @@ -222,37 +207,61 @@ class MultiEntriesBrowserHistory extends BrowserHistory { /// * [MultiEntriesBrowserHistory], which is used when the framework uses a /// Router for routing. class SingleEntryBrowserHistory extends BrowserHistory { + SingleEntryBrowserHistory({required this.urlStrategy}) { + final UrlStrategy? strategy = urlStrategy; + if (strategy == null) { + return; + } + + _setupStrategy(strategy); + + final String path = currentPath; + if (!_isFlutterEntry(html.window.history.state)) { + // An entry may not have come from Flutter, for example, when the user + // refreshes the page. They land directly on the "flutter" entry, so + // there's no need to setup the "origin" and "flutter" entries, we can + // safely assume they are already setup. + _setupOriginEntry(strategy); + _setupFlutterEntry(strategy, replace: false, path: path); + } + } + + @override + final UrlStrategy? urlStrategy; + static const MethodCall _popRouteMethodCall = MethodCall('popRoute'); static const String _kFlutterTag = 'flutter'; static const String _kOriginTag = 'origin'; - Map _wrapOriginState(dynamic state) { + Map _wrapOriginState(Object? state) { return {_kOriginTag: true, 'state': state}; } - dynamic _unwrapOriginState(dynamic state) { + + Object? _unwrapOriginState(Object? state) { assert(_isOriginEntry(state)); final Map originState = state as Map; return originState['state']; } + Map _flutterState = {_kFlutterTag: true}; /// The origin entry is the history entry that the Flutter app landed on. It's /// created by the browser when the user navigates to the url of the app. - bool _isOriginEntry(dynamic state) { + bool _isOriginEntry(Object? state) { return state is Map && state[_kOriginTag] == true; } /// The flutter entry is a history entry that we maintain on top of the origin /// entry. It allows us to catch popstate events when the user hits the back /// button. - bool _isFlutterEntry(dynamic state) { + bool _isFlutterEntry(Object? state) { return state is Map && state[_kFlutterTag] == true; } @override - void setRouteName(String? routeName, {dynamic? state}) { - if (locationStrategy != null) { - _setupFlutterEntry(locationStrategy!, replace: true, path: routeName); + void setRouteName(String? routeName, {Object? state}) { + if (urlStrategy != null) { + _setupFlutterEntry(urlStrategy!, replace: true, path: routeName); } } @@ -260,7 +269,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { @override void onPopState(covariant html.PopStateEvent event) { if (_isOriginEntry(event.state)) { - _setupFlutterEntry(_locationStrategy!); + _setupFlutterEntry(urlStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. if (window._onPlatformMessage != null) { @@ -302,14 +311,14 @@ class SingleEntryBrowserHistory extends BrowserHistory { // 2. Then we remove the new entry. // This will take us back to our "flutter" entry and it causes a new // popstate event that will be handled in the "else if" section above. - _locationStrategy!.back(); + urlStrategy!.go(-1); } } /// This method should be called when the Origin Entry is active. It just /// replaces the state of the entry so that we can recognize it later using /// [_isOriginEntry] inside [_popStateListener]. - void _setupOriginEntry(LocationStrategy strategy) { + void _setupOriginEntry(UrlStrategy strategy) { assert(strategy != null); // ignore: unnecessary_null_comparison strategy.replaceState(_wrapOriginState(currentState), 'origin', ''); } @@ -317,7 +326,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { /// This method is used manipulate the Flutter Entry which is always the /// active entry while the Flutter app is running. void _setupFlutterEntry( - LocationStrategy strategy, { + UrlStrategy strategy, { bool replace = false, String? path, }) { @@ -330,28 +339,18 @@ class SingleEntryBrowserHistory extends BrowserHistory { } } - @override - Future setup() { - final String path = currentPath; - if (_isFlutterEntry(html.window.history.state)) { - // This could happen if the user, for example, refreshes the page. They - // will land directly on the "flutter" entry, so there's no need to setup - // the "origin" and "flutter" entries, we can safely assume they are - // already setup. - } else { - _setupOriginEntry(locationStrategy!); - _setupFlutterEntry(locationStrategy!, replace: false, path: path); - } - return Future.value(); - } - @override Future tearDown() async { - if (locationStrategy != null) { - // We need to remove the flutter entry that we pushed in setup. - await locationStrategy!.back(); - // Restores original state. - locationStrategy!.replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); + if (_isDisposed || urlStrategy == null) { + return; } + _isDisposed = true; + _unsubscribe(); + + // We need to remove the flutter entry that we pushed in setup. + await urlStrategy!.go(-1); + // Restores original state. + urlStrategy! + .replaceState(_unwrapOriginState(currentState), 'flutter', currentPath); } } diff --git a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart new file mode 100644 index 0000000000000..decb7c249d44d --- /dev/null +++ b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +typedef _PathGetter = String Function(); + +typedef _StateGetter = Object? Function(); + +typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener); + +typedef _StringToString = String Function(String); + +typedef _StateOperation = void Function( + Object? state, String title, String url); + +typedef _HistoryMove = Future Function(int count); + +/// The JavaScript representation of a URL strategy. +/// +/// This is used to pass URL strategy implementations across a JS-interop +/// bridge from the app to the engine. +@JS() +@anonymous +abstract class JsUrlStrategy { + /// Creates an instance of [JsUrlStrategy] from a bag of URL strategy + /// functions. + external factory JsUrlStrategy({ + required _PathGetter getPath, + required _StateGetter getState, + required _AddPopStateListener addPopStateListener, + required _StringToString prepareExternalUrl, + required _StateOperation pushState, + required _StateOperation replaceState, + required _HistoryMove go, + }); + + /// Adds a listener to the `popstate` event and returns a function that, when + /// invoked, removes the listener. + external ui.VoidCallback addPopStateListener(html.EventListener fn); + + /// Returns the active path in the browser. + external String getPath(); + + /// Returns the history state in the browser. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + external Object? getState(); + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + external String prepareExternalUrl(String internalUrl); + + /// Push a new history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + external void pushState(Object? state, String title, String url); + + /// Replace the currently active history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + external void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + external Future go(int count); +} diff --git a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart new file mode 100644 index 0000000000000..fcf2cecfd0b8e --- /dev/null +++ b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart @@ -0,0 +1,296 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +/// Represents and reads route state from the browser's URL. +/// +/// By default, the [HashUrlStrategy] subclass is used if the app doesn't +/// specify one. +abstract class UrlStrategy { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const UrlStrategy(); + + /// Adds a listener to the `popstate` event and returns a function that, when + /// invoked, removes the listener. + ui.VoidCallback addPopStateListener(html.EventListener fn); + + /// Returns the active path in the browser. + String getPath(); + + /// The state of the current browser history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + Object? getState(); + + /// Given a path that's internal to the app, create the external url that + /// will be used in the browser. + String prepareExternalUrl(String internalUrl); + + /// Push a new history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + void pushState(Object? state, String title, String url); + + /// Replace the currently active history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + Future go(int count); +} + +/// This is an implementation of [UrlStrategy] that uses the browser URL's +/// [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) +/// to represent its state. +/// +/// In order to use this [UrlStrategy] for an app, it needs to be set like this: +/// +/// ```dart +/// import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +/// +/// // Somewhere before calling `runApp()` do: +/// setUrlStrategy(const HashUrlStrategy()); +/// ``` +class HashUrlStrategy extends UrlStrategy { + /// Creates an instance of [HashUrlStrategy]. + /// + /// The [PlatformLocation] parameter is useful for testing to mock out browser + /// interations. + const HashUrlStrategy( + [this._platformLocation = const BrowserPlatformLocation()]); + + final PlatformLocation _platformLocation; + + @override + ui.VoidCallback addPopStateListener(html.EventListener fn) { + _platformLocation.addPopStateListener(fn); + return () => _platformLocation.removePopStateListener(fn); + } + + @override + String getPath() { + // the hash value is always prefixed with a `#` + // and if it is empty then it will stay empty + final String path = _platformLocation.hash ?? ''; + assert(path.isEmpty || path.startsWith('#')); + + // We don't want to return an empty string as a path. Instead we default to "/". + if (path.isEmpty || path == '#') { + return '/'; + } + // At this point, we know [path] starts with "#" and isn't empty. + return path.substring(1); + } + + @override + Object? getState() => _platformLocation.state; + + @override + String prepareExternalUrl(String internalUrl) { + // It's convention that if the hash path is empty, we omit the `#`; however, + // if the empty URL is pushed it won't replace any existing fragment. So + // when the hash path is empty, we instead return the location's path and + // query. + return internalUrl.isEmpty + ? '${_platformLocation.pathname}${_platformLocation.search}' + : '#$internalUrl'; + } + + @override + void pushState(Object? state, String title, String url) { + _platformLocation.pushState(state, title, prepareExternalUrl(url)); + } + + @override + void replaceState(Object? state, String title, String url) { + _platformLocation.replaceState(state, title, prepareExternalUrl(url)); + } + + @override + Future go(int count) { + _platformLocation.go(count); + return _waitForPopState(); + } + + /// Waits until the next popstate event is fired. + /// + /// This is useful, for example, to wait until the browser has handled the + /// `history.back` transition. + Future _waitForPopState() { + final Completer completer = Completer(); + late ui.VoidCallback unsubscribe; + unsubscribe = addPopStateListener((_) { + unsubscribe(); + completer.complete(); + }); + return completer.future; + } +} + +/// Wraps a custom implementation of [UrlStrategy] that was previously converted +/// to a [JsUrlStrategy]. +class CustomUrlStrategy extends UrlStrategy { + /// Wraps the [delegate] in a [CustomUrlStrategy] instance. + CustomUrlStrategy.fromJs(this.delegate); + + final JsUrlStrategy delegate; + + @override + ui.VoidCallback addPopStateListener(html.EventListener fn) => + delegate.addPopStateListener(fn); + + @override + String getPath() => delegate.getPath(); + + @override + Object? getState() => delegate.getState(); + + @override + String prepareExternalUrl(String internalUrl) => + delegate.prepareExternalUrl(internalUrl); + + @override + void pushState(Object? state, String title, String url) => + delegate.pushState(state, title, url); + + @override + void replaceState(Object? state, String title, String url) => + delegate.replaceState(state, title, url); + + @override + Future go(int count) => delegate.go(count); +} + +/// Encapsulates all calls to DOM apis, which allows the [UrlStrategy] classes +/// to be platform agnostic and testable. +/// +/// For convenience, the [PlatformLocation] class can be used by implementations +/// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc. +abstract class PlatformLocation { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const PlatformLocation(); + + /// Registers an event listener for the `popstate` event. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate + void addPopStateListener(html.EventListener fn); + + /// Unregisters the given listener (added by [addPopStateListener]) from the + /// `popstate` event. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate + void removePopStateListener(html.EventListener fn); + + /// The `pathname` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname + String get pathname; + + /// The `query` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/search + String get search; + + /// The `hash]` part of the URL in the browser address bar. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/Location/hash + String? get hash; + + /// The `state` in the current history entry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state + Object? get state; + + /// Adds a new entry to the browser history stack. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState + void pushState(Object? state, String title, String url); + + /// Replaces the current entry in the browser history stack. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState + void replaceState(Object? state, String title, String url); + + /// Moves forwards or backwards through the history stack. + /// + /// A negative [count] value causes a backward move in the history stack. And + /// a positive [count] value causs a forward move. + /// + /// Examples: + /// + /// * `go(-2)` moves back 2 steps in history. + /// * `go(3)` moves forward 3 steps in hisotry. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/API/History/go + void go(int count); + + /// The base href where the Flutter app is being served. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + String? getBaseHref(); +} + +/// Delegates to real browser APIs to provide platform location functionality. +class BrowserPlatformLocation extends PlatformLocation { + /// Default constructor for [BrowserPlatformLocation]. + const BrowserPlatformLocation(); + + html.Location get _location => html.window.location; + html.History get _history => html.window.history; + + @override + void addPopStateListener(html.EventListener fn) { + html.window.addEventListener('popstate', fn); + } + + @override + void removePopStateListener(html.EventListener fn) { + html.window.removeEventListener('popstate', fn); + } + + @override + String get pathname => _location.pathname!; + + @override + String get search => _location.search!; + + @override + String get hash => _location.hash; + + @override + Object? get state => _history.state; + + @override + void pushState(Object? state, String title, String url) { + _history.pushState(state, title, url); + } + + @override + void replaceState(Object? state, String title, String url) { + _history.replaceState(state, title, url); + } + + @override + void go(int count) { + _history.go(count); + } + + @override + String? getBaseHref() => html.document.baseUri; +} diff --git a/lib/web_ui/lib/src/engine/test_embedding.dart b/lib/web_ui/lib/src/engine/test_embedding.dart index f0d3a4291dbad..0255e5fb19601 100644 --- a/lib/web_ui/lib/src/engine/test_embedding.dart +++ b/lib/web_ui/lib/src/engine/test_embedding.dart @@ -20,29 +20,27 @@ class TestHistoryEntry { } } -/// This location strategy mimics the browser's history as closely as possible +/// This URL strategy mimics the browser's history as closely as possible /// while doing it all in memory with no interaction with the browser. /// /// It keeps a list of history entries and event listeners in memory and /// manipulates them in order to achieve the desired functionality. -class TestLocationStrategy extends LocationStrategy { - /// Creates a instance of [TestLocationStrategy] with an empty string as the +class TestUrlStrategy extends UrlStrategy { + /// Creates a instance of [TestUrlStrategy] with an empty string as the /// path. - factory TestLocationStrategy() => TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '')); + factory TestUrlStrategy() => TestUrlStrategy.fromEntry(TestHistoryEntry(null, null, '')); - /// Creates an instance of [TestLocationStrategy] and populates it with a list + /// Creates an instance of [TestUrlStrategy] and populates it with a list /// that has [initialEntry] as the only item. - TestLocationStrategy.fromEntry(TestHistoryEntry initialEntry) + TestUrlStrategy.fromEntry(TestHistoryEntry initialEntry) : _currentEntryIndex = 0, history = [initialEntry]; @override - String get path => currentEntry.url; + String getPath() => currentEntry.url; @override - dynamic get state { - return currentEntry.state; - } + dynamic getState() => currentEntry.state; int _currentEntryIndex; int get currentEntryIndex => _currentEntryIndex; @@ -105,12 +103,12 @@ class TestLocationStrategy extends LocationStrategy { } @override - Future back({int count = 1}) { + Future go(int count) { assert(withinAppHistory); - // Browsers don't move back in history immediately. They do it at the next + // Browsers don't move in history immediately. They do it at the next // event loop. So let's simulate that. return _nextEventLoop(() { - _currentEntryIndex = _currentEntryIndex - count; + _currentEntryIndex = _currentEntryIndex + count; if (withinAppHistory) { _firePopStateEvent(); } @@ -124,7 +122,7 @@ class TestLocationStrategy extends LocationStrategy { final List listeners = []; @override - ui.VoidCallback onPopState(html.EventListener fn) { + ui.VoidCallback addPopStateListener(html.EventListener fn) { listeners.add(fn); return () { // Schedule a micro task here to avoid removing the listener during diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 94d73f6c7d717..19597f6ae32de 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -13,20 +13,33 @@ const bool _debugPrintPlatformMessages = false; /// This may be overridden in tests, for example, to pump fake frames. ui.VoidCallback? scheduleFrameCallback; +typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); + +/// A JavaScript hook to customize the URL strategy of a Flutter app. +// +// Keep this js name in sync with flutter_web_plugins. Find it at: +// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +// +// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 +@JS('_flutter_web_set_location_strategy') +external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); + +UrlStrategy? _createDefaultUrlStrategy() { + return ui.debugEmulateFlutterTesterEnvironment + ? null + : const HashUrlStrategy(); +} + /// The Web implementation of [ui.Window]. class EngineWindow extends ui.Window { EngineWindow() { _addBrightnessMediaQueryListener(); - js.context['_flutter_web_set_location_strategy'] = (LocationStrategy strategy) { - locationStrategy = strategy; - }; - registerHotRestartListener(() { - js.context['_flutter_web_set_location_strategy'] = null; - }); + _addUrlStrategyListener(); } @override - double get devicePixelRatio => _debugDevicePixelRatio ?? browserDevicePixelRatio; + double get devicePixelRatio => + _debugDevicePixelRatio ?? browserDevicePixelRatio; /// Returns device pixel ratio returned by browser. static double get browserDevicePixelRatio { @@ -117,7 +130,8 @@ class EngineWindow extends ui.Window { double height = 0; double width = 0; if (html.window.visualViewport != null) { - height = html.window.visualViewport!.height!.toDouble() * devicePixelRatio; + height = + html.window.visualViewport!.height!.toDouble() * devicePixelRatio; width = html.window.visualViewport!.width!.toDouble() * devicePixelRatio; } else { height = html.window.innerHeight! * devicePixelRatio; @@ -126,7 +140,7 @@ class EngineWindow extends ui.Window { // This method compares the new dimensions with the previous ones. // Return false if the previous dimensions are not set. - if(_physicalSize != null) { + if (_physicalSize != null) { // First confirm both height and width are effected. if (_physicalSize!.height != height && _physicalSize!.width != width) { // If prior to rotation height is bigger than width it should be the @@ -154,78 +168,41 @@ class EngineWindow extends ui.Window { /// Handles the browser history integration to allow users to use the back /// button, etc. @visibleForTesting - BrowserHistory get browserHistory => _browserHistory; - BrowserHistory _browserHistory = MultiEntriesBrowserHistory(); - - @visibleForTesting - Future debugSwitchBrowserHistory({required bool useSingle}) async { - if (useSingle) - await _useSingleEntryBrowserHistory(); - else - await _useMultiEntryBrowserHistory(); - } - - /// This function should only be used for test setup. In real application, we - /// only allow one time switch from the MultiEntriesBrowserHistory to - /// the SingleEntryBrowserHistory to prevent the application to switch back - /// forth between router and non-router. - Future _useMultiEntryBrowserHistory() async { - if (_browserHistory is MultiEntriesBrowserHistory) { - return; - } - final LocationStrategy? strategy = _browserHistory.locationStrategy; - if (strategy != null) - await _browserHistory.setLocationStrategy(null); - _browserHistory = MultiEntriesBrowserHistory(); - if (strategy != null) - await _browserHistory.setLocationStrategy(strategy); + BrowserHistory get browserHistory { + return _browserHistory ??= + MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); } + BrowserHistory? _browserHistory; + Future _useSingleEntryBrowserHistory() async { if (_browserHistory is SingleEntryBrowserHistory) { return; } - final LocationStrategy? strategy = _browserHistory.locationStrategy; - if (strategy != null) - await _browserHistory.setLocationStrategy(null); - _browserHistory = SingleEntryBrowserHistory(); - if (strategy != null) - await _browserHistory.setLocationStrategy(strategy); + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); } - /// Simulates clicking the browser's back button. - Future webOnlyBack() => _browserHistory.back(); - /// Lazily initialized when the `defaultRouteName` getter is invoked. /// - /// The reason for the lazy initialization is to give enough time for the app to set [locationStrategy] + /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] /// in `lib/src/ui/initialization.dart`. String? _defaultRouteName; @override - String get defaultRouteName => _defaultRouteName ??= _browserHistory.currentPath; + String get defaultRouteName { + return _defaultRouteName ??= browserHistory.currentPath; + } @override void scheduleFrame() { if (scheduleFrameCallback == null) { - throw new Exception( - 'scheduleFrameCallback must be initialized first.'); + throw new Exception('scheduleFrameCallback must be initialized first.'); } scheduleFrameCallback!(); } - /// Change the strategy to use for handling browser history location. - /// Setting this member will automatically update [_browserHistory]. - /// - /// By setting this to null, the browser history will be disabled. - set locationStrategy(LocationStrategy? strategy) { - _browserHistory.setLocationStrategy(strategy); - } - - /// Returns the currently active location strategy. - @visibleForTesting - LocationStrategy? get locationStrategy => _browserHistory.locationStrategy; - @override ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; ui.VoidCallback? _onTextScaleFactorChanged; @@ -477,8 +454,8 @@ class EngineWindow extends ui.Window { /// Engine code should use this method instead of the callback directly. /// Otherwise zones won't work properly. - void invokeOnPlatformMessage( - String name, ByteData? data, ui.PlatformMessageResponseCallback callback) { + void invokeOnPlatformMessage(String name, ByteData? data, + ui.PlatformMessageResponseCallback callback) { _invoke3( _onPlatformMessage, _onPlatformMessageZone, @@ -500,7 +477,9 @@ class EngineWindow extends ui.Window { /// Wraps the given [callback] in another callback that ensures that the /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(ui.PlatformMessageResponseCallback? callback) { + static ui.PlatformMessageResponseCallback? + _zonedPlatformMessageResponseCallback( + ui.PlatformMessageResponseCallback? callback) { if (callback == null) { return null; } @@ -564,7 +543,7 @@ class EngineWindow extends ui.Window { final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { case 'SystemNavigator.pop': - _browserHistory.exit().then((_) { + browserHistory.exit().then((_) { _replyToPlatformMessage( callback, codec.encodeSuccessEnvelope(true)); }); @@ -585,8 +564,8 @@ class EngineWindow extends ui.Window { case 'SystemChrome.setPreferredOrientations': final List? arguments = decoded.arguments; domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage(callback, - codec.encodeSuccessEnvelope(success)); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(success)); }); return; case 'SystemSound.play': @@ -632,7 +611,8 @@ class EngineWindow extends ui.Window { case 'flutter/platform_views': if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder.handlePlatformViewCall(data, callback); + rasterizer!.surface.viewEmbedder + .handlePlatformViewCall(data, callback); } else { ui.handlePlatformViewCall(data!, callback!); } @@ -646,27 +626,11 @@ class EngineWindow extends ui.Window { return; case 'flutter/navigation': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map message = decoded.arguments as Map; - switch (decoded.method) { - case 'routeUpdated': - _useSingleEntryBrowserHistory().then((void data) { - _browserHistory.setRouteName(message['routeName']); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - }); - break; - case 'routeInformationUpdated': - assert(_browserHistory is MultiEntriesBrowserHistory); - _browserHistory.setRouteName( - message['location'], - state: message['state'], - ); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - break; - } + _handleNavigationMessage(data, callback).then((handled) { + if (!handled && callback != null) { + callback(null); + } + }); // As soon as Flutter starts taking control of the app navigation, we // should reset [_defaultRouteName] to "/" so it doesn't have any // further effect after this point. @@ -685,6 +649,51 @@ class EngineWindow extends ui.Window { _replyToPlatformMessage(callback, null); } + @visibleForTesting + Future debugInitializeHistory( + UrlStrategy? strategy, { + required bool useSingle, + }) async { + await _browserHistory?.tearDown(); + if (useSingle) { + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } else { + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } + } + + @visibleForTesting + Future debugResetHistory() async { + await _browserHistory?.tearDown(); + _browserHistory = null; + } + + Future _handleNavigationMessage( + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) async { + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map arguments = decoded.arguments; + + switch (decoded.method) { + case 'routeUpdated': + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + case 'routeInformationUpdated': + assert(browserHistory is MultiEntriesBrowserHistory); + browserHistory.setRouteName( + arguments['location'], + state: arguments['state'], + ); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + } + return false; + } + int _getHapticFeedbackDuration(String? type) { switch (type) { case 'HapticFeedbackType.lightImpact': @@ -746,7 +755,8 @@ class EngineWindow extends ui.Window { : ui.Brightness.light); _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = event as html.MediaQueryListEvent; + final html.MediaQueryListEvent mqEvent = + event as html.MediaQueryListEvent; _updatePlatformBrightness( mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); }; @@ -756,6 +766,21 @@ class EngineWindow extends ui.Window { }); } + void _addUrlStrategyListener() { + _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { + assert( + _browserHistory == null, + 'Cannot set URL strategy more than once.', + ); + final UrlStrategy? strategy = + jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + }); + registerHotRestartListener(() { + _jsSetUrlStrategy = null; + }); + } + /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. void _removeBrightnessMediaQueryListener() { _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); @@ -785,7 +810,8 @@ class EngineWindow extends ui.Window { } @visibleForTesting - late Rasterizer? rasterizer = experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; + late Rasterizer? rasterizer = + experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; } bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { @@ -831,8 +857,8 @@ void _invoke1(void callback(A a)?, Zone? zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3( - void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, A1 arg1, A2 arg2, A3 arg3) { +void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, + A1 arg1, A2 arg2, A3 arg3) { if (callback == null) { return; } diff --git a/lib/web_ui/lib/src/ui/initialization.dart b/lib/web_ui/lib/src/ui/initialization.dart index a7b06b3586def..ca317304ec79b 100644 --- a/lib/web_ui/lib/src/ui/initialization.dart +++ b/lib/web_ui/lib/src/ui/initialization.dart @@ -21,10 +21,6 @@ Future webOnlyInitializePlatform({ Future _initializePlatform({ engine.AssetManager? assetManager, }) async { - if (!debugEmulateFlutterTesterEnvironment) { - engine.window.locationStrategy = const engine.HashLocationStrategy(); - } - engine.initializeEngine(); // This needs to be after `webOnlyInitializeEngine` because that is where the diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index 4c11ed0033636..f5be42ae61db7 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -16,11 +16,6 @@ import 'package:ui/src/engine.dart'; import '../spy.dart'; -TestLocationStrategy get strategy => window.browserHistory.locationStrategy; -Future setStrategy(TestLocationStrategy newStrategy) async { - await window.browserHistory.setLocationStrategy(newStrategy); -} - Map _wrapOriginState(dynamic state) { return {'origin': true, 'state': state}; } @@ -48,18 +43,19 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: true); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await setStrategy(null); + await window.debugResetHistory(); }); test('basic setup works', () async { - await setStrategy(TestLocationStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); // There should be two entries: origin and flutter. expect(strategy.history, hasLength(2)); @@ -82,7 +78,11 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button pops routes correctly', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); + // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(2)); expect(strategy.currentEntry.state, flutterState); @@ -98,7 +98,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.back(); + await strategy.go(-1); // First, the framework should've received a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -115,7 +115,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await routeUpdated('/page1'); await routeUpdated('/page2'); @@ -127,7 +130,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -143,7 +146,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -161,8 +164,8 @@ void testMain() { // The next browser back will exit the app. We store the strategy locally // because it will be remove from the browser history class once it exits // the app. - TestLocationStrategy originalStrategy = strategy; - await originalStrategy.back(); + TestUrlStrategy originalStrategy = strategy; + await originalStrategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -181,7 +184,10 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -202,7 +208,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `popRoute` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -221,7 +227,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('user types unknown url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry(null, null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry(null, null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); await strategy.simulateUserTypingUrl('/unknown'); // This delay is necessary to wait for [BrowserHistory] because it @@ -248,18 +257,19 @@ void testMain() { final PlatformMessagesSpy spy = PlatformMessagesSpy(); setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: false); spy.setUp(); }); tearDown(() async { spy.tearDown(); - await setStrategy(null); + await window.debugResetHistory(); }); test('basic setup works', () async { - await setStrategy(TestLocationStrategy.fromEntry( - TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); // There should be only one entry. expect(strategy.history, hasLength(1)); @@ -273,7 +283,11 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('browser back button push route infromation correctly', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); + // Initially, we should be on the flutter entry. expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); @@ -289,7 +303,7 @@ void testMain() { // No platform messages have been sent so far. expect(spy.messages, isEmpty); // Clicking back should take us to page1. - await strategy.back(); + await strategy.go(-1); // First, the framework should've received a `pushRouteInformation` // platform message. expect(spy.messages, hasLength(1)); @@ -310,7 +324,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('multiple browser back clicks', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -322,7 +339,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -338,7 +355,7 @@ void testMain() { expect(strategy.currentEntry.state, _tagStateWithSerialCount('page1 state', 1)); expect(strategy.currentEntry.url, '/page1'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -359,7 +376,10 @@ void testMain() { browserEngine == BrowserEngine.webkit); test('handle user-provided url', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await strategy.simulateUserTypingUrl('/page3'); // This delay is necessary to wait for [BrowserHistory] because it @@ -381,7 +401,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page3'); // Back to home. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -401,7 +421,10 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); test('forward button works', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/home'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/home'), + ); + await window.debugInitializeHistory(strategy, useSingle: false); await routeInfomrationUpdated('/page1', 'page1 state'); await routeInfomrationUpdated('/page2', 'page2 state'); @@ -413,7 +436,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page2'); // Back to page1. - await strategy.back(); + await strategy.go(-1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -430,7 +453,7 @@ void testMain() { expect(strategy.currentEntry.url, '/page1'); // Forward to page2 - await strategy.back(count: -1); + await strategy.go(1); // 1. The engine sends a `pushRouteInformation` platform message. expect(spy.messages, hasLength(1)); expect(spy.messages[0].channel, 'flutter/navigation'); @@ -450,7 +473,7 @@ void testMain() { skip: browserEngine == BrowserEngine.edge); }); - group('$HashLocationStrategy', () { + group('$HashUrlStrategy', () { TestPlatformLocation location; setUp(() { @@ -462,26 +485,26 @@ void testMain() { }); test('leading slash is optional', () { - final HashLocationStrategy strategy = HashLocationStrategy(location); + final HashUrlStrategy strategy = HashUrlStrategy(location); location.hash = '#/'; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); location.hash = '#/foo'; - expect(strategy.path, '/foo'); + expect(strategy.getPath(), '/foo'); location.hash = '#foo'; - expect(strategy.path, 'foo'); + expect(strategy.getPath(), 'foo'); }); test('path should not be empty', () { - final HashLocationStrategy strategy = HashLocationStrategy(location); + final HashUrlStrategy strategy = HashUrlStrategy(location); location.hash = ''; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); location.hash = '#'; - expect(strategy.path, '/'); + expect(strategy.getPath(), '/'); }); }); } @@ -529,31 +552,31 @@ class TestPlatformLocation extends PlatformLocation { String hash; dynamic state; - void onPopState(html.EventListener fn) { + @override + void addPopStateListener(html.EventListener fn) { throw UnimplementedError(); } - void offPopState(html.EventListener fn) { - throw UnimplementedError(); - } - - void onHashChange(html.EventListener fn) { - throw UnimplementedError(); - } - - void offHashChange(html.EventListener fn) { + @override + void removePopStateListener(html.EventListener fn) { throw UnimplementedError(); } + @override void pushState(dynamic state, String title, String url) { throw UnimplementedError(); } + @override void replaceState(dynamic state, String title, String url) { throw UnimplementedError(); } - void back(int count) { + @override + void go(int count) { throw UnimplementedError(); } + + @override + String getBaseHref() => '/'; } diff --git a/lib/web_ui/test/engine/navigation_test.dart b/lib/web_ui/test/engine/navigation_test.dart index 44d3bf2939e95..99bae818f54a7 100644 --- a/lib/web_ui/test/engine/navigation_test.dart +++ b/lib/web_ui/test/engine/navigation_test.dart @@ -10,7 +10,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart' as engine; -engine.TestLocationStrategy _strategy; +engine.TestUrlStrategy _strategy; const engine.MethodCodec codec = engine.JSONMethodCodec(); @@ -21,12 +21,14 @@ void main() { } void testMain() { - setUp(() { - engine.window.locationStrategy = _strategy = engine.TestLocationStrategy(); + setUp(() async { + _strategy = engine.TestUrlStrategy(); + await engine.window.debugInitializeHistory(_strategy, useSingle: true); }); - tearDown(() { - engine.window.locationStrategy = _strategy = null; + tearDown(() async { + _strategy = null; + await engine.window.debugResetHistory(); }); test('Tracks pushed, replaced and popped routes', () async { @@ -40,6 +42,6 @@ void testMain() { (_) => completer.complete(), ); await completer.future; - expect(_strategy.path, '/foo'); + expect(_strategy.getPath(), '/foo'); }); } diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index b83849bffc8d6..ef0a755f550cf 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. // @dart = 2.6 -import 'dart:async'; import 'dart:html' as html; import 'dart:js_util' as js_util; import 'dart:typed_data'; @@ -12,34 +11,39 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -const MethodCodec codec = JSONMethodCodec(); +import 'engine/history_test.dart'; +import 'matchers.dart'; -void emptyCallback(ByteData date) {} +const MethodCodec codec = JSONMethodCodec(); -Future setStrategy(TestLocationStrategy newStrategy) async { - await window.browserHistory.setLocationStrategy(newStrategy); -} +void emptyCallback(ByteData data) {} void main() { internalBootstrapBrowserTest(() => testMain); } void testMain() { - setUp(() async { - await window.debugSwitchBrowserHistory(useSingle: true); + tearDown(() async { + await window.debugResetHistory(); }); test('window.defaultRouteName should not change', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ); + await window.debugInitializeHistory(strategy, useSingle: true); expect(window.defaultRouteName, '/initial'); // Changing the URL in the address bar later shouldn't affect [window.defaultRouteName]. - window.locationStrategy.replaceState(null, null, '/newpath'); + strategy.replaceState(null, null, '/newpath'); expect(window.defaultRouteName, '/initial'); }); - test('window.defaultRouteName should reset after navigation platform message', () async { - await setStrategy(TestLocationStrategy.fromEntry(TestHistoryEntry('initial state', null, '/initial'))); + test('window.defaultRouteName should reset after navigation platform message', + () async { + await window.debugInitializeHistory(TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ), useSingle: true); // Reading it multiple times should return the same value. expect(window.defaultRouteName, '/initial'); expect(window.defaultRouteName, '/initial'); @@ -57,45 +61,45 @@ void testMain() { }); test('can disable location strategy', () async { - await window.debugSwitchBrowserHistory(useSingle: true); - final testStrategy = TestLocationStrategy.fromEntry( + // Disable URL strategy. + expect(() => jsSetUrlStrategy(null), returnsNormally); + // History should be initialized. + expect(window.browserHistory, isNotNull); + // But without a URL strategy. + expect(window.browserHistory.urlStrategy, isNull); + // Current path is always "/" in this case. + expect(window.browserHistory.currentPath, '/'); + + // Perform some navigation operations. + routeInfomrationUpdated('/foo/bar', null); + // Path should not be updated because URL strategy is disabled. + expect(window.browserHistory.currentPath, '/'); + }); + + test('js interop throws on wrong type', () { + expect(() => jsSetUrlStrategy(123), throwsA(anything)); + expect(() => jsSetUrlStrategy('foo'), throwsA(anything)); + expect(() => jsSetUrlStrategy(false), throwsA(anything)); + }); + + test('cannot set url strategy after it is initialized', () async { + final testStrategy = TestUrlStrategy.fromEntry( TestHistoryEntry('initial state', null, '/'), ); - await setStrategy(testStrategy); - - expect(window.locationStrategy, testStrategy); - // A single listener should've been setup. - expect(testStrategy.listeners, hasLength(1)); - // The initial entry should be there, plus another "flutter" entry. - expect(testStrategy.history, hasLength(2)); - expect(testStrategy.history[0].state, {'origin': true, 'state': 'initial state'}); - expect(testStrategy.history[1].state, {'flutter': true}); - expect(testStrategy.currentEntry, testStrategy.history[1]); - - // Now, let's disable location strategy and make sure things get cleaned up. - expect(() => jsSetLocationStrategy(null), returnsNormally); - // The locationStrategy is teared down asynchronously. - await Future.delayed(Duration.zero); - expect(window.locationStrategy, isNull); - - // The listener is removed asynchronously. - await Future.delayed(const Duration(milliseconds: 10)); - - // No more listeners. - expect(testStrategy.listeners, isEmpty); - // History should've moved back to the initial state. - expect(testStrategy.history[0].state, "initial state"); - expect(testStrategy.currentEntry, testStrategy.history[0]); + await window.debugInitializeHistory(testStrategy, useSingle: true); + + expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); }); - test('js interop throws on wrong type', () { - expect(() => jsSetLocationStrategy(123), throwsA(anything)); - expect(() => jsSetLocationStrategy('foo'), throwsA(anything)); - expect(() => jsSetLocationStrategy(false), throwsA(anything)); + test('cannot set url strategy more than once', () async { + // First time is okay. + expect(() => jsSetUrlStrategy(null), returnsNormally); + // Second time is not allowed. + expect(() => jsSetUrlStrategy(null), throwsA(isAssertionError)); }); } -void jsSetLocationStrategy(dynamic strategy) { +void jsSetUrlStrategy(dynamic strategy) { js_util.callMethod( html.window, '_flutter_web_set_location_strategy', From b2ace0cccb441e320beb9701015a323e97a1b5d2 Mon Sep 17 00:00:00 2001 From: Hamdi Kahloun <32666446+hamdikahloun@users.noreply.github.com> Date: Thu, 8 Oct 2020 22:36:51 +0100 Subject: [PATCH 034/219] SecurityException: Permission Denial (#21290) Fix `java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider` Fixes flutter/flutter#66108 Co-authored-by: Dan Field --- .../plugin/platform/PlatformPlugin.java | 27 +++++++++++++++---- .../plugin/platform/PlatformPluginTest.java | 27 ++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 7237576790394..3f18d9472ede5 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -17,7 +17,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import io.flutter.Log; import io.flutter.embedding.engine.systemchannels.PlatformChannel; +import java.io.FileNotFoundException; import java.util.List; /** Android implementation of the platform plugin. */ @@ -29,6 +31,7 @@ public class PlatformPlugin { private final PlatformChannel platformChannel; private PlatformChannel.SystemChromeStyle currentTheme; private int mEnabledOverlays; + private static final String TAG = "PlatformPlugin"; @VisibleForTesting final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = @@ -283,11 +286,25 @@ private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat for if (!clipboard.hasPrimaryClip()) return null; - ClipData clip = clipboard.getPrimaryClip(); - if (clip == null) return null; - - if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) { - return clip.getItemAt(0).coerceToText(activity); + try { + ClipData clip = clipboard.getPrimaryClip(); + if (clip == null) return null; + if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) { + ClipData.Item item = clip.getItemAt(0); + if (item.getUri() != null) + activity.getContentResolver().openTypedAssetFileDescriptor(item.getUri(), "text/*", null); + return item.coerceToText(activity); + } + } catch (SecurityException e) { + Log.w( + TAG, + "Attempted to get clipboard data that requires additional permission(s).\n" + + "See the exception details for which permission(s) are required, and consider adding them to your Android Manifest as described in:\n" + + "https://developer.android.com/guide/topics/permissions/overview", + e); + return null; + } catch (FileNotFoundException e) { + return null; } return null; diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java index 495b332678fe9..0d9e0ea88e16c 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -1,5 +1,6 @@ package io.flutter.plugin.platform; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -10,11 +11,17 @@ import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.ContentResolver; import android.content.Context; +import android.media.RingtoneManager; +import android.net.Uri; import android.view.View; import android.view.Window; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.PlatformChannel.ClipboardContentFormat; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -42,8 +49,9 @@ public void itIgnoresNewHapticEventsOnOldAndroidPlatforms() { platformPlugin.vibrateHapticFeedback(PlatformChannel.HapticFeedbackType.SELECTION_CLICK); } + @Config(sdk = 29) @Test - public void platformPlugin_getClipboardData() { + public void platformPlugin_getClipboardData() throws IOException { ClipboardManager clipboardManager = RuntimeEnvironment.application.getSystemService(ClipboardManager.class); @@ -61,6 +69,23 @@ public void platformPlugin_getClipboardData() { ClipData clip = ClipData.newPlainText("label", "Text"); clipboardManager.setPrimaryClip(clip); assertNotNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); + + ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver(); + Uri uri = Uri.parse("content://media/external_primary/images/media/"); + clip = ClipData.newUri(contentResolver, "URI", uri); + clipboardManager.setPrimaryClip(clip); + assertNull(platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat)); + + uri = + RingtoneManager.getActualDefaultRingtoneUri( + RuntimeEnvironment.application.getApplicationContext(), RingtoneManager.TYPE_RINGTONE); + clip = ClipData.newUri(contentResolver, "URI", uri); + clipboardManager.setPrimaryClip(clip); + String uriData = + platformPlugin.mPlatformMessageHandler.getClipboardData(clipboardFormat).toString(); + InputStream uriInputStream = contentResolver.openInputStream(uri); + InputStream dataInputStream = new ByteArrayInputStream(uriData.getBytes()); + assertEquals(dataInputStream.read(), uriInputStream.read()); } @Test From db507f8f0e0c4a082d98366d092138a0a6d1e232 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 8 Oct 2020 14:37:05 -0700 Subject: [PATCH 035/219] Roll ICU to 146cb611fb2c1f53e63c2e59bd735d7a8ac6ec8c (#21606) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 6169533db00e5..2d311d5242dc8 100644 --- a/DEPS +++ b/DEPS @@ -143,7 +143,7 @@ deps = { Var('chromium_git') + '/chromium/src/ios.git' + '@' + Var('ios_tools_revision'), 'src/third_party/icu': - Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '8d29692df640668ed7e4d1817715440c4e05697a', + Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '146cb611fb2c1f53e63c2e59bd735d7a8ac6ec8c', 'src/third_party/khronos': Var('chromium_git') + '/chromium/src/third_party/khronos.git' + '@' + '7122230e90547962e0f0c627f62eeed3c701f275', From 2ab369fca1d4625a691ef4121346b2d82be27415 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 8 Oct 2020 23:47:07 +0200 Subject: [PATCH 036/219] fl_method_response.cc: fix lint failures (#21406) Fix lint issues caused by `google-readability-braces-around-statements`. --- shell/platform/linux/fl_method_response.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shell/platform/linux/fl_method_response.cc b/shell/platform/linux/fl_method_response.cc index 5bd1d85252e12..d4c760c213572 100644 --- a/shell/platform/linux/fl_method_response.cc +++ b/shell/platform/linux/fl_method_response.cc @@ -97,16 +97,19 @@ G_MODULE_EXPORT FlValue* fl_method_response_get_result(FlMethodResponse* self, FlValue* details = fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(self)); g_autofree gchar* details_text = nullptr; - if (details != nullptr) + if (details != nullptr) { details_text = fl_value_to_string(details); + } g_autoptr(GString) error_message = g_string_new(""); g_string_append_printf(error_message, "Remote code returned error %s", code); - if (message != nullptr) + if (message != nullptr) { g_string_append_printf(error_message, ": %s", message); - if (details_text != nullptr) + } + if (details_text != nullptr) { g_string_append_printf(error_message, " %s", details_text); + } g_set_error_literal(error, FL_METHOD_RESPONSE_ERROR, FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, error_message->str); @@ -128,8 +131,9 @@ G_MODULE_EXPORT FlMethodSuccessResponse* fl_method_success_response_new( FlMethodSuccessResponse* self = FL_METHOD_SUCCESS_RESPONSE( g_object_new(fl_method_success_response_get_type(), nullptr)); - if (result != nullptr) + if (result != nullptr) { self->result = fl_value_ref(result); + } return self; } From 48687ca3ca6e4f24281ea998789cc21ad840accb Mon Sep 17 00:00:00 2001 From: Dan Field Date: Thu, 8 Oct 2020 14:52:05 -0700 Subject: [PATCH 037/219] Forbid android.util.Log (#21696) --- shell/platform/android/BUILD.gn | 22 +++++++++- shell/platform/android/io/flutter/Log.java | 13 ++++++ .../flutter/app/FlutterActivityDelegate.java | 2 +- .../android/AndroidKeyProcessor.java | 2 +- .../engine/loader/FlutterLoader.java | 2 +- .../engine/loader/ResourceExtractor.java | 2 +- .../plugin/common/BasicMessageChannel.java | 2 +- .../flutter/plugin/common/ErrorLogResult.java | 2 +- .../flutter/plugin/common/EventChannel.java | 2 +- .../plugin/common/FlutterException.java | 2 +- .../flutter/plugin/common/MethodChannel.java | 2 +- .../plugin/common/StandardMessageCodec.java | 2 +- .../platform/PlatformViewsController.java | 2 +- .../platform/SingleViewPresentation.java | 2 +- .../io/flutter/view/AccessibilityBridge.java | 2 +- .../view/AccessibilityViewEmbedder.java | 22 +++++----- .../io/flutter/view/FlutterNativeView.java | 2 +- .../android/io/flutter/view/FlutterView.java | 2 +- tools/android_illegal_imports.py | 44 +++++++++++++++++++ 19 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 tools/android_illegal_imports.py diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 64a5e9b0cced1..0c22c91301648 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -251,6 +251,23 @@ embedding_dependencies_jars = ], "list lines") +action("check_imports") { + script = "//flutter/tools/android_illegal_imports.py" + + sources = android_java_sources + + stamp_file = "$root_out_dir/check_android_imports" + + # File does not actually get created, but GN expects us to have an output here. + outputs = [ stamp_file ] + + args = [ + "--stamp", + rebase_path(stamp_file), + "--files", + ] + rebase_path(android_java_sources) +} + action("flutter_shell_java") { script = "//build/android/gyp/javac.py" depfile = "$target_gen_dir/$target_name.d" @@ -299,7 +316,10 @@ action("flutter_shell_java") { args += rebase_path(sources, root_build_dir) - deps = [ ":gen_android_build_config_java" ] + deps = [ + ":check_imports", + ":gen_android_build_config_java", + ] } action("icudtl_object") { diff --git a/shell/platform/android/io/flutter/Log.java b/shell/platform/android/io/flutter/Log.java index 2fb8d029e79a5..fa31b8476a0f5 100644 --- a/shell/platform/android/io/flutter/Log.java +++ b/shell/platform/android/io/flutter/Log.java @@ -13,6 +13,13 @@ public class Log { private static int logLevel = android.util.Log.DEBUG; + public static int ASSERT = android.util.Log.ASSERT; + public static int DEBUG = android.util.Log.DEBUG; + public static int ERROR = android.util.Log.ERROR; + public static int INFO = android.util.Log.INFO; + public static int VERBOSE = android.util.Log.VERBOSE; + public static int WARN = android.util.Log.WARN; + /** * Sets a log cutoff such that a log level of lower priority than {@code logLevel} is filtered * out. @@ -23,6 +30,12 @@ public static void setLogLevel(int logLevel) { Log.logLevel = logLevel; } + public static void println(@NonNull int level, @NonNull String tag, @NonNull String message) { + if (BuildConfig.DEBUG && logLevel <= level) { + android.util.Log.println(level, tag, message); + } + } + public static void v(@NonNull String tag, @NonNull String message) { if (BuildConfig.DEBUG && logLevel <= android.util.Log.VERBOSE) { android.util.Log.v(tag, message); diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index 970f09775a385..e337f373f7ebe 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -19,12 +19,12 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager.LayoutParams; +import io.flutter.Log; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.util.Preconditions; diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java index 14d7effe56c11..7de46d4d9ebdb 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java @@ -4,12 +4,12 @@ package io.flutter.embedding.android; -import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.Log; import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.plugin.editing.TextInputPlugin; import java.util.AbstractMap.SimpleImmutableEntry; diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 2b7c0879bcb76..e2d97c40ee174 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -10,12 +10,12 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; -import android.util.Log; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.BuildConfig; import io.flutter.FlutterInjector; +import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; import io.flutter.view.VsyncWaiter; diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java index f8714dc813fcd..a0b7382edf3da 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java @@ -11,10 +11,10 @@ import android.content.res.AssetManager; import android.os.AsyncTask; import android.os.Build; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import io.flutter.BuildConfig; +import io.flutter.Log; import java.io.*; import java.util.ArrayList; import java.util.Collection; diff --git a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java index 03fc178498d8e..d94b90c47b86b 100644 --- a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java @@ -4,11 +4,11 @@ package io.flutter.plugin.common; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import io.flutter.BuildConfig; +import io.flutter.Log; import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; import io.flutter.plugin.common.BinaryMessenger.BinaryReply; import java.nio.ByteBuffer; diff --git a/shell/platform/android/io/flutter/plugin/common/ErrorLogResult.java b/shell/platform/android/io/flutter/plugin/common/ErrorLogResult.java index d4acc77ea0f7c..90b1c541df39c 100644 --- a/shell/platform/android/io/flutter/plugin/common/ErrorLogResult.java +++ b/shell/platform/android/io/flutter/plugin/common/ErrorLogResult.java @@ -4,9 +4,9 @@ package io.flutter.plugin.common; -import android.util.Log; import androidx.annotation.Nullable; import io.flutter.BuildConfig; +import io.flutter.Log; /** * An implementation of {@link MethodChannel.Result} that writes error results to the Android log. diff --git a/shell/platform/android/io/flutter/plugin/common/EventChannel.java b/shell/platform/android/io/flutter/plugin/common/EventChannel.java index f7d2e3e84c2f2..fed8b053e93ed 100644 --- a/shell/platform/android/io/flutter/plugin/common/EventChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/EventChannel.java @@ -4,9 +4,9 @@ package io.flutter.plugin.common; -import android.util.Log; import androidx.annotation.UiThread; import io.flutter.BuildConfig; +import io.flutter.Log; import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; import io.flutter.plugin.common.BinaryMessenger.BinaryReply; import java.nio.ByteBuffer; diff --git a/shell/platform/android/io/flutter/plugin/common/FlutterException.java b/shell/platform/android/io/flutter/plugin/common/FlutterException.java index 8748efdc223b5..ffcce9eb046b5 100644 --- a/shell/platform/android/io/flutter/plugin/common/FlutterException.java +++ b/shell/platform/android/io/flutter/plugin/common/FlutterException.java @@ -4,8 +4,8 @@ package io.flutter.plugin.common; -import android.util.Log; import io.flutter.BuildConfig; +import io.flutter.Log; /** Thrown to indicate that a Flutter method invocation failed on the Flutter side. */ public class FlutterException extends RuntimeException { diff --git a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java index 81e50e3b938c3..8b1336a7e63c9 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java @@ -4,11 +4,11 @@ package io.flutter.plugin.common; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import io.flutter.BuildConfig; +import io.flutter.Log; import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; import io.flutter.plugin.common.BinaryMessenger.BinaryReply; import java.io.PrintWriter; diff --git a/shell/platform/android/io/flutter/plugin/common/StandardMessageCodec.java b/shell/platform/android/io/flutter/plugin/common/StandardMessageCodec.java index 9a99de87bf116..625f522cac0a2 100644 --- a/shell/platform/android/io/flutter/plugin/common/StandardMessageCodec.java +++ b/shell/platform/android/io/flutter/plugin/common/StandardMessageCodec.java @@ -4,8 +4,8 @@ package io.flutter.plugin.common; -import android.util.Log; import io.flutter.BuildConfig; +import io.flutter.Log; import java.io.ByteArrayOutputStream; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index c7de3c2c0ec6c..05a6240e67092 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -11,7 +11,6 @@ import android.content.Context; import android.os.Build; import android.util.DisplayMetrics; -import android.util.Log; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; @@ -20,6 +19,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; +import io.flutter.Log; import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index 993f88efb2a8c..b40ef87e5ccc7 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -17,7 +17,6 @@ import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; -import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.View; @@ -29,6 +28,7 @@ import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.Log; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index d7bdf3926432e..cd50894516783 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -15,7 +15,6 @@ import android.os.Bundle; import android.os.Handler; import android.provider.Settings; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; @@ -28,6 +27,7 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; +import io.flutter.Log; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate; import io.flutter.util.Predicate; diff --git a/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java b/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java index 2823eb795d17b..33f18ef579c24 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java +++ b/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java @@ -9,7 +9,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; -import android.util.Log; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; @@ -20,6 +19,7 @@ import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.Log; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -502,9 +502,9 @@ private Long getSourceNodeId(@NonNull AccessibilityNodeInfo node) { try { return (Long) getSourceNodeId.invoke(node); } catch (IllegalAccessException e) { - Log.w(TAG, e); + Log.w(TAG, "Failed to access getSourceNodeId method.", e); } catch (InvocationTargetException e) { - Log.w(TAG, e); + Log.w(TAG, "The getSourceNodeId method threw an exception when invoked.", e); } return null; } @@ -523,9 +523,9 @@ private Long getChildId(@NonNull AccessibilityNodeInfo node, int child) { // type ReflectiveOperationException. As a workaround either create individual // catch statements, or catch Exception. [NewApi] } catch (IllegalAccessException e) { - Log.w(TAG, e); + Log.w(TAG, "Failed to access getChildId method.", e); } catch (InvocationTargetException e) { - Log.w(TAG, e); + Log.w(TAG, "The getChildId method threw an exception when invoked.", e); } } else { try { @@ -536,9 +536,9 @@ private Long getChildId(@NonNull AccessibilityNodeInfo node, int child) { // type ReflectiveOperationException. As a workaround either create individual // catch statements, or catch Exception. [NewApi] } catch (IllegalAccessException e) { - Log.w(TAG, e); + Log.w(TAG, "Failed to access longArrayGetIndex method or the childNodeId field.", e); } catch (InvocationTargetException | ArrayIndexOutOfBoundsException e) { - Log.w(TAG, e); + Log.w(TAG, "The longArrayGetIndex method threw an exception when invoked.", e); } } return null; @@ -555,9 +555,9 @@ private Long getParentNodeId(@NonNull AccessibilityNodeInfo node) { // type ReflectiveOperationException. As a workaround either create individual // catch statements, or catch Exception. [NewApi] } catch (IllegalAccessException e) { - Log.w(TAG, e); + Log.w(TAG, "Failed to access getParentNodeId method.", e); } catch (InvocationTargetException e) { - Log.w(TAG, e); + Log.w(TAG, "The getParentNodeId method threw an exception when invoked.", e); } } @@ -620,9 +620,9 @@ private Long getRecordSourceNodeId(@NonNull AccessibilityRecord node) { try { return (Long) getRecordSourceNodeId.invoke(node); } catch (IllegalAccessException e) { - Log.w(TAG, e); + Log.w(TAG, "Failed to access the getRecordSourceNodeId method.", e); } catch (InvocationTargetException e) { - Log.w(TAG, e); + Log.w(TAG, "The getRecordSourceNodeId method threw an exception when invoked.", e); } return null; } diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java index ea01176ebcec6..2d5ee8c40ad56 100644 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -6,9 +6,9 @@ import android.app.Activity; import android.content.Context; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.UiThread; +import io.flutter.Log; import io.flutter.app.FlutterPluginRegistry; import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.FlutterJNI; diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 9899e1714b255..50598cd7344f4 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -19,7 +19,6 @@ import android.os.Handler; import android.text.format.DateFormat; import android.util.AttributeSet; -import android.util.Log; import android.util.SparseArray; import android.view.DisplayCutout; import android.view.KeyEvent; @@ -41,6 +40,7 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.annotation.UiThread; +import io.flutter.Log; import io.flutter.app.FlutterPluginRegistry; import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.embedding.android.AndroidTouchProcessor; diff --git a/tools/android_illegal_imports.py b/tools/android_illegal_imports.py new file mode 100644 index 0000000000000..2a2d5ac1fbf1f --- /dev/null +++ b/tools/android_illegal_imports.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import os +import subprocess +import sys + +ANDROID_LOG_CLASS = 'android.util.Log' +FLUTTER_LOG_CLASS = 'io.flutter.Log' + +def main(): + parser = argparse.ArgumentParser(description='Checks Flutter Android library for forbidden imports') + parser.add_argument('--stamp', type=str, required=True) + parser.add_argument('--files', type=str, required=True, nargs='+') + args = parser.parse_args() + + open(args.stamp, 'wa').close() + + bad_files = [] + + for file in args.files: + if file.endswith(os.path.join('io', 'flutter', 'Log.java')): + continue + with open(file) as f: + if ANDROID_LOG_CLASS in f.read(): + bad_files.append(file) + + if bad_files: + print('') + print('Illegal import %s detected in the following files:' % ANDROID_LOG_CLASS) + for bad_file in bad_files: + print(' - ' + bad_file) + print('Use %s instead.' % FLUTTER_LOG_CLASS) + print('') + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From fb926b96dca3308c2ae88e6425221e9a664b73d0 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 18:37:03 -0400 Subject: [PATCH 038/219] Roll Dart SDK from 44fa3b9e566c to 4ba58cad60e4 (1 revision) (#21708) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2d311d5242dc8..a621265f74520 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '44fa3b9e566c3aa66c4a0dd9608c435b043190e9', + 'dart_revision': '4ba58cad60e4a366a4fc5fd3df94e34705124812', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 346e7e807b3cc5a8eaa32292cd606dd6def102a6 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 18:42:02 -0400 Subject: [PATCH 039/219] Roll Fuchsia Linux SDK from ZJHmp3INU... to wrXNShr_8... (#21709) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index a621265f74520..24901670d4b0a 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC' + 'version': 'wrXNShr_8ceuCFP4YqU8sotRokTyMkAC0UIW2Ib31VAC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index d6eb7b7290de5..46921eb4c38d6 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: e794e6ce4652a9fdd105bddcb6799529 +Signature: b019af013a3bfb30332af5a42a1210fa UNUSED LICENSES: From 48d837b7eef641d47df29414b5a36d3e7d94b23a Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 8 Oct 2020 16:02:06 -0700 Subject: [PATCH 040/219] Perform selection check in DeleteSelected (#21711) At every call site for TextInputModel::DeleteSelected, we perform a check for a collapsed selection. This moves that check into the method itself. --- shell/platform/common/cpp/text_input_model.cc | 16 ++++++++-------- shell/platform/common/cpp/text_input_model.h | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index c30ff61f4dfe5..8b17a3a26d640 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -52,10 +52,14 @@ bool TextInputModel::SetSelection(size_t base, size_t extent) { return true; } -void TextInputModel::DeleteSelected() { +bool TextInputModel::DeleteSelected() { + if (selection_base_ == selection_extent_) { + return false; + } text_.erase(selection_start(), selection_end() - selection_start()); selection_base_ = selection_start(); selection_extent_ = selection_base_; + return true; } void TextInputModel::AddCodePoint(char32_t c) { @@ -73,9 +77,7 @@ void TextInputModel::AddCodePoint(char32_t c) { } void TextInputModel::AddText(const std::u16string& text) { - if (selection_base_ != selection_extent_) { - DeleteSelected(); - } + DeleteSelected(); text_.insert(selection_extent_, text); selection_extent_ += text.length(); selection_base_ = selection_extent_; @@ -89,8 +91,7 @@ void TextInputModel::AddText(const std::string& text) { bool TextInputModel::Backspace() { // If there's a selection, delete it. - if (selection_base_ != selection_extent_) { - DeleteSelected(); + if (DeleteSelected()) { return true; } // There's no selection; delete the preceding codepoint. @@ -106,8 +107,7 @@ bool TextInputModel::Backspace() { bool TextInputModel::Delete() { // If there's a selection, delete it. - if (selection_base_ != selection_extent_) { - DeleteSelected(); + if (DeleteSelected()) { return true; } // There's no selection; delete the following codepoint. diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index 866969fc885a6..fc2bd85ec848f 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -110,7 +110,11 @@ class TextInputModel { int selection_extent() const { return selection_extent_; } private: - void DeleteSelected(); + // Deletes the current selection, if any. + // + // Returns true if any text is deleted. The selection base and extent are + // reset to the start of the selected range. + bool DeleteSelected(); std::u16string text_; size_t selection_base_ = 0; From 349889833c6f66586d775b7d0978a870b48ebd57 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 8 Oct 2020 23:07:02 -0400 Subject: [PATCH 041/219] Roll Dart SDK from 4ba58cad60e4 to fe566e6d08b1 (1 revision) (#21718) --- DEPS | 4 ++-- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 24901670d4b0a..aabec13cfa9b5 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '4ba58cad60e4a366a4fc5fd3df94e34705124812', + 'dart_revision': 'fe566e6d08b1cb99287aebdf56ac8dea4dcef9c9', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -59,7 +59,7 @@ vars = { 'dart_shelf_static_rev': 'v0.2.8', 'dart_shelf_web_socket_tag': '0.2.2+3', 'dart_sse_tag': 'e5cf68975e8e87171a3dc297577aa073454a91dc', - 'dart_stack_trace_tag': 'a958966148516dfa64e2b54c14492175da5cc8e1', + 'dart_stack_trace_tag': '45319bfd2a6da228d8c32b06e1da02ad199373c7', 'dart_stagehand_tag': 'v3.3.9', 'dart_stream_channel_tag': 'c446774fd077c9bdbd6235a7aadc661ef60a9727', 'dart_test_reflective_loader_tag': '0.1.9', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 8e8c2fe3af8ef..6a13ada148070 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 2a8f5eb8828c74900faa11b3195356a4 +Signature: 2363c9ffe111b0ae2d264c5cb46dead4 UNUSED LICENSES: From 36769af3103819b6e977b71f80761091955fa5d9 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 00:47:03 -0400 Subject: [PATCH 042/219] Roll Fuchsia Mac SDK from zhRBO0hCr... to LyP59nILn... (#21720) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index aabec13cfa9b5..1046a06296606 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'zhRBO0hCrBrirs6jHcTrbXyLo0d0PRygZnfrwKRHxR4C' + 'version': 'LyP59nILnJ9QL0a6qKKuda_yXO-bYlK0TNSge82fAvsC' } ], 'condition': 'host_os == "mac"', From 01b942025e3b57e0504ba98f52d681d5aaa8771a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 03:37:02 -0400 Subject: [PATCH 043/219] Roll Dart SDK from fe566e6d08b1 to 1e7250f91944 (1 revision) (#21723) --- DEPS | 4 ++-- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 1046a06296606..45e6286b075a6 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': 'fe566e6d08b1cb99287aebdf56ac8dea4dcef9c9', + 'dart_revision': '1e7250f919448a42a076de3d2384968311ad0368', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -46,7 +46,7 @@ vars = { 'dart_http_retry_tag': '0.1.1', 'dart_http_throttle_tag': '1.0.2', 'dart_intl_tag': '0.16.1', - 'dart_linter_tag': '0.1.120', + 'dart_linter_tag': '0.1.121', 'dart_oauth2_tag': '1.6.0', 'dart_protobuf_rev': '3746c8fd3f2b0147623a8e3db89c3ff4330de760', 'dart_pub_rev': '04e237f78b2302d7f20d0b362554425e8deb8add', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 6a13ada148070..4943d33481458 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 2363c9ffe111b0ae2d264c5cb46dead4 +Signature: e27db4e6a2cdaf30c289042328766087 UNUSED LICENSES: From 808b29a65149e1e0a75d55b1cc3261e4c3dd968f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 11:19:39 -0400 Subject: [PATCH 044/219] Roll Dart SDK from 1e7250f91944 to 712e35f7fd0b (1 revision) (#21725) https://dart.googlesource.com/sdk.git/+log/1e7250f91944..712e35f7fd0b 2020-10-09 dart-luci-ci-builder@dart-ci.iam.gserviceaccount.com Version 2.11.0-205.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter-engine Please CC dart-vm-team@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/master/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 45e6286b075a6..5953900f5fa27 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '1e7250f919448a42a076de3d2384968311ad0368', + 'dart_revision': '712e35f7fd0b335babe651b84d0f1db080f78a04', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 14cb52e8d7f96ac75953618f88dba7cd927f1be3 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 13:30:00 -0400 Subject: [PATCH 045/219] Roll Skia from e17b0501963a to 453f67ff0ade (28 revisions) (#21732) https://skia.googlesource.com/skia.git/+log/e17b0501963a..453f67ff0ade 2020-10-09 ethannicholas@google.com SkSL enum changes 2020-10-09 egdaniel@google.com Suppress vulkan validation layers bug. 2020-10-09 ethannicholas@google.com more SkSL IRNode refactoring 2020-10-09 egdaniel@google.com Make sure the normal GrProgramDesc handles input attachment key. 2020-10-09 michaelludwig@google.com SkDevice::drawSpecial accepts arbitrary matrix v2 2020-10-09 brianosman@google.com Reorganize how SkSL includes are parsed and stored 2020-10-09 johnstiles@google.com Reland "Add sk_Caps.builtinDeterminantSupport and use it in cross()." 2020-10-09 adlai@google.com Reland "Merge GrContext::init into GrDirectContext ..." 2020-10-09 johnstiles@google.com Add test for sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck. 2020-10-09 johnstiles@google.com Add test for sk_Caps.inBlendModesFailRandomlyForAllZeroVec. 2020-10-09 johnstiles@google.com Reland "Put top level FPs into their own functions" 2020-10-09 ethannicholas@google.com refactored more SkSL IRNodes 2020-10-09 egdaniel@google.com Reland "Rename GrStencilAttachment class to generic GrAttachment" 2020-10-09 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Chromium from af82ff2606e9 to 4a368eae5a6f (468 revisions) 2020-10-09 skia-autoroll@skia-public.iam.gserviceaccount.com Roll SwiftShader from 5f4e70b81046 to 952149303d06 (1 revision) 2020-10-09 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 6b4a4a85dfd7 to 793a07e36636 (12 revisions) 2020-10-09 jvanverth@google.com Revert "Add sk_Caps.builtinDeterminantSupport and use it in cross()." 2020-10-09 brianosman@google.com Remove custom iterators from SkSL::Program 2020-10-08 jvanverth@google.com Revert "Rename GrStencilAttachment class to generic GrAttachment" 2020-10-08 johnstiles@google.com Add sk_Caps.builtinDeterminantSupport and use it in cross(). 2020-10-08 michaelludwig@google.com Add SkImageFilters::Shader in place of Paint factory 2020-10-08 michaelludwig@google.com Clip perspective bounds by device clip in SkPDFDevice 2020-10-08 jvanverth@google.com Revert "Merge GrContext::init into GrDirectContext ..." 2020-10-08 jvanverth@google.com Add push constant support to GrCaps. 2020-10-08 adlai@google.com Merge GrContext::init into GrDirectContext ... 2020-10-08 bsalomon@google.com Expose ManagedBackendTexture from BackendTextureImageFactory. 2020-10-08 kjlubick@google.com [canvaskit] Attempt to turn off rtti 2020-10-08 johnstiles@google.com Use SkSTArray to track CFG exits instead of std. If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jvanverth@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/master/autoroll/README.md --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 5953900f5fa27..733244c3a217e 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'e17b0501963aac4840e280c7ac175fd38e4a0c9d', + 'skia_revision': '453f67ff0adeb1d4c0274fa0493eb788d046c14c', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 14935a6911c8c..366b01b8282a5 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: c05ecdf25a4f555ea48de1e26068b217 +Signature: 57bfe5341c44882bab9791c862ab34b2 UNUSED LICENSES: From f69bb0447744b76bcb2a0788e38ae03e3f4d9c08 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 13:52:01 -0400 Subject: [PATCH 046/219] Roll Fuchsia Mac SDK from LyP59nILn... to lqn8xmlDn... (#21733) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 733244c3a217e..65947182fb2cc 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'LyP59nILnJ9QL0a6qKKuda_yXO-bYlK0TNSge82fAvsC' + 'version': 'lqn8xmlDnsqD12MQd7B8MnZXCxRxPXPWjgjgFfrKBq0C' } ], 'condition': 'host_os == "mac"', From a1a89797b9c7954155b01784e086e034b8376ca0 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 15:47:02 -0400 Subject: [PATCH 047/219] Roll Dart SDK from 712e35f7fd0b to 06536d68ca0f (2 revisions) (#21736) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 65947182fb2cc..a2af8ac68e263 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '712e35f7fd0b335babe651b84d0f1db080f78a04', + 'dart_revision': '06536d68ca0f27528b0bf729f4b8d673ed14beda', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 4943d33481458..301be45916a8c 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: e27db4e6a2cdaf30c289042328766087 +Signature: e1e238bf83ff046e90191a5521ba595f UNUSED LICENSES: From 0989304314f047498ebbf1d338aa3c2696e39809 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 16:17:01 -0400 Subject: [PATCH 048/219] Roll Fuchsia Linux SDK from wrXNShr_8... to EBX49sN_X... (#21729) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index a2af8ac68e263..4b6c3b3011f2c 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'wrXNShr_8ceuCFP4YqU8sotRokTyMkAC0UIW2Ib31VAC' + 'version': 'EBX49sN_XBGH3GkrllJ1SToL2Hlv89TmoSmmNyPuAfwC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 46921eb4c38d6..fa7003c59e071 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: b019af013a3bfb30332af5a42a1210fa +Signature: 527d21c239c82c16b39408812dbfcdf3 UNUSED LICENSES: From 15ac210024105fbc8620aa81a2044f25e6a40b96 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 16:32:02 -0400 Subject: [PATCH 049/219] Roll Skia from 453f67ff0ade to 269e43fd9830 (11 revisions) (#21739) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 4b6c3b3011f2c..710c059080b3d 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '453f67ff0adeb1d4c0274fa0493eb788d046c14c', + 'skia_revision': '269e43fd983047330828c58019807a880659d2d6', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 366b01b8282a5..55ace0722d20c 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 57bfe5341c44882bab9791c862ab34b2 +Signature: 2ff131c66c8a2dd98f34a0aab63c506b UNUSED LICENSES: From 5c1960284a1015456af82d0ad61426cf71a2fac4 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Fri, 9 Oct 2020 13:42:01 -0700 Subject: [PATCH 050/219] Fix filesystem access prior to macOS 10.15 (#21740) --- .../darwin/macos/framework/Source/FlutterEngine.mm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 6d0de14a71cfa..b5356181e3bce 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -7,7 +7,6 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #include -#include #include #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" @@ -324,24 +323,25 @@ - (UniqueAotDataPtr)loadAOTData:(NSString*)assetsDir { return nullptr; } + BOOL isDirOut = false; // required for NSFileManager fileExistsAtPath. + NSFileManager* fileManager = [NSFileManager defaultManager]; + // This is the location where the test fixture places the snapshot file. // For applications built by Flutter tool, this is in "App.framework". - std::filesystem::path assetsFsDir(assetsDir.UTF8String); - std::filesystem::path elfFile("app_elf_snapshot.so"); - auto fullElfPath = assetsFsDir / elfFile; + NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]]; - if (!std::filesystem::exists(fullElfPath)) { + if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) { return nullptr; } FlutterEngineAOTDataSource source = {}; source.type = kFlutterEngineAOTDataSourceTypeElfPath; - source.elf_path = fullElfPath.c_str(); + source.elf_path = [elfPath cStringUsingEncoding:NSUTF8StringEncoding]; FlutterEngineAOTData data = nullptr; auto result = FlutterEngineCreateAOTData(&source, &data); if (result != kSuccess) { - NSLog(@"Failed to load AOT data from: %@", @(fullElfPath.c_str())); + NSLog(@"Failed to load AOT data from: %@", elfPath); return nullptr; } From 5aed0ee7f6e862f911ec80040946488c95118c3a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 18:32:01 -0400 Subject: [PATCH 051/219] Roll Skia from 269e43fd9830 to 88cda17bbeb8 (3 revisions) (#21742) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 710c059080b3d..dad4842135764 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '269e43fd983047330828c58019807a880659d2d6', + 'skia_revision': '88cda17bbeb8f34273184fd8cd40bc282a001719', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 55ace0722d20c..5bd08f3cce802 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 2ff131c66c8a2dd98f34a0aab63c506b +Signature: 5631ff969ea0de3e53374d25f5256624 UNUSED LICENSES: From ec1d1111718811cfa73925398de3bb58ba66e619 Mon Sep 17 00:00:00 2001 From: Ferhat Date: Fri, 9 Oct 2020 15:52:29 -0700 Subject: [PATCH 052/219] [web] Add ShaderBuilder, change drawVertices to use builder. (#21716) * Move shader.dart into shaders directory * Add Shader builder basic structure and decls * rewrite drawVertices with ShaderBuilder * Fix in parameters in fragment shader to use varying for webgl1 --- ci/licenses_golden/licenses_flutter | 3 +- lib/web_ui/lib/src/engine.dart | 3 +- .../lib/src/engine/html/render_vertices.dart | 125 +++--- .../src/engine/html/{ => shaders}/shader.dart | 0 .../engine/html/shaders/shader_builder.dart | 387 ++++++++++++++++++ .../surface/shaders/shader_builder_test.dart | 208 ++++++++++ 6 files changed, 667 insertions(+), 59 deletions(-) rename lib/web_ui/lib/src/engine/html/{ => shaders}/shader.dart (100%) create mode 100644 lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart create mode 100644 lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 72bfafac4c963..bc5ea238c3ef9 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -489,7 +489,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene_builder.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shader.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface_stats.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/transform.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index dcb19c8a3165f..d8b372ba5387a 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -89,7 +89,8 @@ part 'engine/html/recording_canvas.dart'; part 'engine/html/render_vertices.dart'; part 'engine/html/scene.dart'; part 'engine/html/scene_builder.dart'; -part 'engine/html/shader.dart'; +part 'engine/html/shaders/shader.dart'; +part 'engine/html/shaders/shader_builder.dart'; part 'engine/html/surface.dart'; part 'engine/html/surface_stats.dart'; part 'engine/html/transform.dart'; diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 11d400bfc5f5b..0720dd4a2a864 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -77,51 +77,6 @@ abstract class _GlRenderer { /// This class gets instantiated on demand by Vertices constructor. For apps /// that don't use Vertices WebGlRenderer will be removed from release binary. class _WebGlRenderer implements _GlRenderer { - // Vertex shader transforms pixel space [Vertices.positions] to - // final clipSpace -1..1 coordinates with inverted Y Axis. - static const _vertexShaderTriangle = ''' - #version 300 es - layout (location=0) in vec4 position; - layout (location=1) in vec4 color; - uniform mat4 u_ctransform; - uniform vec4 u_scale; - uniform vec4 u_shift; - out vec4 vColor; - void main() { - gl_Position = ((u_ctransform * position) * u_scale) + u_shift; - vColor = color.zyxw; - }'''; - // This fragment shader enables Int32List of colors to be passed directly - // to gl context buffer for rendering by decoding RGBA8888. - static const _fragmentShaderTriangle = ''' - #version 300 es - precision highp float; - in vec4 vColor; - out vec4 fragColor; - void main() { - fragColor = vColor; - }'''; - - // WebGL 1 version of shaders above for compatibility with Safari. - static const _vertexShaderTriangleEs1 = ''' - attribute vec4 position; - attribute vec4 color; - uniform mat4 u_ctransform; - uniform vec4 u_scale; - uniform vec4 u_shift; - varying vec4 vColor; - void main() { - gl_Position = ((u_ctransform * position) * u_scale) + u_shift; - vColor = color.zyxw; - }'''; - // WebGL 1 version of shaders above for compatibility with Safari. - static const _fragmentShaderTriangleEs1 = ''' - precision highp float; - varying vec4 vColor; - void main() { - gl_FragColor = vColor; - }'''; - @override void drawVertices( html.CanvasRenderingContext2D? context, @@ -161,16 +116,16 @@ class _WebGlRenderer implements _GlRenderer { if (widthInPixels == 0 || heightInPixels == 0) { return; } + final String vertexShader = _writeVerticesVertexShader(); + final String fragmentShader = _writeVerticesFragmentShader(); _GlContext gl = _OffscreenCanvas.createGlContext(widthInPixels, heightInPixels)!; - _GlProgram glProgram = webGLVersion == 1 - ? gl.useAndCacheProgram( - _vertexShaderTriangleEs1, _fragmentShaderTriangleEs1)! - : gl.useAndCacheProgram( - _vertexShaderTriangle, _fragmentShaderTriangle)!; - - Object? transformUniform = gl.getUniformLocation(glProgram.program, 'u_ctransform'); - Matrix4 transformAtOffset = transform.clone()..translate(-offsetX, -offsetY); + _GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader)!; + + Object? transformUniform = gl.getUniformLocation(glProgram.program, + 'u_ctransform'); + Matrix4 transformAtOffset = transform.clone() + ..translate(-offsetX, -offsetY); gl.setUniformMatrix4fv(transformUniform, false, transformAtOffset.storage); // Set uniform to scale 0..width/height pixels coordinates to -1..1 @@ -186,8 +141,11 @@ class _WebGlRenderer implements _GlRenderer { assert(positionsBuffer != null); // ignore: unnecessary_null_comparison gl.bindArrayBuffer(positionsBuffer); gl.bufferData(positions, gl.kStaticDraw); + Object? positionLoc = gl.getAttribLocation(glProgram.program, 'position'); js_util.callMethod( - gl.glContext!, 'vertexAttribPointer', [0, 2, gl.kFloat, false, 0, 0]); + gl.glContext!, 'vertexAttribPointer', [ + positionLoc, 2, gl.kFloat, false, 0, 0, + ]); gl.enableVertexAttribArray(0); // Setup color buffer. @@ -195,9 +153,9 @@ class _WebGlRenderer implements _GlRenderer { gl.bindArrayBuffer(colorsBuffer); // Buffer kBGRA_8888. gl.bufferData(vertices._colors, gl.kStaticDraw); - + Object? colorLoc = gl.getAttribLocation(glProgram.program, 'color'); js_util.callMethod(gl.glContext!, 'vertexAttribPointer', - [1, 4, gl.kUnsignedByte, true, 0, 0]); + [colorLoc, 4, gl.kUnsignedByte, true, 0, 0]); gl.enableVertexAttribArray(1); gl.clear(); final int vertexCount = positions.length ~/ 2; @@ -209,6 +167,51 @@ class _WebGlRenderer implements _GlRenderer { context.restore(); } + /// Vertex shader transforms pixel space [Vertices.positions] to + /// final clipSpace -1..1 coordinates with inverted Y Axis. + /// #version 300 es + /// layout (location=0) in vec4 position; + /// layout (location=1) in vec4 color; + /// uniform mat4 u_ctransform; + /// uniform vec4 u_scale; + /// uniform vec4 u_shift; + /// out vec4 vColor; + /// void main() { + /// gl_Position = ((u_ctransform * position) * u_scale) + u_shift; + /// v_color = color.zyxw; + /// } + String _writeVerticesVertexShader() { + ShaderBuilder builder = ShaderBuilder(webGLVersion); + builder.addIn(ShaderType.kVec4, name: 'position'); + builder.addIn(ShaderType.kVec4, name: 'color'); + builder.addUniform(ShaderType.kMat4, name: 'u_ctransform'); + builder.addUniform(ShaderType.kVec4, name: 'u_scale'); + builder.addUniform(ShaderType.kVec4, name: 'u_shift'); + builder.addOut(ShaderType.kVec4, name: 'v_color'); + ShaderMethod method = builder.addMethod('main'); + method.addStatement('gl_Position = ((u_ctransform * position) * u_scale) + u_shift;'); + method.addStatement('v_color = color.zyxw;'); + return builder.build(); + } + + /// This fragment shader enables Int32List of colors to be passed directly + /// to gl context buffer for rendering by decoding RGBA8888. + /// #version 300 es + /// precision mediump float; + /// in vec4 vColor; + /// out vec4 fragColor; + /// void main() { + /// fragColor = vColor; + /// } + String _writeVerticesFragmentShader() { + ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion); + builder.floatPrecision = ShaderPrecision.kMedium; + builder.addIn(ShaderType.kVec4, name:'v_color'); + ShaderMethod method = builder.addMethod('main'); + method.addStatement('${builder.fragmentColor.name} = v_color;'); + return builder.build(); + } + @override void drawHairline(html.CanvasRenderingContext2D? _ctx, Float32List positions) { assert(positions != null); // ignore: unnecessary_null_comparison @@ -547,7 +550,15 @@ class _GlContext { /// Returns reference to uniform in program. Object? getUniformLocation(Object? program, String uniformName) { return js_util - .callMethod(glContext!, 'getUniformLocation', [program, uniformName]); + .callMethod(glContext!, 'getUniformLocation', + [program, uniformName]); + } + + /// Returns reference to attribute in program. + Object? getAttribLocation(Object? program, String uniformName) { + return js_util + .callMethod(glContext!, 'getAttribLocation', + [program, uniformName]); } /// Sets vec2 uniform values. diff --git a/lib/web_ui/lib/src/engine/html/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart similarity index 100% rename from lib/web_ui/lib/src/engine/html/shader.dart rename to lib/web_ui/lib/src/engine/html/shaders/shader.dart diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart new file mode 100644 index 0000000000000..c07fe90985adc --- /dev/null +++ b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart @@ -0,0 +1,387 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +/// Creates shader program for target webgl version. +/// +/// See spec at https://www.khronos.org/registry/webgl/specs/latest/1.0/. +/// +/// Differences in WebGL2 vs WebGL1. +/// - WebGL2 needs '#version 300 es' to enable the new shading language +/// - vertex attributes have the qualifier 'in' instead of 'attribute' +/// - GLSL 3.00 defines texture and other new and future reserved words. +/// - varying is now called `in`. +/// - GLSL 1.00 has a predefined variable gl_FragColor which now needs to be +/// defined as `out vec4 fragmentColor`. +/// - Texture lookup functions texture2D and textureCube have now been +/// replaced with texture. +/// +/// Example usage: +/// ShaderBuilder builder = ShaderBuilder(WebGlVersion.webgl2); +/// ShaderDeclaration u1 = builder.addUniform(ShaderType.kVec4); +/// ShaderMethod method = builder.addMethod('main'); +/// method.addStatement('${u1.name} = vec4(1.0, 1.0, 1.0, 0.0);'); +/// source = builder.build(); +class ShaderBuilder { + /// WebGL version. + final int version; + final List declarations = []; + final List _methods = []; + + /// Precision for integer variables. + int? integerPrecision; + + /// Precision floating point variables. + int? floatPrecision; + + /// Counter for generating unique name if name is not specified for attribute. + int _attribCounter = 0; + + /// Counter for generating unique name if name is not specified for varying. + int _varyingCounter = 0; + + /// Counter for generating unique name if name is not specified for uniform. + int _uniformCounter = 0; + + /// Counter for generating unique name if name is not specified for constant. + int _constCounter = 0; + + final bool isWebGl2; + final bool _isFragmentShader; + + static const String kOpenGlEs3Header = '#version 300 es'; + + /// Lazily allocated fragment color output. + ShaderDeclaration? _fragmentColorDeclaration; + + ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2, + _isFragmentShader = false; + + ShaderBuilder.fragment(this.version) : + isWebGl2 = version == WebGLVersion.webgl2, + _isFragmentShader = true; + + /// Returns fragment color declaration for fragment shader. + /// + /// This is hard coded for webgl1 as gl_FragColor. + ShaderDeclaration get fragmentColor { + _fragmentColorDeclaration ??= ShaderDeclaration( + isWebGl2 ? 'gFragColor' : 'gl_FragColor', + ShaderType.kVec4, + ShaderStorageQualifier.kVarying); + return _fragmentColorDeclaration!; + } + + /// Adds an attribute. + /// + /// The attribute variable is assigned a value from a object buffer as a + /// series of graphics primitives are rendered. The value is only accessible + /// in the vertex shader. + ShaderDeclaration addIn(int dataType, {String? name}) { + ShaderDeclaration attrib = ShaderDeclaration( + name ?? 'attr_${_attribCounter++}', + dataType, + ShaderStorageQualifier.kAttribute); + declarations.add(attrib); + return attrib; + } + + /// Adds a constant. + ShaderDeclaration addConst(int dataType, String value, {String? name}) { + ShaderDeclaration declaration = ShaderDeclaration.constant( + name ?? 'c_${_constCounter++}', dataType, value); + declarations.add(declaration); + return declaration; + } + + /// Adds a uniform variable. + /// + /// The variable is assigned a value before a gl.draw call. + /// It is accessible in both the vertex and fragment shaders. + /// + ShaderDeclaration addUniform(int dataType, {String? name}) { + ShaderDeclaration uniform = ShaderDeclaration( + name ?? 'uni_${_uniformCounter++}', + dataType, + ShaderStorageQualifier.kUniform); + declarations.add(uniform); + return uniform; + } + + /// Adds a varying variable. + /// + /// The variable is assigned a value by a vertex shader and + /// interpolated across the surface of a graphics primitive for each + /// input to a fragment shader. + /// It can be used in a fragment shader, but not changed. + ShaderDeclaration addOut(int dataType, {String? name}) { + ShaderDeclaration varying = ShaderDeclaration( + name ?? 'output_${_varyingCounter++}', + dataType, + ShaderStorageQualifier.kVarying); + declarations.add(varying); + return varying; + } + + void _writeVariableDeclaration(StringBuffer sb, ShaderDeclaration variable) { + switch (variable.storage) { + case ShaderStorageQualifier.kConst: + _buffer.write('const '); + break; + case ShaderStorageQualifier.kAttribute: + _buffer.write(isWebGl2 ? 'in ' + : _isFragmentShader ? 'varying ' : 'attribute '); + break; + case ShaderStorageQualifier.kUniform: + _buffer.write('uniform '); + break; + case ShaderStorageQualifier.kVarying: + _buffer.write(isWebGl2 ? 'out ' : 'varying '); + break; + } + _buffer.write('${typeToString(variable.dataType)} ${variable.name}'); + if (variable.storage == ShaderStorageQualifier.kConst) { + _buffer.write(' = ${variable.constValue}'); + } + _buffer.writeln(';'); + } + + final StringBuffer _buffer = StringBuffer(); + + static String typeToString(int dataType) { + switch (dataType) { + case ShaderType.kBool: + return 'bool'; + case ShaderType.kInt: + return 'int'; + case ShaderType.kFloat: + return 'float'; + case ShaderType.kBVec2: + return 'bvec2'; + case ShaderType.kBVec3: + return 'bvec3'; + case ShaderType.kBVec4: + return 'bvec4'; + case ShaderType.kIVec2: + return 'ivec2'; + case ShaderType.kIVec3: + return 'ivec3'; + case ShaderType.kIVec4: + return 'ivec4'; + case ShaderType.kVec2: + return 'vec2'; + case ShaderType.kVec3: + return 'vec3'; + case ShaderType.kVec4: + return 'vec4'; + case ShaderType.kMat2: + return 'mat2'; + case ShaderType.kMat3: + return 'mat3'; + case ShaderType.kMat4: + return 'mat4'; + case ShaderType.kSampler1D: + return 'sampler1D'; + case ShaderType.kSampler2D: + return 'sampler2D'; + case ShaderType.kSampler3D: + return 'sampler3D'; + case ShaderType.kVoid: + return 'void'; + } + throw ArgumentError(); + } + + ShaderMethod addMethod(String name) { + final ShaderMethod method = ShaderMethod(name); + _methods.add(method); + return method; + } + + String build() { + // Write header. + if (isWebGl2) { + _buffer.writeln(kOpenGlEs3Header); + } + // Write optional precision. + if (integerPrecision != null) { + _buffer + .writeln('precision ${_precisionToString(integerPrecision!)} int;'); + } + if (floatPrecision != null) { + _buffer + .writeln('precision ${_precisionToString(floatPrecision!)} float;'); + } + if (isWebGl2 && _fragmentColorDeclaration != null) { + _writeVariableDeclaration(_buffer, _fragmentColorDeclaration!); + } + for (ShaderDeclaration decl in declarations) { + _writeVariableDeclaration(_buffer, decl); + } + for (ShaderMethod method in _methods) { + method.write(_buffer); + } + return _buffer.toString(); + } + + String _precisionToString(int precision) => precision == ShaderPrecision.kLow + ? 'lowp' + : precision == ShaderPrecision.kMedium ? 'mediump' : 'highp'; +} + +class ShaderMethod { + ShaderMethod(this.name); + + final String returnType = 'void'; + final String name; + final List _statements = []; + + void addStatement(String statement) { + _statements.add(statement); + } + + void write(StringBuffer buffer) { + buffer.writeln('$returnType $name() {'); + for (String statement in _statements) { + buffer.writeln(statement); + } + buffer.writeln('}'); + } +} + +/// html webgl version qualifier constants. +abstract class WebGLVersion { + // WebGL 1.0 is based on OpenGL ES 2.0 / GLSL 1.00 + static const int webgl1 = 1; + // WebGL 2.0 is based on OpenGL ES 3.0 / GLSL 3.00 + static const int webgl2 = 2; +} + +/// WebGl Shader data types. +abstract class ShaderType { + // Basic types. + static const int kBool = 0; + static const int kInt = 1; + static const int kFloat = 2; + // Vector types. + static const int kBVec2 = 3; + static const int kBVec3 = 4; + static const int kBVec4 = 5; + static const int kIVec2 = 6; + static const int kIVec3 = 7; + static const int kIVec4 = 8; + static const int kVec2 = 9; + static const int kVec3 = 10; + static const int kVec4 = 11; + static const int kMat2 = 12; + static const int kMat3 = 13; + static const int kMat4 = 14; + // Textures. + static const int kSampler1D = 15; + static const int kSampler2D = 16; + static const int kSampler3D = 17; + // Other. + static const int kVoid = 18; +} + +/// Precision of int and float types. +/// +/// Integers: 8 bit, 10 bit and 16 bits. +/// Float: 8 bit. 14 bit and 62 bits. +abstract class ShaderPrecision { + static const int kLow = 0; + static const int kMedium = 1; + static const int kHigh = 2; +} + +/// GL Variable storage qualifiers. +abstract class ShaderStorageQualifier { + static const int kConst = 0; + static const int kAttribute = 1; + static const int kUniform = 2; + static const int kVarying = 3; +} + +/// Shader variable and constant declaration. +class ShaderDeclaration { + final String name; + final int dataType; + final int storage; + final String constValue; + ShaderDeclaration(this.name, this.dataType, this.storage) + : assert(!_isGLSLReservedWord(name)), + constValue = ''; + + /// Constructs a constant. + ShaderDeclaration.constant(this.name, this.dataType, this.constValue) + : storage = ShaderStorageQualifier.kConst; +} + +// These are used only in debug mode to assert if used as variable name. +// https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.10.pdf +const List _kReservedWords = [ + 'attribute', + 'const', + 'uniform', + 'varying', + 'layout', + 'centroid', + 'flat', + 'smooth', + 'noperspective', + 'patch', 'sample', + 'break', 'continue', + 'do', 'for', 'while', 'switch', 'case', 'default', 'if', 'else', + 'subroutine', + 'in', 'out', 'inout', 'float', 'double', 'int', + 'void', + 'bool', 'true', 'false', + 'invariant', + 'discard', 'return', + 'mat2', 'mat3', 'mat4', 'dmat2', 'dmat3', 'dmat4', + 'mat2x2', 'mat2x3', 'mat2x4', 'dmat2x2', 'dmat2x3', 'dmat2x4', + 'mat3x2', 'mat3x3', 'mat3x4', 'dmat3x2', 'dmat3x3', 'dmat3x4', + 'mat4x2', 'mat4x3', 'mat4x4', 'dmat4x2', 'dmat4x3', 'dmat4x4', + 'vec2', 'vec3', 'vec4', 'ivec2', 'ivec3', 'ivec4', 'bvec2', 'bvec3', 'bvec4', + 'dvec2', 'dvec3', 'dvec4', + 'uint', 'uvec2', 'uvec3', 'uvec4', + 'lowp', 'mediump', 'highp', 'precision', + 'sampler1D', 'sampler2D', 'sampler3D', 'samplerCube', + 'sampler1DShadow', 'sampler2DShadow', 'samplerCubeShadow', + 'sampler1DArray', 'sampler2DArray', + 'sampler1DArrayShadow', 'sampler2DArrayShadow', + 'isampler1D', 'isampler2D', 'isampler3D', 'isamplerCube', + 'isampler1DArray', 'isampler2DArray', + 'usampler1D', 'usampler2D', 'usampler3D', 'usamplerCube', + 'usampler1DArray', 'usampler2DArray', + 'sampler2DRect', 'sampler2DRectShadow', 'isampler2DRect', 'usampler2DRect', + 'samplerBuffer', 'isamplerBuffer', 'usamplerBuffer', + 'sampler2DMS', 'isampler2DMS', 'usampler2DMS', + 'sampler2DMSArray', 'isampler2DMSArray', 'usampler2DMSArray', + 'samplerCubeArray', 'samplerCubeArrayShadow', 'isamplerCubeArray', + 'usamplerCubeArray', + 'struct', + 'texture', + + // Reserved for future use, see + // https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.10.pdf + 'active', 'asm', 'cast', 'class', 'common', 'enum', 'extern', 'external', + 'filter', 'fixed', 'fvec2', 'fvec3', 'fvec4', 'goto', 'half', 'hvec2', + 'hvec3', 'hvec4', 'iimage1D', 'iimage1DArray', 'iimage2D', 'iimage2DArray', + 'iimage3D', 'iimageBuffer', 'iimageCube', 'image1D', 'image1DArray', + 'image1DArrayShadow', 'image1DShadow', 'image2D', 'image2DArray', + 'image2DArrayShadow', 'image2DShadow', 'image3D', 'imageBuffer', + 'imageCube', 'inline', 'input', 'interface', 'long', + 'namespace', 'noinline', 'output', 'packed', 'partition', 'public', + 'row_majo', 'short', 'sizeof', 'static', 'superp', 'template', 'this', + 'typedef', 'uimage1D', 'uimage1DArray', 'uimage2D', 'uimage2DArray', + 'uimage3D', 'uimageBuffer', 'uimageCube', 'union', 'unsigned', + 'using', 'volatile', +]; + +bool _isGLSLReservedWord(String name) { + return _kReservedWords.contains(name); +} diff --git a/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart b/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart new file mode 100644 index 0000000000000..3af3db222a870 --- /dev/null +++ b/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart @@ -0,0 +1,208 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' hide window; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + String mat2Sample = 'mat2(1.1, 2.1, 1.2, 2.2)'; + String mat3Sample = 'mat3(1.1, 2.1, 3.1, // first column (not row!)\n' + '1.2, 2.2, 3.2, // second column\n' + '1.3, 2.3, 3.3 // third column\n' + ')'; + String mat4Sample = 'mat3(1.1, 2.1, 3.1, 4.1,\n' + '1.2, 2.2, 3.2, 4.2,\n' + '1.3, 2.3, 3.3, 4.3,\n' + '1.4, 2.4, 3.4, 4.4,\n' + ')'; + + setUpAll(() async { + await webOnlyInitializeEngine(); + }); + + group('Shader Declarations', () { + test('Constant declaration WebGL1', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1); + builder.addConst(ShaderType.kBool, 'false'); + builder.addConst(ShaderType.kInt, '0'); + builder.addConst(ShaderType.kFloat, '1.0'); + builder.addConst(ShaderType.kBVec2, 'bvec2(false, false)'); + builder.addConst(ShaderType.kBVec3, 'bvec3(false, false, true)'); + builder.addConst(ShaderType.kBVec4, 'bvec4(true, true, false, false)'); + builder.addConst(ShaderType.kIVec2, 'ivec2(1, 2)'); + builder.addConst(ShaderType.kIVec3, 'ivec3(1, 2, 3)'); + builder.addConst(ShaderType.kIVec4, 'ivec4(1, 2, 3, 4)'); + builder.addConst(ShaderType.kVec2, 'vec2(1.0, 2.0)'); + builder.addConst(ShaderType.kVec3, 'vec3(1.0, 2.0, 3.0)'); + builder.addConst(ShaderType.kVec4, 'vec4(1.0, 2.0, 3.0, 4.0)'); + builder.addConst(ShaderType.kMat2, mat2Sample); + builder.addConst(ShaderType.kMat2, mat2Sample, name: 'transform1'); + builder.addConst(ShaderType.kMat3, mat3Sample); + builder.addConst(ShaderType.kMat4, mat4Sample); + expect( + builder.build(), + 'const bool c_0 = false;\n' + 'const int c_1 = 0;\n' + 'const float c_2 = 1.0;\n' + 'const bvec2 c_3 = bvec2(false, false);\n' + 'const bvec3 c_4 = bvec3(false, false, true);\n' + 'const bvec4 c_5 = bvec4(true, true, false, false);\n' + 'const ivec2 c_6 = ivec2(1, 2);\n' + 'const ivec3 c_7 = ivec3(1, 2, 3);\n' + 'const ivec4 c_8 = ivec4(1, 2, 3, 4);\n' + 'const vec2 c_9 = vec2(1.0, 2.0);\n' + 'const vec3 c_10 = vec3(1.0, 2.0, 3.0);\n' + 'const vec4 c_11 = vec4(1.0, 2.0, 3.0, 4.0);\n' + 'const mat2 c_12 = $mat2Sample;\n' + 'const mat2 transform1 = ${mat2Sample};\n' + 'const mat3 c_13 = ${mat3Sample};\n' + 'const mat4 c_14 = ${mat4Sample};\n'); + }); + + test('Constant declaration WebGL2', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + builder.addConst(ShaderType.kBool, 'false'); + builder.addConst(ShaderType.kInt, '0'); + builder.addConst(ShaderType.kFloat, '1.0'); + builder.addConst(ShaderType.kBVec2, 'bvec2(false, false)'); + builder.addConst(ShaderType.kBVec3, 'bvec3(false, false, true)'); + builder.addConst(ShaderType.kBVec4, 'bvec4(true, true, false, false)'); + builder.addConst(ShaderType.kIVec2, 'ivec2(1, 2)'); + builder.addConst(ShaderType.kIVec3, 'ivec3(1, 2, 3)'); + builder.addConst(ShaderType.kIVec4, 'ivec4(1, 2, 3, 4)'); + builder.addConst(ShaderType.kVec2, 'vec2(1.0, 2.0)'); + builder.addConst(ShaderType.kVec3, 'vec3(1.0, 2.0, 3.0)'); + builder.addConst(ShaderType.kVec4, 'vec4(1.0, 2.0, 3.0, 4.0)'); + builder.addConst(ShaderType.kMat2, mat2Sample); + builder.addConst(ShaderType.kMat2, mat2Sample, name: 'transform2'); + builder.addConst(ShaderType.kMat3, mat3Sample); + builder.addConst(ShaderType.kMat4, mat4Sample); + expect( + builder.build(), + '#version 300 es\n' + 'const bool c_0 = false;\n' + 'const int c_1 = 0;\n' + 'const float c_2 = 1.0;\n' + 'const bvec2 c_3 = bvec2(false, false);\n' + 'const bvec3 c_4 = bvec3(false, false, true);\n' + 'const bvec4 c_5 = bvec4(true, true, false, false);\n' + 'const ivec2 c_6 = ivec2(1, 2);\n' + 'const ivec3 c_7 = ivec3(1, 2, 3);\n' + 'const ivec4 c_8 = ivec4(1, 2, 3, 4);\n' + 'const vec2 c_9 = vec2(1.0, 2.0);\n' + 'const vec3 c_10 = vec3(1.0, 2.0, 3.0);\n' + 'const vec4 c_11 = vec4(1.0, 2.0, 3.0, 4.0);\n' + 'const mat2 c_12 = $mat2Sample;\n' + 'const mat2 transform2 = ${mat2Sample};\n' + 'const mat3 c_13 = ${mat3Sample};\n' + 'const mat4 c_14 = ${mat4Sample};\n'); + }); + + test('Attribute declaration WebGL1', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1); + builder.addIn(ShaderType.kVec4, name: 'position'); + builder.addIn(ShaderType.kVec4); + expect( + builder.build(), + 'attribute vec4 position;\n' + 'attribute vec4 attr_0;\n'); + }); + + test('in declaration WebGL1', () { + ShaderBuilder builder = ShaderBuilder.fragment(WebGLVersion.webgl1); + builder.addIn(ShaderType.kVec4, name: 'position'); + builder.addIn(ShaderType.kVec4); + expect( + builder.build(), + 'varying vec4 position;\n' + 'varying vec4 attr_0;\n'); + }); + + test('Attribute declaration WebGL2', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + builder.addIn(ShaderType.kVec4, name: 'position'); + builder.addIn(ShaderType.kVec4); + expect( + builder.build(), + '#version 300 es\n' + 'in vec4 position;\n' + 'in vec4 attr_0;\n'); + }); + + test('Uniform declaration WebGL1', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl1); + ShaderDeclaration variable = + builder.addUniform(ShaderType.kVec4, name: 'v1'); + expect(variable.name, 'v1'); + expect(variable.dataType, ShaderType.kVec4); + expect(variable.storage, ShaderStorageQualifier.kUniform); + builder.addUniform(ShaderType.kVec4); + expect( + builder.build(), + 'uniform vec4 v1;\n' + 'uniform vec4 uni_0;\n'); + }); + + test('Uniform declaration WebGL2', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + ShaderDeclaration variable = + builder.addUniform(ShaderType.kVec4, name: 'v1'); + expect(variable.name, 'v1'); + expect(variable.dataType, ShaderType.kVec4); + expect(variable.storage, ShaderStorageQualifier.kUniform); + builder.addUniform(ShaderType.kVec4); + expect( + builder.build(), + '#version 300 es\n' + 'uniform vec4 v1;\n' + 'uniform vec4 uni_0;\n'); + }); + + test('Float precision', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + builder.floatPrecision = ShaderPrecision.kLow; + builder.addUniform(ShaderType.kFloat, name: 'f1'); + expect( + builder.build(), + '#version 300 es\n' + 'precision lowp float;\n' + 'uniform float f1;\n'); + }); + + test('Integer precision', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + builder.integerPrecision = ShaderPrecision.kLow; + builder.addUniform(ShaderType.kInt, name: 'i1'); + expect( + builder.build(), + '#version 300 es\n' + 'precision lowp int;\n' + 'uniform int i1;\n'); + }); + + test('Method', () { + ShaderBuilder builder = ShaderBuilder(WebGLVersion.webgl2); + builder.floatPrecision = ShaderPrecision.kMedium; + ShaderDeclaration variable = + builder.addUniform(ShaderType.kFloat, name: 'f1'); + ShaderMethod m = builder.addMethod('main'); + m.addStatement('f1 = 5.0;'); + expect( + builder.build(), + '#version 300 es\n' + 'precision mediump float;\n' + 'uniform float ${variable.name};\n' + 'void main() {\n' + 'f1 = 5.0;\n' + '}\n'); + }); + }); +} From 11d756a62ed0ddf87a9ce20b219b55300ec6b67d Mon Sep 17 00:00:00 2001 From: George Wright Date: Fri, 9 Oct 2020 16:17:02 -0700 Subject: [PATCH 053/219] Add dart_entrypoint_argc/argv to the FlutterProjectArgs (#21737) --- shell/platform/embedder/embedder.cc | 14 ++++++++++ shell/platform/embedder/embedder.h | 13 +++++++++ shell/platform/embedder/fixtures/main.dart | 7 +++++ .../embedder/tests/embedder_config_builder.cc | 25 +++++++++++++++++ .../embedder/tests/embedder_config_builder.h | 3 +++ .../embedder/tests/embedder_unittests.cc | 27 +++++++++++++++++++ 6 files changed, 89 insertions(+) diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 971ef22024f59..06b45ac5372e1 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1126,6 +1126,20 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, } } + if (SAFE_ACCESS(args, dart_entrypoint_argc, 0) > 0) { + if (SAFE_ACCESS(args, dart_entrypoint_argv, nullptr) == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Could not determine Dart entrypoint arguments " + "as dart_entrypoint_argc " + "was set, but dart_entrypoint_argv was null."); + } + std::vector arguments(args->dart_entrypoint_argc); + for (int i = 0; i < args->dart_entrypoint_argc; ++i) { + arguments[i] = std::string{args->dart_entrypoint_argv[i]}; + } + settings.dart_entrypoint_args = std::move(arguments); + } + if (!run_configuration.IsValid()) { return LOG_EMBEDDER_ERROR( kInvalidArguments, diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 8ed2dadfac37f..51d832bfe5397 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -1368,6 +1368,19 @@ typedef struct { /// matches what the platform would natively resolve to as possible. FlutterComputePlatformResolvedLocaleCallback compute_platform_resolved_locale_callback; + + /// The command line argument count for arguments passed through to the Dart + /// entrypoint. + int dart_entrypoint_argc; + + /// The command line arguments passed through to the Dart entrypoint. The + /// strings must be `NULL` terminated. + /// + /// The strings will be copied out and so any strings passed in here can + /// be safely collected after initializing the engine with + /// `FlutterProjectArgs`. + const char* const* dart_entrypoint_argv; + } FlutterProjectArgs; //------------------------------------------------------------------------------ diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index de4cf422b662f..5dae4e494745c 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -733,3 +733,10 @@ void render_targets_are_recycled() { }; window.scheduleFrame(); } + +void nativeArgumentsCallback(List args) native 'NativeArgumentsCallback'; + +@pragma('vm:entry-point') +void dart_entrypoint_args(List args) { + nativeArgumentsCallback(args); +} diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 5c1f741996257..93d4c6af916f2 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -217,6 +217,14 @@ void EmbedderConfigBuilder::AddCommandLineArgument(std::string arg) { command_line_arguments_.emplace_back(std::move(arg)); } +void EmbedderConfigBuilder::AddDartEntrypointArgument(std::string arg) { + if (arg.size() == 0) { + return; + } + + dart_entrypoint_arguments_.emplace_back(std::move(arg)); +} + void EmbedderConfigBuilder::SetPlatformTaskRunner( const FlutterTaskRunnerDescription* runner) { if (runner == nullptr) { @@ -317,6 +325,23 @@ UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { project_args.command_line_argc = 0; } + std::vector dart_args; + dart_args.reserve(dart_entrypoint_arguments_.size()); + + for (const auto& arg : dart_entrypoint_arguments_) { + dart_args.push_back(arg.c_str()); + } + + if (dart_args.size() > 0) { + project_args.dart_entrypoint_argv = dart_args.data(); + project_args.dart_entrypoint_argc = dart_args.size(); + } else { + // Clear it out in case this is not the first engine launch from the + // embedder config builder. + project_args.dart_entrypoint_argv = nullptr; + project_args.dart_entrypoint_argc = 0; + } + auto result = run ? FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, &project_args, &context_, &engine) diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 29dc4ef65ad11..4c37d644bc7ac 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -76,6 +76,8 @@ class EmbedderConfigBuilder { void AddCommandLineArgument(std::string arg); + void AddDartEntrypointArgument(std::string arg); + void SetPlatformTaskRunner(const FlutterTaskRunnerDescription* runner); void SetRenderTaskRunner(const FlutterTaskRunnerDescription* runner); @@ -106,6 +108,7 @@ class EmbedderConfigBuilder { FlutterCustomTaskRunners custom_task_runners_ = {}; FlutterCompositor compositor_ = {}; std::vector command_line_arguments_; + std::vector dart_entrypoint_arguments_; UniqueEngine SetupEngine(bool run) const; diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 786b7da063e9f..01309bb7366f3 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -473,6 +473,33 @@ TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { } } +//------------------------------------------------------------------------------ +/// +TEST_F(EmbedderTest, DartEntrypointArgs) { + auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.AddDartEntrypointArgument("foo"); + builder.AddDartEntrypointArgument("bar"); + builder.SetDartEntrypoint("dart_entrypoint_args"); + fml::AutoResetWaitableEvent callback_latch; + std::vector callback_args; + auto nativeArgumentsCallback = [&callback_args, + &callback_latch](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + callback_args = + tonic::DartConverter>::FromArguments( + args, 0, exception); + callback_latch.Signal(); + }; + context.AddNativeCallback("NativeArgumentsCallback", + CREATE_NATIVE_ENTRY(nativeArgumentsCallback)); + auto engine = builder.LaunchEngine(); + callback_latch.Wait(); + ASSERT_EQ(callback_args[0], "foo"); + ASSERT_EQ(callback_args[1], "bar"); +} + //------------------------------------------------------------------------------ /// These snapshots may be materialized from symbols and the size field may not /// be relevant. Since this information is redundant, engine launch should not From 85b0031f73544e448354047dc6a236c0b0808252 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 9 Oct 2020 16:29:16 -0700 Subject: [PATCH 054/219] Migration to PlatformDispatcher and multi-window (#20496) This is a PR for converting the dart:ui code in the engine to use a multi-window API. The goal here is to convert from the window singleton to an API that has the concept of multiple windows. Also, I'm matching up the new PlatformDispatcher class to talk directly to the PlatformConfiguration class in the engine. I'm not attempting to actually enable creating multiple windows here, just migrate to an API that has a concept of multiple windows. The multi-window API in this PR currently only ever creates one window. The design doc for this change is here. The major changes in this PR: Move the platfom-specific attributes out of Window, and into the new PlatformDispatcher class that holds all of the platform state, so that the platform code need only update the configuration on this class. Create FlutterView, FlutterWindow, and SingletonFlutterWindow classes to separate out the concepts of a view (of which there may be multiple in a window), a window (of which there may be multiple on a screen, and they host views), and a window where there is only ever expected to be one (this hosts the entire API of the former Window class, and will eventually be the type of the window singleton). Next step after this PR lands: Remove the Window class entirely (it is replaced by SingletonFlutterWindow). Some minor changes in the Framework are needed to switch to using SingletonFlutterWindow directly first. The Window class still exists in this PR, but will be removed as soon as the framework is converted to point to the SingletonFlutterWindow class instead. They share the same API, just have different names (Window is currently a subclass of SingletonFlutterWindow). The intention is that the Window name will be freed up to use as a widget class name in the framework for managing windows. The singleton called window will remain, and keep the same API it has now. --- ci/licenses_golden/licenses_flutter | 3 + lib/ui/compositing.dart | 22 +- lib/ui/compositing/scene.cc | 3 +- lib/ui/dart_ui.gni | 1 + lib/ui/hooks.dart | 225 +-- lib/ui/natives.dart | 2 +- lib/ui/painting/canvas.cc | 2 +- lib/ui/platform_dispatcher.dart | 1554 +++++++++++++++++ lib/ui/semantics.dart | 17 +- lib/ui/text.dart | 2 +- lib/ui/ui.dart | 1 + lib/ui/window.dart | 1248 ++++--------- lib/ui/window/platform_configuration.cc | 6 +- lib/ui/window/platform_configuration.h | 11 +- .../platform_configuration_unittests.cc | 27 +- lib/ui/window/window.cc | 4 +- lib/ui/window/window.h | 5 +- lib/web_ui/lib/src/engine.dart | 9 +- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 8 +- lib/web_ui/lib/src/engine/canvas_pool.dart | 12 +- .../src/engine/canvaskit/embedded_views.dart | 2 +- .../engine/canvaskit/skia_object_cache.dart | 2 +- lib/web_ui/lib/src/engine/dom_renderer.dart | 8 +- .../lib/src/engine/html/render_vertices.dart | 4 +- .../lib/src/engine/html/surface_stats.dart | 8 +- lib/web_ui/lib/src/engine/keyboard.dart | 6 +- .../lib/src/engine/navigation/history.dart | 12 +- .../lib/src/engine/platform_dispatcher.dart | 923 ++++++++++ .../lib/src/engine/pointer_binding.dart | 4 +- lib/web_ui/lib/src/engine/profiler.dart | 4 +- .../src/engine/semantics/incrementable.dart | 4 +- .../lib/src/engine/semantics/scrollable.dart | 8 +- .../lib/src/engine/semantics/semantics.dart | 4 +- .../lib/src/engine/semantics/tappable.dart | 2 +- .../lib/src/engine/semantics/text_field.dart | 4 +- .../src/engine/text_editing/text_editing.dart | 18 +- lib/web_ui/lib/src/engine/util.dart | 4 +- lib/web_ui/lib/src/engine/window.dart | 853 ++------- .../lib/src/ui/platform_dispatcher.dart | 420 +++++ lib/web_ui/lib/src/ui/text.dart | 1 - lib/web_ui/lib/src/ui/window.dart | 371 +--- lib/web_ui/lib/ui.dart | 1 + .../canvaskit/skia_objects_cache_test.dart | 2 +- lib/web_ui/test/engine/history_test.dart | 12 +- .../engine/surface/platform_view_test.dart | 2 +- lib/web_ui/test/engine/window_test.dart | 28 +- .../engine/canvas_golden_test.dart | 4 +- .../engine/compositing_golden_test.dart | 4 +- lib/web_ui/test/window_test.dart | 2 +- runtime/runtime_controller.cc | 4 +- runtime/runtime_controller.h | 2 +- shell/common/engine.h | 19 +- shell/common/fixtures/shell_test.dart | 40 +- shell/common/shell_test.cc | 2 +- shell/common/shell_unittests.cc | 2 +- .../embedding/android/FlutterView.java | 1 - .../android/platform_view_android_jni_impl.cc | 18 +- .../framework/Source/FlutterViewController.mm | 20 +- .../macos/framework/Source/FlutterEngine.mm | 11 +- shell/platform/embedder/embedder.cc | 7 +- shell/platform/embedder/embedder.h | 4 + shell/platform/embedder/fixtures/main.dart | 180 +- .../dart/window_hooks_integration_test.dart | 146 +- .../lib/src/animated_color_square.dart | 8 +- .../scenario_app/lib/src/channel_util.dart | 4 +- .../lib/src/initial_route_reply.dart | 8 +- .../lib/src/locale_initialization.dart | 9 +- .../scenario_app/lib/src/platform_view.dart | 160 +- .../scenario_app/lib/src/poppable_screen.dart | 10 +- testing/scenario_app/lib/src/scenario.dart | 20 +- testing/scenario_app/lib/src/scenarios.dart | 48 +- .../lib/src/send_text_focus_semantics.dart | 8 +- .../lib/src/touches_scenario.dart | 2 +- 73 files changed, 4066 insertions(+), 2546 deletions(-) create mode 100644 lib/ui/platform_dispatcher.dart create mode 100644 lib/web_ui/lib/src/engine/platform_dispatcher.dart create mode 100644 lib/web_ui/lib/src/ui/platform_dispatcher.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index bc5ea238c3ef9..771fd9a20ca8a 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -371,6 +371,7 @@ FILE: ../../../flutter/lib/ui/painting/single_frame_codec.h FILE: ../../../flutter/lib/ui/painting/vertices.cc FILE: ../../../flutter/lib/ui/painting/vertices.h FILE: ../../../flutter/lib/ui/painting/vertices_unittests.cc +FILE: ../../../flutter/lib/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/ui/plugins.dart FILE: ../../../flutter/lib/ui/plugins/callback_cache.cc FILE: ../../../flutter/lib/ui/plugins/callback_cache.h @@ -499,6 +500,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -553,6 +555,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path_metrics.dart +FILE: ../../../flutter/lib/web_ui/lib/src/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/pointer.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/test_embedding.dart diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index f484f0e53c521..266c1b2ba7300 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -10,8 +10,8 @@ part of dart.ui; /// /// To create a Scene object, use a [SceneBuilder]. /// -/// Scene objects can be displayed on the screen using the -/// [Window.render] method. +/// Scene objects can be displayed on the screen using the [FlutterView.render] +/// method. @pragma('vm:entry-point') class Scene extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated @@ -186,7 +186,7 @@ class PhysicalShapeEngineLayer extends _EngineLayerWrapper { /// Builds a [Scene] containing the given visuals. /// -/// A [Scene] can then be rendered using [Window.render]. +/// A [Scene] can then be rendered using [FlutterView.render]. /// /// To draw graphical operations onto a [Scene], first create a /// [Picture] using a [PictureRecorder] and a [Canvas], and then add @@ -655,13 +655,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// - 0x08: visualizeEngineStatistics - graph UI thread frame times /// Set enabledOptions to 0x0F to enable all the currently defined features. /// - /// The "UI thread" is the thread that includes all the execution of - /// the main Dart isolate (the isolate that can call - /// [Window.render]). The UI thread frame time is the total time - /// spent executing the [Window.onBeginFrame] callback. The "raster - /// thread" is the thread (running on the CPU) that subsequently - /// processes the [Scene] provided by the Dart code to turn it into - /// GPU commands and send it to the GPU. + /// The "UI thread" is the thread that includes all the execution of the main + /// Dart isolate (the isolate that can call [FlutterView.render]). The UI + /// thread frame time is the total time spent executing the + /// [PlatformDispatcher.onBeginFrame] callback. The "raster thread" is the + /// thread (running on the CPU) that subsequently processes the [Scene] + /// provided by the Dart code to turn it into GPU commands and send it to the + /// GPU. /// /// See also the [PerformanceOverlayOption] enum in the rendering library. /// for more details. @@ -802,7 +802,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// Returns a [Scene] containing the objects that have been added to /// this scene builder. The [Scene] can then be displayed on the - /// screen with [Window.render]. + /// screen with [FlutterView.render]. /// /// After calling this function, the scene builder object is invalid and /// cannot be used further. diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 96eac5885d083..46ac6efa534ca 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -42,9 +42,10 @@ Scene::Scene(std::shared_ptr rootLayer, uint32_t rasterizerTracingThreshold, bool checkerboardRasterCacheImages, bool checkerboardOffscreenLayers) { + // Currently only supports a single window. auto viewport_metrics = UIDartState::Current() ->platform_configuration() - ->window() + ->get_window(0) ->viewport_metrics(); layer_tree_ = std::make_unique( diff --git a/lib/ui/dart_ui.gni b/lib/ui/dart_ui.gni index 28cb82e9cd125..11fa74da53f84 100644 --- a/lib/ui/dart_ui.gni +++ b/lib/ui/dart_ui.gni @@ -13,6 +13,7 @@ dart_ui_files = [ "//flutter/lib/ui/lerp.dart", "//flutter/lib/ui/natives.dart", "//flutter/lib/ui/painting.dart", + "//flutter/lib/ui/platform_dispatcher.dart", "//flutter/lib/ui/plugins.dart", "//flutter/lib/ui/pointer.dart", "//flutter/lib/ui/semantics.dart", diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index a911d79f63a8d..8802af5f27c18 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -11,6 +11,7 @@ part of dart.ui; @pragma('vm:entry-point') // ignore: unused_element void _updateWindowMetrics( + Object id, double devicePixelRatio, double width, double height, @@ -27,186 +28,96 @@ void _updateWindowMetrics( double systemGestureInsetBottom, double systemGestureInsetLeft, ) { - window - .._devicePixelRatio = devicePixelRatio - .._physicalSize = Size(width, height) - .._viewPadding = WindowPadding._( - top: viewPaddingTop, - right: viewPaddingRight, - bottom: viewPaddingBottom, - left: viewPaddingLeft) - .._viewInsets = WindowPadding._( - top: viewInsetTop, - right: viewInsetRight, - bottom: viewInsetBottom, - left: viewInsetLeft) - .._padding = WindowPadding._( - top: math.max(0.0, viewPaddingTop - viewInsetTop), - right: math.max(0.0, viewPaddingRight - viewInsetRight), - bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), - left: math.max(0.0, viewPaddingLeft - viewInsetLeft)) - .._systemGestureInsets = WindowPadding._( - top: math.max(0.0, systemGestureInsetTop), - right: math.max(0.0, systemGestureInsetRight), - bottom: math.max(0.0, systemGestureInsetBottom), - left: math.max(0.0, systemGestureInsetLeft)); - _invoke(window.onMetricsChanged, window._onMetricsChangedZone); + PlatformDispatcher.instance._updateWindowMetrics( + id, + devicePixelRatio, + width, + height, + viewPaddingTop, + viewPaddingRight, + viewPaddingBottom, + viewPaddingLeft, + viewInsetTop, + viewInsetRight, + viewInsetBottom, + viewInsetLeft, + systemGestureInsetTop, + systemGestureInsetRight, + systemGestureInsetBottom, + systemGestureInsetLeft, + ); } typedef _LocaleClosure = String? Function(); -String? _localeClosure() { - if (window.locale == null) { - return null; - } - return window.locale.toString(); -} - @pragma('vm:entry-point') // ignore: unused_element -_LocaleClosure? _getLocaleClosure() => _localeClosure; +_LocaleClosure? _getLocaleClosure() => PlatformDispatcher.instance._localeClosure; @pragma('vm:entry-point') // ignore: unused_element void _updateLocales(List locales) { - const int stringsPerLocale = 4; - final int numLocales = locales.length ~/ stringsPerLocale; - final List newLocales = []; - for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { - final String countryCode = locales[localeIndex * stringsPerLocale + 1]; - final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; - - newLocales.add(Locale.fromSubtags( - languageCode: locales[localeIndex * stringsPerLocale], - countryCode: countryCode.isEmpty ? null : countryCode, - scriptCode: scriptCode.isEmpty ? null : scriptCode, - )); - } - window._locales = newLocales; - _invoke(window.onLocaleChanged, window._onLocaleChangedZone); + PlatformDispatcher.instance._updateLocales(locales); } @pragma('vm:entry-point') // ignore: unused_element void _updateUserSettingsData(String jsonData) { - final Map data = json.decode(jsonData) as Map; - if (data.isEmpty) { - return; - } - _updateTextScaleFactor((data['textScaleFactor'] as num).toDouble()); - _updateAlwaysUse24HourFormat(data['alwaysUse24HourFormat'] as bool); - _updatePlatformBrightness(data['platformBrightness'] as String); + PlatformDispatcher.instance._updateUserSettingsData(jsonData); } @pragma('vm:entry-point') // ignore: unused_element void _updateLifecycleState(String state) { - // We do not update the state if the state has already been used to initialize - // the lifecycleState. - if (!window._initialLifecycleStateAccessed) - window._initialLifecycleState = state; -} - - -void _updateTextScaleFactor(double textScaleFactor) { - window._textScaleFactor = textScaleFactor; - _invoke(window.onTextScaleFactorChanged, window._onTextScaleFactorChangedZone); -} - -void _updateAlwaysUse24HourFormat(bool alwaysUse24HourFormat) { - window._alwaysUse24HourFormat = alwaysUse24HourFormat; -} - -void _updatePlatformBrightness(String brightnessName) { - window._platformBrightness = brightnessName == 'dark' ? Brightness.dark : Brightness.light; - _invoke(window.onPlatformBrightnessChanged, window._onPlatformBrightnessChangedZone); + PlatformDispatcher.instance._updateLifecycleState(state); } @pragma('vm:entry-point') // ignore: unused_element void _updateSemanticsEnabled(bool enabled) { - window._semanticsEnabled = enabled; - _invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone); + PlatformDispatcher.instance._updateSemanticsEnabled(enabled); } @pragma('vm:entry-point') // ignore: unused_element void _updateAccessibilityFeatures(int values) { - final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); - if (newFeatures == window._accessibilityFeatures) - return; - window._accessibilityFeatures = newFeatures; - _invoke(window.onAccessibilityFeaturesChanged, window._onAccessibilityFeaturesChangedZone); + PlatformDispatcher.instance._updateAccessibilityFeatures(values); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { - if (name == ChannelBuffers.kControlChannelName) { - try { - channelBuffers.handleMessage(data!); - } catch (ex) { - _printDebug('Message to "$name" caused exception $ex'); - } finally { - window._respondToPlatformMessage(responseId, null); - } - } else if (window.onPlatformMessage != null) { - _invoke3( - window.onPlatformMessage, - window._onPlatformMessageZone, - name, - data, - (ByteData? responseData) { - window._respondToPlatformMessage(responseId, responseData); - }, - ); - } else { - channelBuffers.push(name, data, (ByteData? responseData) { - window._respondToPlatformMessage(responseId, responseData); - }); - } + PlatformDispatcher.instance._dispatchPlatformMessage(name, data, responseId); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPointerDataPacket(ByteData packet) { - if (window.onPointerDataPacket != null) - _invoke1(window.onPointerDataPacket, window._onPointerDataPacketZone, _unpackPointerDataPacket(packet)); + PlatformDispatcher.instance._dispatchPointerDataPacket(packet); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchSemanticsAction(int id, int action, ByteData? args) { - _invoke3( - window.onSemanticsAction, - window._onSemanticsActionZone, - id, - SemanticsAction.values[action]!, - args, - ); + PlatformDispatcher.instance._dispatchSemanticsAction(id, action, args); } @pragma('vm:entry-point') // ignore: unused_element void _beginFrame(int microseconds) { - _invoke1(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds)); + PlatformDispatcher.instance._beginFrame(microseconds); } @pragma('vm:entry-point') // ignore: unused_element void _reportTimings(List timings) { - assert(timings.length % FramePhase.values.length == 0); - final List frameTimings = []; - for (int i = 0; i < timings.length; i += FramePhase.values.length) { - frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); - } - _invoke1(window.onReportTimings, window._onReportTimingsZone, frameTimings); + PlatformDispatcher.instance._reportTimings(timings); } @pragma('vm:entry-point') // ignore: unused_element void _drawFrame() { - _invoke(window.onDrawFrame, window._onDrawFrameZone); + PlatformDispatcher.instance._drawFrame(); } // ignore: always_declare_return_types, prefer_generic_function_type_aliases @@ -219,7 +130,7 @@ typedef _BinaryFunction(Null args, Null message); void _runMainZoned(Function startMainIsolateFunction, Function userMainFunction, List args) { - startMainIsolateFunction((){ + startMainIsolateFunction(() { runZonedGuarded(() { if (userMainFunction is _BinaryFunction) { // This seems to be undocumented but supported by the command line VM. @@ -239,9 +150,10 @@ void _runMainZoned(Function startMainIsolateFunction, void _reportUnhandledException(String error, String stackTrace) native 'PlatformConfiguration_reportUnhandledException'; /// Invokes [callback] inside the given [zone]. -void _invoke(void callback()?, Zone zone) { - if (callback == null) +void _invoke(void Function()? callback, Zone zone) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -253,9 +165,10 @@ void _invoke(void callback()?, Zone zone) { } /// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1(void callback(A a)?, Zone zone, A arg) { - if (callback == null) +void _invoke1(void Function(A a)? callback, Zone zone, A arg) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -267,9 +180,16 @@ void _invoke1(void callback(A a)?, Zone zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg1, A2 arg2, A3 arg3) { - if (callback == null) +void _invoke3( + void Function(A1 a1, A2 a2, A3 a3)? callback, + Zone zone, + A1 arg1, + A2 arg2, + A3 arg3, +) { + if (callback == null) { return; + } assert(zone != null); // ignore: unnecessary_null_comparison @@ -281,54 +201,3 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg }); } } - -// If this value changes, update the encoding code in the following files: -// -// * pointer_data.cc -// * pointer.dart -// * AndroidTouchProcessor.java -const int _kPointerDataFieldCount = 29; - -PointerDataPacket _unpackPointerDataPacket(ByteData packet) { - const int kStride = Int64List.bytesPerElement; - const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; - final int length = packet.lengthInBytes ~/ kBytesPerPointerData; - assert(length * kBytesPerPointerData == packet.lengthInBytes); - final List data = []; - for (int i = 0; i < length; ++i) { - int offset = i * _kPointerDataFieldCount; - data.add(PointerData( - embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), - timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), - change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - device: packet.getInt64(kStride * offset++, _kFakeHostEndian), - pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), - physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), - obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), - scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian) - )); - assert(offset == (i + 1) * _kPointerDataFieldCount); - } - return PointerDataPacket(data: data); -} diff --git a/lib/ui/natives.dart b/lib/ui/natives.dart index 0f2939592add1..13838cb1a512b 100644 --- a/lib/ui/natives.dart +++ b/lib/ui/natives.dart @@ -34,7 +34,7 @@ Future _scheduleFrame( Map parameters ) async { // Schedule the frame. - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); // Always succeed. return developer.ServiceExtensionResponse.result(json.encode({ 'type': 'Success', diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 68ae4898c176a..575e752eeee24 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -468,7 +468,7 @@ void Canvas::drawShadow(const CanvasPath* path, } SkScalar dpr = UIDartState::Current() ->platform_configuration() - ->window() + ->get_window(0) ->viewport_metrics() .device_pixel_ratio; flutter::PhysicalShapeLayer::DrawShadow(canvas_, path->path(), color, diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart new file mode 100644 index 0000000000000..766ac5535081a --- /dev/null +++ b/lib/ui/platform_dispatcher.dart @@ -0,0 +1,1554 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of dart.ui; + +/// Signature of callbacks that have no arguments and return no data. +typedef VoidCallback = void Function(); + +/// Signature for [PlatformDispatcher.onBeginFrame]. +typedef FrameCallback = void Function(Duration duration); + +/// Signature for [PlatformDispatcher.onReportTimings]. +/// +/// {@template dart.ui.TimingsCallback.list} +/// The callback takes a list of [FrameTiming] because it may not be +/// immediately triggered after each frame. Instead, Flutter tries to batch +/// frames together and send all their timings at once to decrease the +/// overhead (as this is available in the release mode). The list is sorted in +/// ascending order of time (earliest frame first). The timing of any frame +/// will be sent within about 1 second (100ms if in the profile/debug mode) +/// even if there are no later frames to batch. The timing of the first frame +/// will be sent immediately without batching. +/// {@endtemplate} +typedef TimingsCallback = void Function(List timings); + +/// Signature for [PlatformDispatcher.onPointerDataPacket]. +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); + +/// Signature for [PlatformDispatcher.onSemanticsAction]. +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); + +/// Signature for responses to platform messages. +/// +/// Used as a parameter to [PlatformDispatcher.sendPlatformMessage] and +/// [PlatformDispatcher.onPlatformMessage]. +typedef PlatformMessageResponseCallback = void Function(ByteData? data); + +/// Signature for [PlatformDispatcher.onPlatformMessage]. +typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); + +// Signature for _setNeedsReportTimings. +typedef _SetNeedsReportTimingsFunc = void Function(bool value); + +/// Signature for [PlatformDispatcher.onConfigurationChanged]. +typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); + +/// Platform event dispatcher singleton. +/// +/// The most basic interface to the host operating system's interface. +/// +/// This is the central entry point for platform messages and configuration +/// events from the platform. +/// +/// It exposes the core scheduler API, the input event callback, the graphics +/// drawing API, and other such core services. +/// +/// It manages the list of the application's [views] and the [screens] attached +/// to the device, as well as the [configuration] of various platform +/// attributes. +/// +/// Consider avoiding static references to this singleton through +/// [PlatformDispatcher.instance] and instead prefer using a binding for +/// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. +/// See [PlatformDispatcher.instance] for more information about why this is +/// preferred. +class PlatformDispatcher { + /// Private constructor, since only dart:ui is supposed to create one of + /// these. Use [instance] to access the singleton. + PlatformDispatcher._() { + _setNeedsReportTimings = _nativeSetNeedsReportTimings; + } + + /// The [PlatformDispatcher] singleton. + /// + /// Consider avoiding static references to this singleton though + /// [PlatformDispatcher.instance] and instead prefer using a binding for + /// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. + /// + /// Static access of this object means that Flutter has few, if any options to + /// fake or mock the given object in tests. Even in cases where Dart offers + /// special language constructs to forcefully shadow such properties, those + /// mechanisms would only be reasonable for tests and they would not be + /// reasonable for a future of Flutter where we legitimately want to select an + /// appropriate implementation at runtime. + /// + /// The only place that `WidgetsBinding.instance.platformDispatcher` is + /// inappropriate is if access to these APIs is required before the binding is + /// initialized by invoking `runApp()` or + /// `WidgetsFlutterBinding.instance.ensureInitialized()`. In that case, it is + /// necessary (though unfortunate) to use the [PlatformDispatcher.instance] + /// object statically. + static PlatformDispatcher get instance => _instance; + static final PlatformDispatcher _instance = PlatformDispatcher._(); + + /// The current platform configuration. + /// + /// If values in this configuration change, [onPlatformConfigurationChanged] + /// will be called. + PlatformConfiguration get configuration => _configuration; + PlatformConfiguration _configuration = const PlatformConfiguration(); + + /// Called when the platform configuration changes. + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; + VoidCallback? _onPlatformConfigurationChanged; + Zone _onPlatformConfigurationChangedZone = Zone.root; + set onPlatformConfigurationChanged(VoidCallback? callback) { + _onPlatformConfigurationChanged = callback; + _onPlatformConfigurationChangedZone = Zone.current; + } + + /// The current list of views, including top level platform windows used by + /// the application. + /// + /// If any of their configurations change, [onMetricsChanged] will be called. + Iterable get views => _views.values; + Map _views = {}; + + // A map of opaque platform view identifiers to view configurations. + Map _viewConfigurations = {}; + + /// A callback that is invoked whenever the [ViewConfiguration] of any of the + /// [views] changes. + /// + /// For example when the device is rotated or when the application is resized + /// (e.g. when showing applications side-by-side on Android), + /// `onMetricsChanged` is called. + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + /// + /// The framework registers with this callback and updates the layout + /// appropriately. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// register for notifications when this is called. + /// * [MediaQuery.of], a simpler mechanism for the same. + VoidCallback? get onMetricsChanged => _onMetricsChanged; + VoidCallback? _onMetricsChanged; + Zone _onMetricsChangedZone = Zone.root; + set onMetricsChanged(VoidCallback? callback) { + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + // + // Updates the metrics of the window with the given id. + void _updateWindowMetrics( + Object id, + double devicePixelRatio, + double width, + double height, + double viewPaddingTop, + double viewPaddingRight, + double viewPaddingBottom, + double viewPaddingLeft, + double viewInsetTop, + double viewInsetRight, + double viewInsetBottom, + double viewInsetLeft, + double systemGestureInsetTop, + double systemGestureInsetRight, + double systemGestureInsetBottom, + double systemGestureInsetLeft, + ) { + final ViewConfiguration previousConfiguration = + _viewConfigurations[id] ?? const ViewConfiguration(); + if (!_views.containsKey(id)) { + _views[id] = FlutterWindow._(id, this); + } + _viewConfigurations[id] = previousConfiguration.copyWith( + window: _views[id], + devicePixelRatio: devicePixelRatio, + geometry: Rect.fromLTWH(0.0, 0.0, width, height), + viewPadding: WindowPadding._( + top: viewPaddingTop, + right: viewPaddingRight, + bottom: viewPaddingBottom, + left: viewPaddingLeft, + ), + viewInsets: WindowPadding._( + top: viewInsetTop, + right: viewInsetRight, + bottom: viewInsetBottom, + left: viewInsetLeft, + ), + padding: WindowPadding._( + top: math.max(0.0, viewPaddingTop - viewInsetTop), + right: math.max(0.0, viewPaddingRight - viewInsetRight), + bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), + left: math.max(0.0, viewPaddingLeft - viewInsetLeft), + ), + systemGestureInsets: WindowPadding._( + top: math.max(0.0, systemGestureInsetTop), + right: math.max(0.0, systemGestureInsetRight), + bottom: math.max(0.0, systemGestureInsetBottom), + left: math.max(0.0, systemGestureInsetLeft), + ), + ); + _invoke(onMetricsChanged, _onMetricsChangedZone); + } + + /// A callback invoked when any view begins a frame. + /// + /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} + /// A callback that is invoked to notify the application that it is an + /// appropriate time to provide a scene using the [SceneBuilder] API and the + /// [FlutterView.render] method. + /// + /// When possible, this is driven by the hardware VSync signal of the attached + /// screen with the highest VSync rate. This is only called if + /// [PlatformDispatcher.scheduleFrame] has been called since the last time + /// this callback was invoked. + /// {@endtemplate} + FrameCallback? get onBeginFrame => _onBeginFrame; + FrameCallback? _onBeginFrame; + Zone _onBeginFrameZone = Zone.root; + set onBeginFrame(FrameCallback? callback) { + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _beginFrame(int microseconds) { + _invoke1( + onBeginFrame, + _onBeginFrameZone, + Duration(microseconds: microseconds), + ); + } + + /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} + /// A callback that is invoked for each frame after [onBeginFrame] has + /// completed and after the microtask queue has been drained. + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. + /// {@endtemplate} + VoidCallback? get onDrawFrame => _onDrawFrame; + VoidCallback? _onDrawFrame; + Zone _onDrawFrameZone = Zone.root; + set onDrawFrame(VoidCallback? callback) { + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _drawFrame() { + _invoke(onDrawFrame, _onDrawFrameZone); + } + + /// A callback that is invoked when pointer data is available. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [GestureBinding], the Flutter framework class which manages pointer + /// events. + PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + PointerDataPacketCallback? _onPointerDataPacket; + Zone _onPointerDataPacketZone = Zone.root; + set onPointerDataPacket(PointerDataPacketCallback? callback) { + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _dispatchPointerDataPacket(ByteData packet) { + if (onPointerDataPacket != null) { + _invoke1( + onPointerDataPacket, + _onPointerDataPacketZone, + _unpackPointerDataPacket(packet), + ); + } + } + + // If this value changes, update the encoding code in the following files: + // + // * pointer_data.cc + // * pointer.dart + // * AndroidTouchProcessor.java + static const int _kPointerDataFieldCount = 29; + + static PointerDataPacket _unpackPointerDataPacket(ByteData packet) { + const int kStride = Int64List.bytesPerElement; + const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; + final int length = packet.lengthInBytes ~/ kBytesPerPointerData; + assert(length * kBytesPerPointerData == packet.lengthInBytes); + final List data = []; + for (int i = 0; i < length; ++i) { + int offset = i * _kPointerDataFieldCount; + data.add(PointerData( + embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), + timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), + change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + device: packet.getInt64(kStride * offset++, _kFakeHostEndian), + pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), + physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), + obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), + scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + )); + assert(offset == (i + 1) * _kPointerDataFieldCount); + } + return PointerDataPacket(data: data); + } + + /// A callback that is invoked to report the [FrameTiming] of recently + /// rasterized frames. + /// + /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use + /// [onReportTimings] directly because [SchedulerBinding.addTimingsCallback] + /// allows multiple callbacks. + /// + /// This can be used to see if the application has missed frames (through + /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high + /// latencies (through [FrameTiming.totalSpan]). + /// + /// Unlike [Timeline], the timing information here is available in the release + /// mode (additional to the profile and the debug mode). Hence this can be + /// used to monitor the application's performance in the wild. + /// + /// {@macro dart.ui.TimingsCallback.list} + /// + /// If this is null, no additional work will be done. If this is not null, + /// Flutter spends less than 0.1ms every 1 second to report the timings + /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for + /// 60fps), or 0.01% CPU usage per second. + TimingsCallback? get onReportTimings => _onReportTimings; + TimingsCallback? _onReportTimings; + Zone _onReportTimingsZone = Zone.root; + set onReportTimings(TimingsCallback? callback) { + if ((callback == null) != (_onReportTimings == null)) { + _setNeedsReportTimings(callback != null); + } + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; + } + + late _SetNeedsReportTimingsFunc _setNeedsReportTimings; + void _nativeSetNeedsReportTimings(bool value) + native 'PlatformConfiguration_setNeedsReportTimings'; + + // Called from the engine, via hooks.dart + void _reportTimings(List timings) { + assert(timings.length % FramePhase.values.length == 0); + final List frameTimings = []; + for (int i = 0; i < timings.length; i += FramePhase.values.length) { + frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); + } + _invoke1(onReportTimings, _onReportTimingsZone, frameTimings); + } + + /// Sends a message to a platform-specific plugin. + /// + /// The `name` parameter determines which plugin receives the message. The + /// `data` parameter contains the message payload and is typically UTF-8 + /// encoded JSON but can be arbitrary data. If the plugin replies to the + /// message, `callback` will be called with the response. + /// + /// The framework invokes [callback] in the same zone in which this method was + /// called. + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { + final String? error = + _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); + if (error != null) + throw Exception(error); + } + + String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data) + native 'PlatformConfiguration_sendPlatformMessage'; + + /// Called whenever this platform dispatcher receives a message from a + /// platform-specific plugin. + /// + /// The `name` parameter determines which plugin sent the message. The `data` + /// parameter is the payload and is typically UTF-8 encoded JSON but can be + /// arbitrary data. + /// + /// Message handlers must call the function given in the `callback` parameter. + /// If the handler does not need to respond, the handler should pass null to + /// the callback. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + PlatformMessageCallback? _onPlatformMessage; + Zone _onPlatformMessageZone = Zone.root; + set onPlatformMessage(PlatformMessageCallback? callback) { + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; + } + + /// Called by [_dispatchPlatformMessage]. + void _respondToPlatformMessage(int responseId, ByteData? data) + native 'PlatformConfiguration_respondToPlatformMessage'; + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback( + PlatformMessageResponseCallback? callback, + ) { + if (callback == null) { + return null; + } + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + // Called from the engine, via hooks.dart + void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { + if (name == ChannelBuffers.kControlChannelName) { + try { + channelBuffers.handleMessage(data!); + } catch (ex) { + _printDebug('Message to "$name" caused exception $ex'); + } finally { + _respondToPlatformMessage(responseId, null); + } + } else if (onPlatformMessage != null) { + _invoke3( + onPlatformMessage, + _onPlatformMessageZone, + name, + data, + (ByteData? responseData) { + _respondToPlatformMessage(responseId, responseData); + }, + ); + } else { + channelBuffers.push(name, data, (ByteData? responseData) { + _respondToPlatformMessage(responseId, responseData); + }); + } + } + + /// Set the debug name associated with this platform dispatcher's root + /// isolate. + /// + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. + /// + /// This can be combined with flutter tools `--isolate-filter` flag to debug + /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. + /// Note that this does not rename any child isolates of the root. + void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; + + /// The embedder can specify data that the isolate can request synchronously + /// on launch. This accessor fetches that data. + /// + /// This data is persistent for the duration of the Flutter application and is + /// available even after isolate restarts. Because of this lifecycle, the size + /// of this data must be kept to a minimum. + /// + /// For asynchronous communication between the embedder and isolate, a + /// platform channel may be used. + ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and + /// [onDrawFrame] callbacks be invoked. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; + + /// Additional accessibility features that may be enabled by the platform. + AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; + + /// A callback that is invoked when the value of [accessibilityFeatures] + /// changes. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; + VoidCallback? _onAccessibilityFeaturesChanged; + Zone _onAccessibilityFeaturesChangedZone = Zone.root; + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateAccessibilityFeatures(int values) { + final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); + final PlatformConfiguration previousConfiguration = configuration; + if (newFeatures == previousConfiguration.accessibilityFeatures) { + return; + } + _configuration = previousConfiguration.copyWith( + accessibilityFeatures: newFeatures, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone,); + _invoke(onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone,); + } + + /// Change the retained semantics data about this platform dispatcher. + /// + /// If [semanticsEnabled] is true, the user has requested that this function + /// be called whenever the semantic content of this platform dispatcher + /// changes. + /// + /// In either case, this function disposes the given update, which means the + /// semantics update cannot be used further. + void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; + + /// The system-reported default locale of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// This is the first locale selected by the user and is the user's primary + /// locale (the locale the device UI is displayed in) + /// + /// This is equivalent to `locales.first` and will provide an empty non-null + /// locale if the [locales] list has not been set or is empty. + Locale? get locale { + if (locales != null && locales!.isNotEmpty) { + return locales!.first; + } + return null; + } + + /// The full system-reported supported locales of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// The list is ordered in order of priority, with lower-indexed locales being + /// preferred over higher-indexed ones. The first element is the primary + /// [locale]. + /// + /// The [onLocaleChanged] callback is called whenever this value changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + List? get locales => configuration.locales; + + /// Performs the platform-native locale resolution. + /// + /// Each platform may return different results. + /// + /// If the platform fails to resolve a locale, then this will return null. + /// + /// This method returns synchronously and is a direct call to + /// platform specific APIs without invoking method channels. + Locale? computePlatformResolvedLocale(List supportedLocales) { + final List supportedLocalesData = []; + for (Locale locale in supportedLocales) { + supportedLocalesData.add(locale.languageCode); + supportedLocalesData.add(locale.countryCode); + supportedLocalesData.add(locale.scriptCode); + } + + final List result = _computePlatformResolvedLocale(supportedLocalesData); + + if (result.isNotEmpty) { + return Locale.fromSubtags( + languageCode: result[0], + countryCode: result[1] == '' ? null : result[1], + scriptCode: result[2] == '' ? null : result[2]); + } + return null; + } + List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; + + /// A callback that is invoked whenever [locale] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onLocaleChanged => _onLocaleChanged; + VoidCallback? _onLocaleChanged; + Zone _onLocaleChangedZone = Zone.root; // ignore: unused_field + set onLocaleChanged(VoidCallback? callback) { + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateLocales(List locales) { + const int stringsPerLocale = 4; + final int numLocales = locales.length ~/ stringsPerLocale; + final PlatformConfiguration previousConfiguration = configuration; + final List newLocales = []; + bool localesDiffer = numLocales != previousConfiguration.locales.length; + for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { + final String countryCode = locales[localeIndex * stringsPerLocale + 1]; + final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; + + newLocales.add(Locale.fromSubtags( + languageCode: locales[localeIndex * stringsPerLocale], + countryCode: countryCode.isEmpty ? null : countryCode, + scriptCode: scriptCode.isEmpty ? null : scriptCode, + )); + if (!localesDiffer && newLocales[localeIndex] != previousConfiguration.locales[localeIndex]) { + localesDiffer = true; + } + } + if (!localesDiffer) { + return; + } + _configuration = previousConfiguration.copyWith(locales: newLocales); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + _invoke(onLocaleChanged, _onLocaleChangedZone); + } + + // Called from the engine, via hooks.dart + String? _localeClosure() { + if (locale == null) { + return null; + } + return locale.toString(); + } + + /// The lifecycle state immediately after dart isolate initialization. + /// + /// This property will not be updated as the lifecycle changes. + /// + /// It is used to initialize [SchedulerBinding.lifecycleState] at startup with + /// any buffered lifecycle state events. + String get initialLifecycleState { + _initialLifecycleStateAccessed = true; + return _initialLifecycleState; + } + + late String _initialLifecycleState; + + /// Tracks if the initial state has been accessed. Once accessed, we will stop + /// updating the [initialLifecycleState], as it is not the preferred way to + /// access the state. + bool _initialLifecycleStateAccessed = false; + + // Called from the engine, via hooks.dart + void _updateLifecycleState(String state) { + // We do not update the state if the state has already been used to initialize + // the lifecycleState. + if (!_initialLifecycleStateAccessed) + _initialLifecycleState = state; + } + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + /// + /// This option is used by [showTimePicker]. + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + /// The system-reported text scale. + /// + /// This establishes the text scaling factor to use when rendering text, + /// according to the user's platform preferences. + /// + /// The [onTextScaleFactorChanged] callback is called whenever this value + /// changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + double get textScaleFactor => configuration.textScaleFactor; + + /// A callback that is invoked whenever [textScaleFactor] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + VoidCallback? _onTextScaleFactorChanged; + Zone _onTextScaleFactorChangedZone = Zone.root; + set onTextScaleFactorChanged(VoidCallback? callback) { + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; + } + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + Brightness get platformBrightness => configuration.platformBrightness; + + /// A callback that is invoked whenever [platformBrightness] changes value. + /// + /// The framework invokes this callback in the same zone in which the callback + /// was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; + VoidCallback? _onPlatformBrightnessChanged; + Zone _onPlatformBrightnessChangedZone = Zone.root; + set onPlatformBrightnessChanged(VoidCallback? callback) { + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateUserSettingsData(String jsonData) { + final Map data = json.decode(jsonData) as Map; + if (data.isEmpty) { + return; + } + + final double textScaleFactor = (data['textScaleFactor'] as num).toDouble(); + final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat'] as bool; + final Brightness platformBrightness = + data['platformBrightness'] as String == 'dark' ? Brightness.dark : Brightness.light; + final PlatformConfiguration previousConfiguration = configuration; + final bool platformBrightnessChanged = + previousConfiguration.platformBrightness != platformBrightness; + final bool textScaleFactorChanged = previousConfiguration.textScaleFactor != textScaleFactor; + final bool alwaysUse24HourFormatChanged = + previousConfiguration.alwaysUse24HourFormat != alwaysUse24HourFormat; + if (!platformBrightnessChanged && !textScaleFactorChanged && !alwaysUse24HourFormatChanged) { + return; + } + _configuration = previousConfiguration.copyWith( + textScaleFactor: textScaleFactor, + alwaysUse24HourFormat: alwaysUse24HourFormat, + platformBrightness: platformBrightness, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + if (textScaleFactorChanged) { + _invoke(onTextScaleFactorChanged, _onTextScaleFactorChangedZone); + } + if (platformBrightnessChanged) { + _invoke(onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); + } + } + + /// Whether the user has requested that [updateSemantics] be called when the + /// semantic contents of a view changes. + /// + /// The [onSemanticsEnabledChanged] callback is called whenever this value + /// changes. + bool get semanticsEnabled => configuration.semanticsEnabled; + + /// A callback that is invoked when the value of [semanticsEnabled] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + VoidCallback? _onSemanticsEnabledChanged; + Zone _onSemanticsEnabledChangedZone = Zone.root; + set onSemanticsEnabledChanged(VoidCallback? callback) { + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _updateSemanticsEnabled(bool enabled) { + final PlatformConfiguration previousConfiguration = configuration; + if (previousConfiguration.semanticsEnabled == enabled) { + return; + } + _configuration = previousConfiguration.copyWith( + semanticsEnabled: enabled, + ); + _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + _invoke(onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); + } + + /// A callback that is invoked whenever the user requests an action to be + /// performed. + /// + /// This callback is used when the user expresses the action they wish to + /// perform based on the semantics supplied by [updateSemantics]. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + SemanticsActionCallback? _onSemanticsAction; + Zone _onSemanticsActionZone = Zone.root; + set onSemanticsAction(SemanticsActionCallback? callback) { + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; + } + + // Called from the engine, via hooks.dart + void _dispatchSemanticsAction(int id, int action, ByteData? args) { + _invoke3( + onSemanticsAction, + _onSemanticsActionZone, + id, + SemanticsAction.values[action]!, + args, + ); + } + + /// The route or path that the embedder requested when the application was + /// launched. + /// + /// This will be the string "`/`" if no particular route was requested. + /// + /// ## Android + /// + /// On Android, calling + /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `createFlutterView` method in your `FlutterActivity` + /// subclass is a suitable time to set the value. The application's + /// `AndroidManifest.xml` file must also be updated to have a suitable + /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). + /// + /// ## iOS + /// + /// On iOS, calling + /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `application:didFinishLaunchingWithOptions:` method is a + /// suitable time to set this value. + /// + /// See also: + /// + /// * [Navigator], a widget that handles routing. + /// * [SystemChannels.navigation], which handles subsequent navigation + /// requests from the embedder. + String get defaultRouteName => _defaultRouteName(); + String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; +} + +/// Configuration of the platform. +/// +/// Immutable class (but can't use @immutable in dart:ui) +class PlatformConfiguration { + /// Const constructor for [PlatformConfiguration]. + const PlatformConfiguration({ + this.accessibilityFeatures = const AccessibilityFeatures._(0), + this.alwaysUse24HourFormat = false, + this.semanticsEnabled = false, + this.platformBrightness = Brightness.light, + this.textScaleFactor = 1.0, + this.locales = const [], + this.defaultRouteName, + }); + + /// Copy a [PlatformConfiguration] with some fields replaced. + PlatformConfiguration copyWith({ + AccessibilityFeatures? accessibilityFeatures, + bool? alwaysUse24HourFormat, + bool? semanticsEnabled, + Brightness? platformBrightness, + double? textScaleFactor, + List? locales, + String? defaultRouteName, + }) { + return PlatformConfiguration( + accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, + alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, + semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, + platformBrightness: platformBrightness ?? this.platformBrightness, + textScaleFactor: textScaleFactor ?? this.textScaleFactor, + locales: locales ?? this.locales, + defaultRouteName: defaultRouteName ?? this.defaultRouteName, + ); + } + + /// Additional accessibility features that may be enabled by the platform. + final AccessibilityFeatures accessibilityFeatures; + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + final bool alwaysUse24HourFormat; + + /// Whether the user has requested that [updateSemantics] be called when the + /// semantic contents of a view changes. + final bool semanticsEnabled; + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + final Brightness platformBrightness; + + /// The system-reported text scale. + final double textScaleFactor; + + /// The full system-reported supported locales of the device. + final List locales; + + /// The route or path that the embedder requested when the application was + /// launched. + final String? defaultRouteName; +} + +/// An immutable view configuration. +class ViewConfiguration { + /// A const constructor for an immutable [ViewConfiguration]. + const ViewConfiguration({ + this.window, + this.devicePixelRatio = 1.0, + this.geometry = Rect.zero, + this.visible = false, + this.viewInsets = WindowPadding.zero, + this.viewPadding = WindowPadding.zero, + this.systemGestureInsets = WindowPadding.zero, + this.padding = WindowPadding.zero, + }); + + /// Copy this configuration with some fields replaced. + ViewConfiguration copyWith({ + FlutterView? window, + double? devicePixelRatio, + Rect? geometry, + bool? visible, + WindowPadding? viewInsets, + WindowPadding? viewPadding, + WindowPadding? systemGestureInsets, + WindowPadding? padding, + }) { + return ViewConfiguration( + window: window ?? this.window, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, + geometry: geometry ?? this.geometry, + visible: visible ?? this.visible, + viewInsets: viewInsets ?? this.viewInsets, + viewPadding: viewPadding ?? this.viewPadding, + systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, + padding: padding ?? this.padding, + ); + } + + /// The top level view into which the view is placed and its geometry is + /// relative to. + /// + /// If null, then this configuration represents a top level view itself. + final FlutterView? window; + + /// The pixel density of the output surface. + final double devicePixelRatio; + + /// The geometry requested for the view on the screen or within its parent + /// window, in logical pixels. + final Rect geometry; + + /// Whether or not the view is currently visible on the screen. + final bool visible; + + /// The view insets, as it intersects with [Screen.viewInsets] for the screen + /// it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.viewInsets] area, [viewInsets] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this view rectangle into + /// which the application can draw, but over which the operating system will + /// likely place system UI, such as the keyboard or system menus, that fully + /// obscures any content. + final WindowPadding viewInsets; + + /// The view insets, as it intersects with [ScreenConfiguration.viewPadding] + /// for the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.viewPadding] area, [viewPadding] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but which may be partially + /// obscured by system UI (such as the system notification area), or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). + final WindowPadding viewPadding; + + /// The view insets, as it intersects with + /// [ScreenConfiguration.systemGestureInsets] for the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.systemGestureInsets] area, [systemGestureInsets] will + /// be [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but where the operating system + /// will consume input gestures for the sake of system navigation. + final WindowPadding systemGestureInsets; + + /// The view insets, as it intersects with [ScreenConfiguration.padding] for + /// the screen it is on. + /// + /// For instance, if the view doesn't overlap the + /// [ScreenConfiguration.padding] area, [padding] will be + /// [WindowPadding.zero]. + /// + /// The number of physical pixels on each side of this screen rectangle into + /// which the application can place a view, but which may be partially + /// obscured by system UI (such as the system notification area), or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). + final WindowPadding padding; + + @override + String toString() { + return '$runtimeType[window: $window, geometry: $geometry]'; + } +} + +/// Various important time points in the lifetime of a frame. +/// +/// [FrameTiming] records a timestamp of each phase for performance analysis. +enum FramePhase { + /// The timestamp of the vsync signal given by the operating system. + /// + /// See also [FrameTiming.vsyncOverhead]. + vsyncStart, + + /// When the UI thread starts building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildStart, + + /// When the UI thread finishes building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildFinish, + + /// When the raster thread starts rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterStart, + + /// When the raster thread finishes rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterFinish, +} + +/// Time-related performance metrics of a frame. +/// +/// If you're using the whole Flutter framework, please use +/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using +/// [PlatformDispatcher.onReportTimings] directly because +/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If +/// [SchedulerBinding] is unavailable, then see [PlatformDispatcher.onReportTimings] +/// for how to get this. +/// +/// The metrics in debug mode (`flutter run` without any flags) may be very +/// different from those in profile and release modes due to the debug overhead. +/// Therefore it's recommended to only monitor and analyze performance metrics +/// in profile and release modes. +class FrameTiming { + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// This constructor is used for unit test only. Real [FrameTiming]s should + /// be retrieved from [PlatformDispatcher.onReportTimings]. + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// List [timestamps] must have the same number of elements as + /// [FramePhase.values]. + /// + /// This constructor is usually only called by the Flutter engine, or a test. + /// To get the [FrameTiming] of your app, see [PlatformDispatcher.onReportTimings]. + FrameTiming._(this._timestamps) + : assert(_timestamps.length == FramePhase.values.length); + + /// This is a raw timestamp in microseconds from some epoch. The epoch in all + /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + + /// The duration to build the frame on the UI thread. + /// + /// The build starts approximately when [PlatformDispatcher.onBeginFrame] is + /// called. The [Duration] in the [PlatformDispatcher.onBeginFrame] callback + /// is exactly the `Duration(microseconds: + /// timestampInMicroseconds(FramePhase.buildStart))`. + /// + /// The build finishes when [FlutterView.render] is called. + /// + /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// To ensure smooth animations of X fps, this should not exceed 1000/X + /// milliseconds. + /// {@endtemplate} + /// {@template dart.ui.FrameTiming.fps_milliseconds} + /// That's about 16ms for 60fps, and 8ms for 120fps. + /// {@endtemplate} + Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + + /// The duration to rasterize the frame on the raster thread. + /// + /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + + /// The duration between receiving the vsync signal and starting building the + /// frame. + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + + /// The timespan between vsync start and raster finish. + /// + /// To achieve the lowest latency on an X fps display, this should not exceed + /// 1000/X milliseconds. + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + /// + /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. + Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + +/// States that an application can be in. +/// +/// The values below describe notifications from the operating system. +/// Applications should not expect to always receive all possible +/// notifications. For example, if the users pulls out the battery from the +/// device, no notification will be sent before the application is suddenly +/// terminated, along with the rest of the operating system. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state +/// from the widgets layer. +enum AppLifecycleState { + /// The application is visible and responding to user input. + resumed, + + /// The application is in an inactive state and is not receiving user input. + /// + /// On iOS, this state corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when in + /// a phone call, responding to a TouchID request, when entering the app + /// switcher or the control center, or when the UIViewController hosting the + /// Flutter app is transitioning. + /// + /// On Android, this corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when + /// another activity is focused, such as a split-screen app, a phone call, + /// a picture-in-picture app, a system dialog, or another window. + /// + /// Apps in this state should assume that they may be [paused] at any time. + inactive, + + /// The application is not currently visible to the user, not responding to + /// user input, and running in the background. + /// + /// When the application is in this state, the engine will not call the + /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame] + /// callbacks. + paused, + + /// The application is still hosted on a flutter engine but is detached from + /// any host views. + /// + /// When the application is in this state, the engine is running without + /// a view. It can either be in the progress of attaching a view when engine + /// was first initializes, or after the view being destroyed due to a Navigator + /// pop. + detached, +} + +/// A representation of distances for each of the four edges of a rectangle, +/// used to encode the view insets and padding that applications should place +/// around their user interface, as exposed by [FlutterView.viewInsets] and +/// [FlutterView.padding]. View insets and padding are preferably read via +/// [MediaQuery.of]. +/// +/// For a generic class that represents distances around a rectangle, see the +/// [EdgeInsets] class. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive +/// notifications when the padding changes. +/// * [MediaQuery.of], for the preferred mechanism for accessing these values. +/// * [Scaffold], which automatically applies the padding in material design +/// applications. +class WindowPadding { + const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); + + /// The distance from the left edge to the first unpadded pixel, in physical pixels. + final double left; + + /// The distance from the top edge to the first unpadded pixel, in physical pixels. + final double top; + + /// The distance from the right edge to the first unpadded pixel, in physical pixels. + final double right; + + /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. + final double bottom; + + /// A window padding that has zeros for each edge. + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); + + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +/// An identifier used to select a user's language and formatting preferences. +/// +/// This represents a [Unicode Language +/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) +/// (i.e. without Locale extensions), except variants are not supported. +/// +/// Locales are canonicalized according to the "preferred value" entries in the +/// [IANA Language Subtag +/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). +/// For example, `const Locale('he')` and `const Locale('iw')` are equal and +/// both have the [languageCode] `he`, because `iw` is a deprecated language +/// subtag that was replaced by the subtag `he`. +/// +/// See also: +/// +/// * [PlatformDispatcher.locale], which specifies the system's currently selected +/// [Locale]. +class Locale { + /// Creates a new Locale object. The first argument is the + /// primary language subtag, the second is the region (also + /// referred to as 'country') subtag. + /// + /// For example: + /// + /// ```dart + /// const Locale swissFrench = Locale('fr', 'CH'); + /// const Locale canadianFrench = Locale('fr', 'CA'); + /// ``` + /// + /// The primary language subtag must not be null. The region subtag is + /// optional. When there is no region/country subtag, the parameter should + /// be omitted or passed `null` instead of an empty-string. + /// + /// The subtag values are _case sensitive_ and must be one of the valid + /// subtags according to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The + /// primary language subtag must be at least two and at most eight lowercase + /// letters, but not four letters. The region region subtag must be two + /// uppercase letters or three digits. See the [Unicode Language + /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) + /// specification. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which also allows a [scriptCode] to be + /// specified. + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + + /// Creates a new Locale object. + /// + /// The keyword arguments specify the subtags of the Locale. + /// + /// The subtag values are _case sensitive_ and must be valid subtags according + /// to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for + /// each of languageCode, scriptCode and countryCode respectively. + /// + /// The [countryCode] subtag is optional. When there is no country subtag, + /// the parameter should be omitted or passed `null` instead of an empty-string. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + + /// The primary language subtag for the locale. + /// + /// This must not be null. It may be 'und', representing 'undefined'. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "language". The string specified must match the case of the + /// string in the registry. + /// + /// Language subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const + /// Locale('he')` and `const Locale('iw')` are equal, and both have the + /// [languageCode] `he`, because `iw` is a deprecated language subtag that was + /// replaced by the subtag `he`. + /// + /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR + /// supplemental + /// data](http://unicode.org/cldr/latest/common/validity/language.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + + /// The script subtag for the locale. + /// + /// This may be null, indicating that there is no specified script subtag. + /// + /// This must be a valid Unicode Language Identifier script subtag as listed + /// in [Unicode CLDR supplemental + /// data](http://unicode.org/cldr/latest/common/validity/script.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + final String? scriptCode; + + /// The region subtag for the locale. + /// + /// This may be null, indicating that there is no specified region subtag. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "region". The string specified must match the case of the + /// string in the registry. + /// + /// Region subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const Locale('de', + /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the + /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was + /// replaced by the subtag `DE`. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other is! Locale) { + return false; + } + final String? countryCode = _countryCode; + final String? otherCountryCode = other.countryCode; + return other.languageCode == languageCode + && other.scriptCode == scriptCode // scriptCode cannot be '' + && (other.countryCode == countryCode // Treat '' as equal to null. + || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null + || countryCode != null && countryCode.isEmpty && other.countryCode == null); + } + + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); + + static Locale? _cachedLocale; + static String? _cachedLocaleString; + + /// Returns a string representing the locale. + /// + /// This identifier happens to be a valid Unicode Locale Identifier using + /// underscores as separator, however it is intended to be used for debugging + /// purposes only. For parsable results, use [toLanguageTag] instead. + @keepToString + @override + String toString() { + if (!identical(_cachedLocale, this)) { + _cachedLocale = this; + _cachedLocaleString = _rawToString('_'); + } + return _cachedLocaleString!; + } + + /// Returns a syntactically valid Unicode BCP47 Locale Identifier. + /// + /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and + /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical + /// details. + String toLanguageTag() => _rawToString('-'); + + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null && scriptCode!.isNotEmpty) + out.write('$separator$scriptCode'); + if (_countryCode != null && _countryCode!.isNotEmpty) + out.write('$separator$countryCode'); + return out.toString(); + } +} \ No newline at end of file diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index 30bce7cc91e47..5769905628fdf 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -625,7 +625,8 @@ class SemanticsFlag { /// An object that creates [SemanticsUpdate] objects. /// /// Once created, the [SemanticsUpdate] objects can be passed to -/// [Window.updateSemantics] to update the semantics conveyed to the user. +/// [PlatformDispatcher.updateSemantics] to update the semantics conveyed to the +/// user. @pragma('vm:entry-point') class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates an empty [SemanticsUpdateBuilder] object. @@ -653,10 +654,10 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// /// The `actions` are a bit field of [SemanticsAction]s that can be undertaken /// by this node. If the user wishes to undertake one of these actions on this - /// node, the [Window.onSemanticsAction] will be called with `id` and one of - /// the possible [SemanticsAction]s. Because the semantics tree is maintained - /// asynchronously, the [Window.onSemanticsAction] callback might be called - /// with an action that is no longer possible. + /// node, the [PlatformDispatcher.onSemanticsAction] will be called with `id` + /// and one of the possible [SemanticsAction]s. Because the semantics tree is + /// maintained asynchronously, the [PlatformDispatcher.onSemanticsAction] + /// callback might be called with an action that is no longer possible. /// /// The `label` is a string that describes this node. The `value` property /// describes the current value of the node as a string. The `increasedValue` @@ -832,8 +833,8 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates a [SemanticsUpdate] object that encapsulates the updates recorded /// by this object. /// - /// The returned object can be passed to [Window.updateSemantics] to actually - /// update the semantics retained by the system. + /// The returned object can be passed to [PlatformDispatcher.updateSemantics] + /// to actually update the semantics retained by the system. SemanticsUpdate build() { final SemanticsUpdate semanticsUpdate = SemanticsUpdate._(); _build(semanticsUpdate); @@ -847,7 +848,7 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// To create a SemanticsUpdate object, use a [SemanticsUpdateBuilder]. /// /// Semantics updates can be applied to the system's retained semantics tree -/// using the [Window.updateSemantics] method. +/// using the [PlatformDispatcher.updateSemantics] method. @pragma('vm:entry-point') class SemanticsUpdate extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 8a03b68c68fa1..96e78678f60a2 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -2273,7 +2273,7 @@ final ByteData _fontChangeMessage = utf8.encoder.convert( ).buffer.asByteData(); FutureOr _sendFontChangeMessage() async { - window.onPlatformMessage?.call( + PlatformDispatcher.instance.onPlatformMessage?.call( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart index fe0e4fa16bcd6..4603fb3ce7774 100644 --- a/lib/ui/ui.dart +++ b/lib/ui/ui.dart @@ -33,6 +33,7 @@ part 'isolate_name_server.dart'; part 'lerp.dart'; part 'natives.dart'; part 'painting.dart'; +part 'platform_dispatcher.dart'; part 'plugins.dart'; part 'pointer.dart'; part 'semantics.dart'; diff --git a/lib/ui/window.dart b/lib/ui/window.dart index a39c5407ad45e..910866379d814 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -5,614 +5,72 @@ // @dart = 2.10 part of dart.ui; -/// Signature of callbacks that have no arguments and return no data. -typedef VoidCallback = void Function(); - -/// Signature for [Window.onBeginFrame]. -typedef FrameCallback = void Function(Duration duration); - -/// Signature for [Window.onReportTimings]. +/// A view into which a Flutter [Scene] is drawn. /// -/// {@template dart.ui.TimingsCallback.list} -/// The callback takes a list of [FrameTiming] because it may not be -/// immediately triggered after each frame. Instead, Flutter tries to batch -/// frames together and send all their timings at once to decrease the -/// overhead (as this is available in the release mode). The list is sorted in -/// ascending order of time (earliest frame first). The timing of any frame -/// will be sent within about 1 second (100ms if in the profile/debug mode) -/// even if there are no later frames to batch. The timing of the first frame -/// will be sent immediately without batching. -/// {@endtemplate} -typedef TimingsCallback = void Function(List timings); - -/// Signature for [Window.onPointerDataPacket]. -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); - -/// Signature for [Window.onSemanticsAction]. -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); - -/// Signature for responses to platform messages. -/// -/// Used as a parameter to [Window.sendPlatformMessage] and -/// [Window.onPlatformMessage]. -typedef PlatformMessageResponseCallback = void Function(ByteData? data); - -/// Signature for [Window.onPlatformMessage]. -typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); - -// Signature for _setNeedsReportTimings. -typedef _SetNeedsReportTimingsFunc = void Function(bool value); - -/// Various important time points in the lifetime of a frame. -/// -/// [FrameTiming] records a timestamp of each phase for performance analysis. -enum FramePhase { - /// The timestamp of the vsync signal given by the operating system. - /// - /// See also [FrameTiming.vsyncOverhead]. - vsyncStart, - - /// When the UI thread starts building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildStart, - - /// When the UI thread finishes building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildFinish, - - /// When the raster thread starts rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterStart, - - /// When the raster thread finishes rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterFinish, -} - -/// Time-related performance metrics of a frame. -/// -/// If you're using the whole Flutter framework, please use -/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using -/// [Window.onReportTimings] directly because -/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If -/// [SchedulerBinding] is unavailable, then see [Window.onReportTimings] for how -/// to get this. -/// -/// The metrics in debug mode (`flutter run` without any flags) may be very -/// different from those in profile and release modes due to the debug overhead. -/// Therefore it's recommended to only monitor and analyze performance metrics -/// in profile and release modes. -class FrameTiming { - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// This constructor is used for unit test only. Real [FrameTiming]s should - /// be retrieved from [Window.onReportTimings]. - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// List [timestamps] must have the same number of elements as - /// [FramePhase.values]. - /// - /// This constructor is usually only called by the Flutter engine, or a test. - /// To get the [FrameTiming] of your app, see [Window.onReportTimings]. - FrameTiming._(List timestamps) - : assert(timestamps.length == FramePhase.values.length), _timestamps = timestamps; - - /// This is a raw timestamp in microseconds from some epoch. The epoch in all - /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - - /// The duration to build the frame on the UI thread. - /// - /// The build starts approximately when [Window.onBeginFrame] is called. The - /// [Duration] in the [Window.onBeginFrame] callback is exactly the - /// `Duration(microseconds: timestampInMicroseconds(FramePhase.buildStart))`. - /// - /// The build finishes when [Window.render] is called. - /// - /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// To ensure smooth animations of X fps, this should not exceed 1000/X - /// milliseconds. - /// {@endtemplate} - /// {@template dart.ui.FrameTiming.fps_milliseconds} - /// That's about 16ms for 60fps, and 8ms for 120fps. - /// {@endtemplate} - Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - - /// The duration to rasterize the frame on the raster thread. - /// - /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - - /// The duration between receiving the vsync signal and starting building the - /// frame. - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - - /// The timespan between vsync start and raster finish. - /// - /// To achieve the lowest latency on an X fps display, this should not exceed - /// 1000/X milliseconds. - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - /// - /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. - Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - -/// States that an application can be in. -/// -/// The values below describe notifications from the operating system. -/// Applications should not expect to always receive all possible -/// notifications. For example, if the users pulls out the battery from the -/// device, no notification will be sent before the application is suddenly -/// terminated, along with the rest of the operating system. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state -/// from the widgets layer. -enum AppLifecycleState { - /// The application is visible and responding to user input. - resumed, - - /// The application is in an inactive state and is not receiving user input. - /// - /// On iOS, this state corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when in - /// a phone call, responding to a TouchID request, when entering the app - /// switcher or the control center, or when the UIViewController hosting the - /// Flutter app is transitioning. - /// - /// On Android, this corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when - /// another activity is focused, such as a split-screen app, a phone call, - /// a picture-in-picture app, a system dialog, or another window. - /// - /// Apps in this state should assume that they may be [paused] at any time. - inactive, - - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. - /// - /// When the application is in this state, the engine will not call the - /// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks. - paused, - - /// The application is still hosted on a flutter engine but is detached from - /// any host views. - /// - /// When the application is in this state, the engine is running without - /// a view. It can either be in the progress of attaching a view when engine - /// was first initializes, or after the view being destroyed due to a Navigator - /// pop. - detached, -} - -/// A representation of distances for each of the four edges of a rectangle, -/// used to encode the view insets and padding that applications should place -/// around their user interface, as exposed by [Window.viewInsets] and -/// [Window.padding]. View insets and padding are preferably read via -/// [MediaQuery.of]. -/// -/// For a generic class that represents distances around a rectangle, see the -/// [EdgeInsets] class. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive -/// notifications when the padding changes. -/// * [MediaQuery.of], for the preferred mechanism for accessing these values. -/// * [Scaffold], which automatically applies the padding in material design -/// applications. -class WindowPadding { - const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); - - /// The distance from the left edge to the first unpadded pixel, in physical pixels. - final double left; - - /// The distance from the top edge to the first unpadded pixel, in physical pixels. - final double top; - - /// The distance from the right edge to the first unpadded pixel, in physical pixels. - final double right; - - /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. - final double bottom; - - /// A window padding that has zeros for each edge. - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); - - @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } -} - -/// An identifier used to select a user's language and formatting preferences. -/// -/// This represents a [Unicode Language -/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) -/// (i.e. without Locale extensions), except variants are not supported. -/// -/// Locales are canonicalized according to the "preferred value" entries in the -/// [IANA Language Subtag -/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). -/// For example, `const Locale('he')` and `const Locale('iw')` are equal and -/// both have the [languageCode] `he`, because `iw` is a deprecated language -/// subtag that was replaced by the subtag `he`. -/// -/// See also: -/// -/// * [Window.locale], which specifies the system's currently selected -/// [Locale]. -class Locale { - /// Creates a new Locale object. The first argument is the - /// primary language subtag, the second is the region (also - /// referred to as 'country') subtag. - /// - /// For example: - /// - /// ```dart - /// const Locale swissFrench = Locale('fr', 'CH'); - /// const Locale canadianFrench = Locale('fr', 'CA'); - /// ``` - /// - /// The primary language subtag must not be null. The region subtag is - /// optional. When there is no region/country subtag, the parameter should - /// be omitted or passed `null` instead of an empty-string. - /// - /// The subtag values are _case sensitive_ and must be one of the valid - /// subtags according to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The - /// primary language subtag must be at least two and at most eight lowercase - /// letters, but not four letters. The region region subtag must be two - /// uppercase letters or three digits. See the [Unicode Language - /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) - /// specification. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which also allows a [scriptCode] to be - /// specified. - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - - /// Creates a new Locale object. - /// - /// The keyword arguments specify the subtags of the Locale. - /// - /// The subtag values are _case sensitive_ and must be valid subtags according - /// to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for - /// each of languageCode, scriptCode and countryCode respectively. - /// - /// The [countryCode] subtag is optional. When there is no country subtag, - /// the parameter should be omitted or passed `null` instead of an empty-string. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - - /// The primary language subtag for the locale. - /// - /// This must not be null. It may be 'und', representing 'undefined'. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "language". The string specified must match the case of the - /// string in the registry. - /// - /// Language subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const - /// Locale('he')` and `const Locale('iw')` are equal, and both have the - /// [languageCode] `he`, because `iw` is a deprecated language subtag that was - /// replaced by the subtag `he`. - /// - /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR - /// supplemental - /// data](http://unicode.org/cldr/latest/common/validity/language.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - - /// The script subtag for the locale. - /// - /// This may be null, indicating that there is no specified script subtag. - /// - /// This must be a valid Unicode Language Identifier script subtag as listed - /// in [Unicode CLDR supplemental - /// data](http://unicode.org/cldr/latest/common/validity/script.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - final String? scriptCode; - - /// The region subtag for the locale. - /// - /// This may be null, indicating that there is no specified region subtag. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "region". The string specified must match the case of the - /// string in the registry. - /// - /// Region subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const Locale('de', - /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the - /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was - /// replaced by the subtag `DE`. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) - return true; - if (other is! Locale) { - return false; - } - final String? countryCode = _countryCode; - final String? otherCountryCode = other.countryCode; - return other.languageCode == languageCode - && other.scriptCode == scriptCode // scriptCode cannot be '' - && (other.countryCode == countryCode // Treat '' as equal to null. - || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null - || countryCode != null && countryCode.isEmpty && other.countryCode == null); - } - - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); - - static Locale? _cachedLocale; - static String? _cachedLocaleString; - - /// Returns a string representing the locale. - /// - /// This identifier happens to be a valid Unicode Locale Identifier using - /// underscores as separator, however it is intended to be used for debugging - /// purposes only. For parseable results, use [toLanguageTag] instead. - @keepToString - @override - String toString() { - if (!identical(_cachedLocale, this)) { - _cachedLocale = this; - _cachedLocaleString = _rawToString('_'); - } - return _cachedLocaleString!; - } - - /// Returns a syntactically valid Unicode BCP47 Locale Identifier. - /// - /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and - /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical - /// details. - String toLanguageTag() => _rawToString('-'); - - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null && scriptCode!.isNotEmpty) - out.write('$separator$scriptCode'); - if (_countryCode != null && _countryCode!.isNotEmpty) - out.write('$separator$countryCode'); - return out.toString(); - } -} - -/// The most basic interface to the host operating system's user interface. -/// -/// It exposes the size of the display, the core scheduler API, the input event -/// callback, the graphics drawing API, and other such core services. -/// -/// There is a single Window instance in the system, which you can -/// obtain from `WidgetsBinding.instance.window`. -/// -/// There is also a [window] singleton object in `dart:ui` if `WidgetsBinding` -/// is unavailable. But we strongly advise to avoid statically referencing it. -/// See the document of [window] for more details of why it should be avoided. +/// Each [FlutterView] has its own layer tree that is rendered into an area +/// inside of a [FlutterWindow] whenever [render] is called with a [Scene]. /// /// ## Insets and Padding /// /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4} /// -/// In this diagram, the black areas represent system UI that the app cannot -/// draw over. The red area represents view padding that the application may not +/// In this illustration, the black areas represent system UI that the app +/// cannot draw over. The red area represents view padding that the view may not /// be able to detect gestures in and may not want to draw in. The grey area -/// represents the system keyboard, which can cover over the bottom view -/// padding when visible. +/// represents the system keyboard, which can cover over the bottom view padding +/// when visible. /// -/// The [Window.viewInsets] are the physical pixels which the operating +/// The [viewInsets] are the physical pixels which the operating /// system reserves for system UI, such as the keyboard, which would fully /// obscure any content drawn in that area. /// -/// The [Window.viewPadding] are the physical pixels on each side of the display -/// that may be partially obscured by system UI or by physical intrusions into -/// the display, such as an overscan region on a television or a "notch" on a -/// phone. Unlike the insets, these areas may have portions that show the user -/// application painted pixels without being obscured, such as a notch at the -/// top of a phone that covers only a subset of the area. Insets, on the other -/// hand, either partially or fully obscure the window, such as an opaque -/// keyboard or a partially transluscent statusbar, which cover an area without -/// gaps. +/// The [viewPadding] are the physical pixels on each side of the +/// display that may be partially obscured by system UI or by physical +/// intrusions into the display, such as an overscan region on a television or a +/// "notch" on a phone. Unlike the insets, these areas may have portions that +/// show the user view-painted pixels without being obscured, such as a +/// notch at the top of a phone that covers only a subset of the area. Insets, +/// on the other hand, either partially or fully obscure the window, such as an +/// opaque keyboard or a partially translucent status bar, which cover an area +/// without gaps. /// -/// The [Window.padding] property is computed from both [Window.viewInsets] and -/// [Window.viewPadding]. It will allow a view inset to consume view padding -/// where appropriate, such as when a phone's keyboard is covering the bottom -/// view padding and so "absorbs" it. +/// The [padding] property is computed from both +/// [viewInsets] and [viewPadding]. It will allow a +/// view inset to consume view padding where appropriate, such as when a phone's +/// keyboard is covering the bottom view padding and so "absorbs" it. /// /// Clients that want to position elements relative to the view padding -/// regardless of the view insets should use the [Window.viewPadding] property, -/// e.g. if you wish to draw a widget at the center of the screen with respect -/// to the iPhone "safe area" regardless of whether the keyboard is showing. +/// regardless of the view insets should use the [viewPadding] +/// property, e.g. if you wish to draw a widget at the center of the screen with +/// respect to the iPhone "safe area" regardless of whether the keyboard is +/// showing. /// -/// [Window.padding] is useful for clients that want to know how much padding -/// should be accounted for without concern for the current inset(s) state, e.g. -/// determining whether a gesture should be considered for scrolling purposes. -/// This value varies based on the current state of the insets. For example, a -/// visible keyboard will consume all gestures in the bottom part of the -/// [Window.viewPadding] anyway, so there is no need to account for that in the -/// [Window.padding], which is always safe to use for such calculations. -class Window { - Window._() { - _setNeedsReportTimings = _nativeSetNeedsReportTimings; - } +/// [padding] is useful for clients that want to know how much +/// padding should be accounted for without concern for the current inset(s) +/// state, e.g. determining whether a gesture should be considered for scrolling +/// purposes. This value varies based on the current state of the insets. For +/// example, a visible keyboard will consume all gestures in the bottom part of +/// the [viewPadding] anyway, so there is no need to account for +/// that in the [padding], which is always safe to use for such +/// calculations. +/// +/// See also: +/// +/// * [FlutterWindow], a special case of a [FlutterView] that is represented on +/// the platform as a separate window which can host other [FlutterView]s. +abstract class FlutterView { + /// The platform dispatcher that this view is registered with, and gets its + /// information from. + PlatformDispatcher get platformDispatcher; - /// The number of device pixels for each logical pixel. This number might not - /// be a power of two. Indeed, it might not even be an integer. For example, - /// the Nexus 6 has a device pixel ratio of 3.5. + /// The configuration of this view. + ViewConfiguration get viewConfiguration; + + /// The number of device pixels for each logical pixel for the screen this + /// view is displayed on. + /// + /// This number might not be a power of two. Indeed, it might not even be an + /// integer. For example, the Nexus 6 has a device pixel ratio of 3.5. /// /// Device pixels are also referred to as physical pixels. Logical pixels are /// also referred to as device-independent or resolution-independent pixels. @@ -633,39 +91,59 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get devicePixelRatio => _devicePixelRatio; - double _devicePixelRatio = 1.0; + double get devicePixelRatio => viewConfiguration.devicePixelRatio; - /// The dimensions of the rectangle into which the application will be drawn, - /// in physical pixels. + /// The dimensions and location of the rectangle into which the scene rendered + /// in this view will be drawn on the screen, in physical pixels. /// /// When this changes, [onMetricsChanged] is called. /// - /// At startup, the size of the application window may not be known before Dart + /// At startup, the size and location of the view may not be known before Dart /// code runs. If this value is observed early in the application lifecycle, - /// it may report [Size.zero]. + /// it may report [Rect.zero]. /// /// This value does not take into account any on-screen keyboards or other /// system UI. The [padding] and [viewInsets] properties provide a view into - /// how much of each side of the application may be obscured by system UI. + /// how much of each side of the view may be obscured by system UI. /// /// See also: /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - Size get physicalSize => _physicalSize; - Size _physicalSize = Size.zero; + Rect get physicalGeometry => viewConfiguration.geometry; + + /// The dimensions of the rectangle into which the scene rendered in this view + /// will be drawn on the screen, in physical pixels. + /// + /// When this changes, [onMetricsChanged] is called. + /// + /// At startup, the size of the view may not be known before Dart code runs. + /// If this value is observed early in the application lifecycle, it may + /// report [Size.zero]. + /// + /// This value does not take into account any on-screen keyboards or other + /// system UI. The [padding] and [viewInsets] properties provide information + /// about how much of each side of the view may be obscured by system UI. + /// + /// This value is the same as [physicalGeometry.size]. + /// + /// See also: + /// + /// * [physicalGeometry], which reports the location of the view as well as + /// its size. + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + Size get physicalSize => viewConfiguration.geometry.size; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but over which the operating system - /// will likely place system UI, such as the keyboard, that fully obscures - /// any content. + /// which the view can render, but over which the operating system will likely + /// place system UI, such as the keyboard, that fully obscures any content. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], + /// [viewPadding], and [padding] are described in + /// more detail in the documentation for [FlutterView]. /// /// See also: /// @@ -674,25 +152,24 @@ class Window { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the view insets in material /// design applications. - WindowPadding get viewInsets => _viewInsets; - WindowPadding _viewInsets = WindowPadding.zero; + WindowPadding get viewInsets => viewConfiguration.viewInsets; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but which may be partially obscured by - /// system UI (such as the system notification area), or or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). + /// which the view can render, but which may be partially obscured by system + /// UI (such as the system notification area), or or physical intrusions in + /// the display (e.g. overscan regions on television screens or phone sensor + /// housings). /// - /// Unlike [Window.padding], this value does not change relative to - /// [Window.viewInsets]. For example, on an iPhone X, it will not change in - /// response to the soft keyboard being visible or hidden, whereas - /// [Window.padding] will. + /// Unlike [padding], this value does not change relative to + /// [viewInsets]. For example, on an iPhone X, it will not + /// change in response to the soft keyboard being visible or hidden, whereas + /// [padding] will. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], + /// [viewPadding], and [padding] are described in + /// more detail in the documentation for [FlutterView]. /// /// See also: /// @@ -701,12 +178,11 @@ class Window { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the padding in material design /// applications. - WindowPadding get viewPadding => _viewPadding; - WindowPadding _viewPadding = WindowPadding.zero; + WindowPadding get viewPadding => viewConfiguration.viewPadding; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but where the operating system will - /// consume input gestures for the sake of system navigation. + /// which the view can render, but where the operating system will consume + /// input gestures for the sake of system navigation. /// /// For example, an operating system might use the vertical edges of the /// screen, where swiping inwards from the edges takes users backward @@ -719,85 +195,158 @@ class Window { /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. /// * [MediaQuery.of], a simpler mechanism for the same. - WindowPadding get systemGestureInsets => _systemGestureInsets; - WindowPadding _systemGestureInsets = WindowPadding.zero; + WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; /// The number of physical pixels on each side of the display rectangle into - /// which the application can render, but which may be partially obscured by - /// system UI (such as the system notification area), or or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). - /// - /// This value is calculated by taking - /// `max(0.0, Window.viewPadding - Window.viewInsets)`. This will treat a - /// system IME that increases the bottom inset as consuming that much of the - /// bottom padding. For example, on an iPhone X, [EdgeInsets.bottom] of - /// [Window.padding] is the same as [EdgeInsets.bottom] of - /// [Window.viewPadding] when the soft keyboard is not drawn (to account for - /// the bottom soft button area), but will be `0.0` when the soft keyboard is - /// visible. + /// which the view can render, but which may be partially obscured by system + /// UI (such as the system notification area), or or physical intrusions in + /// the display (e.g. overscan regions on television screens or phone sensor + /// housings). + /// + /// This value is calculated by taking `max(0.0, FlutterView.viewPadding - + /// FlutterView.viewInsets)`. This will treat a system IME that increases the + /// bottom inset as consuming that much of the bottom padding. For example, on + /// an iPhone X, [EdgeInsets.bottom] of [FlutterView.padding] is the same as + /// [EdgeInsets.bottom] of [FlutterView.viewPadding] when the soft keyboard is + /// not drawn (to account for the bottom soft button area), but will be `0.0` + /// when the soft keyboard is visible. /// /// When this changes, [onMetricsChanged] is called. /// - /// The relationship between this [Window.viewInsets], [Window.viewPadding], - /// and [Window.padding] are described in more detail in the documentation for - /// [Window]. + /// The relationship between this [viewInsets], [viewPadding], and [padding] + /// are described in more detail in the documentation for [FlutterView]. /// /// See also: /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - /// * [MediaQuery.of], a simpler mechanism for the same. - /// * [Scaffold], which automatically applies the padding in material design - /// applications. - WindowPadding get padding => _padding; - WindowPadding _padding = WindowPadding.zero; + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + /// * [MediaQuery.of], a simpler mechanism for the same. + /// * [Scaffold], which automatically applies the padding in material design + /// applications. + WindowPadding get padding => viewConfiguration.padding; - /// A callback that is invoked whenever the [devicePixelRatio], - /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] - /// values change, for example when the device is rotated or when the - /// application is resized (e.g. when showing applications side-by-side - /// on Android). + /// Updates the view's rendering on the GPU with the newly provided [Scene]. /// - /// The engine invokes this callback in the same zone in which the callback - /// was set. + /// This function must be called within the scope of the + /// [PlatformDispatcher.onBeginFrame] or [PlatformDispatcher.onDrawFrame] + /// callbacks being invoked. /// - /// The framework registers with this callback and updates the layout - /// appropriately. + /// If this function is called a second time during a single + /// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame] + /// callback sequence or called outside the scope of those callbacks, the call + /// will be ignored. + /// + /// To record graphical operations, first create a [PictureRecorder], then + /// construct a [Canvas], passing that [PictureRecorder] to its constructor. + /// After issuing all the graphical operations, call the + /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain + /// the final [Picture] that represents the issued graphical operations. + /// + /// Next, create a [SceneBuilder], and add the [Picture] to it using + /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can + /// then obtain a [Scene] object, which you can display to the user via this + /// [render] function. /// /// See also: /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// register for notifications when this is called. - /// * [MediaQuery.of], a simpler mechanism for the same. - VoidCallback? get onMetricsChanged => _onMetricsChanged; - VoidCallback? _onMetricsChanged; - Zone _onMetricsChangedZone = Zone.root; + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + /// * [RendererBinding], the Flutter framework class which manages layout and + /// painting. + void render(Scene scene) => _render(scene, this); + void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render'; +} + +/// A top-level platform window displaying a Flutter layer tree drawn from a +/// [Scene]. +/// +/// The current list of all Flutter views for the application is available from +/// `WidgetsBinding.instance.platformDispatcher.views`. Only views that are of type +/// [FlutterWindow] are top level platform windows. +/// +/// There is also a [PlatformDispatcher.instance] singleton object in `dart:ui` +/// if `WidgetsBinding` is unavailable, but we strongly advise avoiding a static +/// reference to it. See the documentation for [PlatformDispatcher.instance] for +/// more details about why it should be avoided. +/// +/// See also: +/// +/// * [PlatformDispatcher], which manages the current list of [FlutterView] (and +/// thus [FlutterWindow]) instances. +class FlutterWindow extends FlutterView { + FlutterWindow._(this._windowId, this.platformDispatcher); + + /// The opaque ID for this view. + final Object _windowId; + + @override + final PlatformDispatcher platformDispatcher; + + @override + ViewConfiguration get viewConfiguration { + assert(platformDispatcher._viewConfigurations.containsKey(_windowId)); + return platformDispatcher._viewConfigurations[_windowId]!; + } +} + +/// A [FlutterWindow] that includes access to setting callbacks and retrieving +/// properties that reside on the [PlatformDispatcher]. +/// +/// It is the type of the global [window] singleton used by applications that +/// only have a single main window. +/// +/// In addition to the properties of [FlutterView], this class provides access +/// to platform-specific properties. To modify or retrieve these properties, +/// applications designed for more than one main window should prefer using +/// `WidgetsBinding.instance.platformDispatcher` instead. +/// +/// Prefer access through `WidgetsBinding.instance.window` or +/// `WidgetsBinding.instance.platformDispatcher` over a static reference to +/// [window], or [PlatformDispatcher.instance]. See the documentation for +/// [PlatformDispatcher.instance] for more details about this recommendation. +class SingletonFlutterWindow extends FlutterWindow { + SingletonFlutterWindow._(Object windowId, PlatformDispatcher platformDispatcher) + : super._(windowId, platformDispatcher); + + /// A callback that is invoked whenever the [devicePixelRatio], + /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], + /// [PlatformDispatcher.screens], or [systemGestureInsets] values change. + /// + /// {@template flutter.lib.ui.window.forwardWarning} + /// + /// See [PlatformDispatcher.onMetricsChanged] for more information. + VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; set onMetricsChanged(VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; + platformDispatcher.onMetricsChanged = callback; } /// The system-reported default locale of the device. /// - /// This establishes the language and formatting conventions that application + /// {@template flutter.lib.ui.window.accessorForwardWarning} + /// Accessing this value returns the value contained in the + /// [PlatformDispatcher] singleton, so instead of getting it from here, you + /// should consider getting it from `WidgetsBinding.instance.platformDispatcher` instead + /// (or, when `WidgetsBinding` isn't available, from + /// [PlatformDispatcher.instance]). The reason this value forwards to the + /// [PlatformDispatcher] is to provide convenience for applications that only + /// use a single main window. + /// {@endtemplate} + /// + /// This establishes the language and formatting conventions that window /// should, if possible, use to render their user interface. /// - /// This is the first locale selected by the user and is the user's - /// primary locale (the locale the device UI is displayed in) + /// This is the first locale selected by the user and is the user's primary + /// locale (the locale the device UI is displayed in) /// - /// This is equivalent to `locales.first` and will provide an empty non-null locale - /// if the [locales] list has not been set or is empty. - Locale? get locale { - if (_locales != null && _locales!.isNotEmpty) { - return _locales!.first; - } - return null; - } + /// This is equivalent to `locales.first` and will provide an empty non-null + /// locale if the [locales] list has not been set or is empty. + Locale? get locale => platformDispatcher.locale; /// The full system-reported supported locales of the device. /// - /// This establishes the language and formatting conventions that application + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// + /// This establishes the language and formatting conventions that window /// should, if possible, use to render their user interface. /// /// The list is ordered in order of priority, with lower-indexed locales being @@ -809,8 +358,7 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - List? get locales => _locales; - List? _locales; + List? get locales => platformDispatcher.locales; /// Performs the platform-native locale resolution. /// @@ -821,27 +369,13 @@ class Window { /// This method returns synchronously and is a direct call to /// platform specific APIs without invoking method channels. Locale? computePlatformResolvedLocale(List supportedLocales) { - final List supportedLocalesData = []; - for (Locale locale in supportedLocales) { - supportedLocalesData.add(locale.languageCode); - supportedLocalesData.add(locale.countryCode); - supportedLocalesData.add(locale.scriptCode); - } - - final List result = _computePlatformResolvedLocale(supportedLocalesData); - - if (result.isNotEmpty) { - return Locale.fromSubtags( - languageCode: result[0], - countryCode: result[1] == '' ? null : result[1], - scriptCode: result[2] == '' ? null : result[2]); - } - return null; + return platformDispatcher.computePlatformResolvedLocale(supportedLocales); } - List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; /// A callback that is invoked whenever [locale] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -849,32 +383,25 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onLocaleChanged => _onLocaleChanged; - VoidCallback? _onLocaleChanged; - Zone _onLocaleChangedZone = Zone.root; + VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; set onLocaleChanged(VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; + platformDispatcher.onLocaleChanged = callback; } /// The lifecycle state immediately after dart isolate initialization. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This property will not be updated as the lifecycle changes. /// /// It is used to initialize [SchedulerBinding.lifecycleState] at startup /// with any buffered lifecycle state events. - String get initialLifecycleState { - _initialLifecycleStateAccessed = true; - return _initialLifecycleState; - } - late String _initialLifecycleState; - /// Tracks if the initial state has been accessed. Once accessed, we - /// will stop updating the [initialLifecycleState], as it is not the - /// preferred way to access the state. - bool _initialLifecycleStateAccessed = false; + String get initialLifecycleState => platformDispatcher.initialLifecycleState; /// The system-reported text scale. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This establishes the text scaling factor to use when rendering text, /// according to the user's platform preferences. /// @@ -885,18 +412,20 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get textScaleFactor => _textScaleFactor; - double _textScaleFactor = 1.0; + double get textScaleFactor => platformDispatcher.textScaleFactor; /// The setting indicating whether time should always be shown in the 24-hour /// format. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This option is used by [showTimePicker]. - bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; - bool _alwaysUse24HourFormat = false; + bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; /// A callback that is invoked whenever [textScaleFactor] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -904,21 +433,23 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - VoidCallback? _onTextScaleFactorChanged; - Zone _onTextScaleFactorChangedZone = Zone.root; + VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; set onTextScaleFactorChanged(VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; + platformDispatcher.onTextScaleFactorChanged = callback; } /// The setting indicating the current brightness mode of the host platform. - /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. - Brightness get platformBrightness => _platformBrightness; - Brightness _platformBrightness = Brightness.light; + /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// + /// If the platform has no preference, [platformBrightness] defaults to + /// [Brightness.light]. + Brightness get platformBrightness => platformDispatcher.platformBrightness; /// A callback that is invoked whenever [platformBrightness] changes value. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -926,19 +457,20 @@ class Window { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; - VoidCallback? _onPlatformBrightnessChanged; - Zone _onPlatformBrightnessChangedZone = Zone.root; + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; set onPlatformBrightnessChanged(VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; + platformDispatcher.onPlatformBrightnessChanged = callback; } - /// A callback that is invoked to notify the application that it is an - /// appropriate time to provide a scene using the [SceneBuilder] API and the - /// [render] method. When possible, this is driven by the hardware VSync - /// signal. This is only called if [scheduleFrame] has been called since the - /// last time this callback was invoked. + /// A callback that is invoked to notify the window that it is an appropriate + /// time to provide a scene using the [SceneBuilder] API and the [render] + /// method. + /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// + /// When possible, this is driven by the hardware VSync signal. This is only + /// called if [scheduleFrame] has been called since the last time this + /// callback was invoked. /// /// The [onDrawFrame] callback is invoked immediately after [onBeginFrame], /// after draining any microtasks (e.g. completions of any [Future]s) queued @@ -953,18 +485,18 @@ class Window { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - FrameCallback? get onBeginFrame => _onBeginFrame; - FrameCallback? _onBeginFrame; - Zone _onBeginFrameZone = Zone.root; + FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; set onBeginFrame(FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; + platformDispatcher.onBeginFrame = callback; } /// A callback that is invoked for each frame after [onBeginFrame] has - /// completed and after the microtask queue has been drained. This can be - /// used to implement a second phase of frame rendering that happens - /// after any deferred work queued by the [onBeginFrame] phase. + /// completed and after the microtask queue has been drained. + /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -975,22 +507,21 @@ class Window { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - VoidCallback? get onDrawFrame => _onDrawFrame; - VoidCallback? _onDrawFrame; - Zone _onDrawFrameZone = Zone.root; + VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; set onDrawFrame(VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; + platformDispatcher.onDrawFrame = callback; } /// A callback that is invoked to report the [FrameTiming] of recently /// rasterized frames. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use - /// [Window.onReportTimings] directly because + /// [FlutterWindow.onReportTimings] directly because /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. /// - /// This can be used to see if the application has missed frames (through + /// This can be used to see if the window has missed frames (through /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high /// latencies (through [FrameTiming.totalSpan]). /// @@ -1004,22 +535,15 @@ class Window { /// Flutter spends less than 0.1ms every 1 second to report the timings /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for /// 60fps), or 0.01% CPU usage per second. - TimingsCallback? get onReportTimings => _onReportTimings; - TimingsCallback? _onReportTimings; - Zone _onReportTimingsZone = Zone.root; + TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; set onReportTimings(TimingsCallback? callback) { - if ((callback == null) != (_onReportTimings == null)) { - _setNeedsReportTimings(callback != null); - } - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; + platformDispatcher.onReportTimings = callback; } - late _SetNeedsReportTimingsFunc _setNeedsReportTimings; - void _nativeSetNeedsReportTimings(bool value) native 'PlatformConfiguration_setNeedsReportTimings'; - /// A callback that is invoked when pointer data is available. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -1027,17 +551,16 @@ class Window { /// /// * [GestureBinding], the Flutter framework class which manages pointer /// events. - PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - PointerDataPacketCallback? _onPointerDataPacket; - Zone _onPointerDataPacketZone = Zone.root; + PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; set onPointerDataPacket(PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; + platformDispatcher.onPointerDataPacket = callback; } /// The route or path that the embedder requested when the application was /// launched. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// This will be the string "`/`" if no particular route was requested. /// /// ## Android @@ -1061,118 +584,90 @@ class Window { /// * [Navigator], a widget that handles routing. /// * [SystemChannels.navigation], which handles subsequent navigation /// requests from the embedder. - String get defaultRouteName => _defaultRouteName(); - String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; - - /// Requests that, at the next appropriate opportunity, the [onBeginFrame] - /// and [onDrawFrame] callbacks be invoked. - /// - /// See also: - /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; - - /// Updates the application's rendering on the GPU with the newly provided - /// [Scene]. This function must be called within the scope of the - /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function - /// is called a second time during a single [onBeginFrame]/[onDrawFrame] - /// callback sequence or called outside the scope of those callbacks, the call - /// will be ignored. - /// - /// To record graphical operations, first create a [PictureRecorder], then - /// construct a [Canvas], passing that [PictureRecorder] to its constructor. - /// After issuing all the graphical operations, call the - /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain - /// the final [Picture] that represents the issued graphical operations. - /// - /// Next, create a [SceneBuilder], and add the [Picture] to it using - /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can - /// then obtain a [Scene] object, which you can display to the user via this - /// [render] function. + String get defaultRouteName => platformDispatcher.defaultRouteName; + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and + /// [onDrawFrame] callbacks be invoked. + /// + /// {@template flutter.lib.ui.window.functionForwardWarning} + /// Calling this function forwards the call to the same function on the + /// [PlatformDispatcher] singleton, so instead of calling it here, you should + /// consider calling it on `WidgetsBinding.instance.platformDispatcher` instead (or, when + /// `WidgetsBinding` isn't available, on [PlatformDispatcher.instance]). The + /// reason this function forwards to the [PlatformDispatcher] is to provide + /// convenience for applications that only use a single main window. + /// {@endtemplate} /// /// See also: /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - /// * [RendererBinding], the Flutter framework class which manages layout and - /// painting. - void render(Scene scene) native 'PlatformConfiguration_render'; + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + void scheduleFrame() => platformDispatcher.scheduleFrame(); /// Whether the user has requested that [updateSemantics] be called when /// the semantic contents of window changes. /// + /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// /// The [onSemanticsEnabledChanged] callback is called whenever this value /// changes. - bool get semanticsEnabled => _semanticsEnabled; - bool _semanticsEnabled = false; + bool get semanticsEnabled => platformDispatcher.semanticsEnabled; /// A callback that is invoked when the value of [semanticsEnabled] changes. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - VoidCallback? _onSemanticsEnabledChanged; - Zone _onSemanticsEnabledChangedZone = Zone.root; + VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; set onSemanticsEnabledChanged(VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; + platformDispatcher.onSemanticsEnabledChanged = callback; } /// A callback that is invoked whenever the user requests an action to be /// performed. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// This callback is used when the user expresses the action they wish to /// perform based on the semantics supplied by [updateSemantics]. /// /// The framework invokes this callback in the same zone in which the /// callback was set. - SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - SemanticsActionCallback? _onSemanticsAction; - Zone _onSemanticsActionZone = Zone.root; + SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; set onSemanticsAction(SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; + platformDispatcher.onSemanticsAction = callback; } /// Additional accessibility features that may be enabled by the platform. - AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; - // The zero value matches the default value in `platform_data.h`. - AccessibilityFeatures _accessibilityFeatures = const AccessibilityFeatures._(0); + AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; /// A callback that is invoked when the value of [accessibilityFeatures] changes. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; - VoidCallback? _onAccessibilityFeaturesChanged; - Zone _onAccessibilityFeaturesChangedZone = Zone.root; + VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged; set onAccessibilityFeaturesChanged(VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; + platformDispatcher.onAccessibilityFeaturesChanged = callback; } /// Change the retained semantics data about this window. /// + /// {@macro flutter.lib.ui.window.functionForwardWarning} + /// /// If [semanticsEnabled] is true, the user has requested that this function /// be called whenever the semantic content of this window changes. /// /// In either case, this function disposes the given update, which means the /// semantics update cannot be used further. - void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; - - /// Set the debug name associated with this window's root isolate. - /// - /// Normally debug names are automatically generated from the Dart port, entry - /// point, and source file. For example: `main.dart$main-1234`. - /// - /// This can be combined with flutter tools `--isolate-filter` flag to debug - /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. - /// Note that this does not rename any child isolates of the root. - void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; + void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); /// Sends a message to a platform-specific plugin. /// + /// {@macro flutter.lib.ui.window.functionForwardWarning} + /// /// The `name` parameter determines which plugin receives the message. The /// `data` parameter contains the message payload and is typically UTF-8 /// encoded JSON but can be arbitrary data. If the plugin replies to the @@ -1181,20 +676,16 @@ class Window { /// The framework invokes [callback] in the same zone in which this method /// was called. void sendPlatformMessage(String name, - ByteData? data, - PlatformMessageResponseCallback? callback) { - final String? error = - _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); - if (error != null) - throw Exception(error); + ByteData? data, + PlatformMessageResponseCallback? callback) { + platformDispatcher.sendPlatformMessage(name, data, callback); } - String? _sendPlatformMessage(String name, - PlatformMessageResponseCallback? callback, - ByteData? data) native 'PlatformConfiguration_sendPlatformMessage'; /// Called whenever this window receives a message from a platform-specific /// plugin. /// + /// {@macro flutter.lib.ui.window.forwardWarning} + /// /// The `name` parameter determines which plugin sent the message. The `data` /// parameter is the payload and is typically UTF-8 encoded JSON but can be /// arbitrary data. @@ -1205,43 +696,23 @@ class Window { /// /// The framework invokes this callback in the same zone in which the /// callback was set. - PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - PlatformMessageCallback? _onPlatformMessage; - Zone _onPlatformMessageZone = Zone.root; + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; set onPlatformMessage(PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Called by [_dispatchPlatformMessage]. - void _respondToPlatformMessage(int responseId, ByteData? data) - native 'PlatformConfiguration_respondToPlatformMessage'; - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback? callback) { - if (callback == null) - return null; - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; + platformDispatcher.onPlatformMessage = callback; } - - /// The embedder can specify data that the isolate can request synchronously - /// on launch. This accessor fetches that data. + /// Set the debug name associated with this platform dispatcher's root + /// isolate. /// - /// This data is persistent for the duration of the Flutter application and is - /// available even after isolate restarts. Because of this lifecycle, the size - /// of this data must be kept to a minimum. + /// {@macro flutter.lib.ui.window.forwardWarning} /// - /// For asynchronous communication between the embedder and isolate, a - /// platform channel may be used. - ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. + /// + /// This can be combined with flutter tools `--isolate-filter` flag to debug + /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. + /// Note that this does not rename any child isolates of the root. + void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } /// Additional accessibility features that may be enabled by the platform. @@ -1323,6 +794,15 @@ class AccessibilityFeatures { int get hashCode => _index.hashCode; } +/// A soon-to-be deprecated class that is wholly replaced by +/// [SingletonFlutterWindow]. +/// +/// This class will be removed once the framework no longer refers to it. +class Window extends SingletonFlutterWindow { + Window._(Object windowId, PlatformDispatcher platformDispatcher) + : super._(windowId, platformDispatcher); +} + /// Describes the contrast of a theme or color palette. enum Brightness { /// The color is dark and will require a light text color to achieve readable @@ -1338,12 +818,18 @@ enum Brightness { light, } -/// The [Window] singleton. +/// The [SingletonFlutterWindow] representing the main window for applications +/// where there is only one window, such as applications designed for +/// single-display mobile devices. /// -/// Please try to avoid statically referencing this and instead use a -/// binding for dependency resolution such as `WidgetsBinding.instance.window`. +/// Applications that are designed to use more than one window should interact +/// with the `WidgetsBinding.instance.platformDispatcher` instead. /// -/// Static access of this "window" object means that Flutter has few, if any +/// Consider avoiding static references to this singleton through +/// [PlatformDispatcher.instance] and instead prefer using a binding for +/// dependency resolution such as `WidgetsBinding.instance.window`. +/// +/// Static access of this `window` object means that Flutter has few, if any /// options to fake or mock the given object in tests. Even in cases where Dart /// offers special language constructs to forcefully shadow such properties, /// those mechanisms would only be reasonable for tests and they would not be @@ -1351,6 +837,14 @@ enum Brightness { /// appropriate implementation at runtime. /// /// The only place that `WidgetsBinding.instance.window` is inappropriate is if -/// a `Window` is required before invoking `runApp()`. In that case, it is -/// acceptable (though unfortunate) to use this object statically. -final Window window = Window._(); +/// access to these APIs is required before the binding is initialized by +/// invoking `runApp()` or `WidgetsFlutterBinding.instance.ensureInitialized()`. +/// In that case, it is necessary (though unfortunate) to use the +/// [PlatformDispatcher.instance] object statically. +/// +/// See also: +/// +/// * [PlatformDispatcher.views], contains the current list of Flutter windows +/// belonging to the application, including top level application windows like +/// this one. +final Window window = Window._(0, PlatformDispatcher.instance); diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index e470e1dab79e6..e8ff2274fe7a1 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -9,6 +9,7 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" +#include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" @@ -197,7 +198,8 @@ PlatformConfiguration::~PlatformConfiguration() {} void PlatformConfiguration::DidCreateIsolate() { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); - window_.reset(new Window({1.0, 0.0, 0.0})); + windows_.insert(std::make_pair(0, std::unique_ptr(new Window{ + 0, ViewportMetrics{1.0, 0.0, 0.0}}))); } void PlatformConfiguration::UpdateLocales( @@ -421,7 +423,7 @@ void PlatformConfiguration::RegisterNatives( true}, {"PlatformConfiguration_respondToPlatformMessage", _RespondToPlatformMessage, 3, true}, - {"PlatformConfiguration_render", Render, 2, true}, + {"PlatformConfiguration_render", Render, 3, true}, {"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true}, {"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2, true}, diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 4652c7c5dcdbb..e4ce4bd5b0307 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -6,14 +6,12 @@ #define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_ #include -#include #include #include #include #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" -#include "flutter/lib/ui/window/platform_message.h" #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" @@ -378,11 +376,14 @@ class PlatformConfiguration final { static void RegisterNatives(tonic::DartLibraryNatives* natives); //---------------------------------------------------------------------------- - /// @brief Retrieves the Window managed by the PlatformConfiguration. + /// @brief Retrieves the Window with the given ID managed by the + /// `PlatformConfiguration`. + /// + /// @param[in] window_id The id of the window to find and return. /// /// @return a pointer to the Window. /// - Window* window() const { return window_.get(); } + Window* get_window(int window_id) { return windows_[window_id].get(); } //---------------------------------------------------------------------------- /// @brief Responds to a previous platform message to the engine from the @@ -408,7 +409,7 @@ class PlatformConfiguration final { PlatformConfigurationClient* client_; tonic::DartPersistentValue library_; - std::unique_ptr window_; + std::unordered_map> windows_; // We use id 0 to mean that no response is expected. int next_response_id_ = 1; diff --git a/lib/ui/window/platform_configuration_unittests.cc b/lib/ui/window/platform_configuration_unittests.cc index 1ff3ce01de52d..f38498614e187 100644 --- a/lib/ui/window/platform_configuration_unittests.cc +++ b/lib/ui/window/platform_configuration_unittests.cc @@ -54,11 +54,14 @@ TEST_F(ShellTest, PlatformConfigurationInitialization) { Dart_NativeArguments args) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->window(), nullptr); - ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, - 1.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 0.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, 0.0); + ASSERT_NE(configuration->get_window(0), nullptr); + ASSERT_EQ( + configuration->get_window(0)->viewport_metrics().device_pixel_ratio, + 1.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, + 0.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, + 0.0); message_latch->Signal(); }; @@ -97,13 +100,15 @@ TEST_F(ShellTest, PlatformConfigurationWindowMetricsUpdate) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->window(), nullptr); - configuration->window()->UpdateWindowMetrics( + ASSERT_NE(configuration->get_window(0), nullptr); + configuration->get_window(0)->UpdateWindowMetrics( ViewportMetrics{2.0, 10.0, 20.0}); - ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, - 2.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 10.0); - ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, + ASSERT_EQ( + configuration->get_window(0)->viewport_metrics().device_pixel_ratio, + 2.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, + 10.0); + ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, 20.0); message_latch->Signal(); diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index 1779998945554..082df1b823d32 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -11,7 +11,8 @@ namespace flutter { -Window::Window(ViewportMetrics metrics) : viewport_metrics_(metrics) { +Window::Window(int64_t window_id, ViewportMetrics metrics) + : window_id_(window_id), viewport_metrics_(metrics) { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); } @@ -46,6 +47,7 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { tonic::LogIfError(tonic::DartInvokeField( library_.value(), "_updateWindowMetrics", { + tonic::ToDart(window_id_), tonic::ToDart(metrics.device_pixel_ratio), tonic::ToDart(metrics.physical_width), tonic::ToDart(metrics.physical_height), diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index 172cf6b8c2ae5..b6fa2555b04d7 100644 --- a/lib/ui/window/window.h +++ b/lib/ui/window/window.h @@ -18,10 +18,12 @@ namespace flutter { class Window final { public: - explicit Window(ViewportMetrics metrics); + Window(int64_t window_id, ViewportMetrics metrics); ~Window(); + int window_id() const { return window_id_; } + const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; } void DispatchPointerDataPacket(const PointerDataPacket& packet); @@ -29,6 +31,7 @@ class Window final { private: tonic::DartPersistentValue library_; + int64_t window_id_; ViewportMetrics viewport_metrics_; }; diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index d8b372ba5387a..5eb19ad5e70f6 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -99,6 +99,7 @@ part 'engine/keyboard.dart'; part 'engine/mouse_cursor.dart'; part 'engine/onscreen_logging.dart'; part 'engine/picture.dart'; +part 'engine/platform_dispatcher.dart'; part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; @@ -224,17 +225,17 @@ void initializeEngine() { // part of the rasterization process, particularly in the HTML // renderer, takes place in the `SceneBuilder.build()`. _frameTimingsOnBuildStart(); - if (window._onBeginFrame != null) { - window.invokeOnBeginFrame( + if (EnginePlatformDispatcher.instance._onBeginFrame != null) { + EnginePlatformDispatcher.instance.invokeOnBeginFrame( Duration(microseconds: highResTimeMicroseconds)); } - if (window._onDrawFrame != null) { + if (EnginePlatformDispatcher.instance._onDrawFrame != null) { // TODO(yjbanov): technically Flutter flushes microtasks between // onBeginFrame and onDrawFrame. We don't, which hasn't // been an issue yet, but eventually we'll have to // implement it properly. - window.invokeOnDrawFrame(); + EnginePlatformDispatcher.instance.invokeOnDrawFrame(); } }); } diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 0f054559b18a0..96f0c304950f8 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -71,7 +71,7 @@ class BitmapCanvas extends EngineCanvas { /// Keeps track of what device pixel ratio was used when this [BitmapCanvas] /// was created. - final double _devicePixelRatio = EngineWindow.browserDevicePixelRatio; + final double _devicePixelRatio = EnginePlatformDispatcher.browserDevicePixelRatio; // Compensation for [_initializeViewport] snapping canvas position to 1 pixel. int? _canvasPositionX, _canvasPositionY; @@ -150,13 +150,13 @@ class BitmapCanvas extends EngineCanvas { static int _widthToPhysical(double width) { final double boundsWidth = width + 1; - return (boundsWidth * EngineWindow.browserDevicePixelRatio).ceil() + + return (boundsWidth * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } static int _heightToPhysical(double height) { final double boundsHeight = height + 1; - return (boundsHeight * EngineWindow.browserDevicePixelRatio).ceil() + + return (boundsHeight * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } @@ -198,7 +198,7 @@ class BitmapCanvas extends EngineCanvas { /// * [PersistedPicture._recycleCanvas] which also uses this method /// for the same reason. bool isReusable() { - return _devicePixelRatio == EngineWindow.browserDevicePixelRatio; + return _devicePixelRatio == EnginePlatformDispatcher.browserDevicePixelRatio; } /// Returns a "data://" URI containing a representation of the image in this diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index d56351124eed6..b0c3a7d2f3388 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -99,9 +99,9 @@ class _CanvasPool extends _SaveStackTracking { // * To make sure that when we scale the canvas by devicePixelRatio (see // _initializeViewport below) the pixels line up. final double cssWidth = - _widthInBitmapPixels / EngineWindow.browserDevicePixelRatio; + _widthInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; final double cssHeight = - _heightInBitmapPixels / EngineWindow.browserDevicePixelRatio; + _heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; canvas = html.CanvasElement( width: _widthInBitmapPixels, height: _heightInBitmapPixels, @@ -194,7 +194,7 @@ class _CanvasPool extends _SaveStackTracking { clipTimeTransform[5] != prevTransform[5] || clipTimeTransform[12] != prevTransform[12] || clipTimeTransform[13] != prevTransform[13]) { - final double ratio = EngineWindow.browserDevicePixelRatio; + final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform( clipTimeTransform[0], @@ -223,7 +223,7 @@ class _CanvasPool extends _SaveStackTracking { transform[5] != prevTransform[5] || transform[12] != prevTransform[12] || transform[13] != prevTransform[13]) { - final double ratio = EngineWindow.browserDevicePixelRatio; + final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform(transform[0], transform[1], transform[4], transform[5], transform[12], transform[13]); @@ -307,8 +307,8 @@ class _CanvasPool extends _SaveStackTracking { // This scale makes sure that 1 CSS pixel is translated to the correct // number of bitmap pixels. - ctx.scale(EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio); + ctx.scale(EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio); } void resetTransform() { diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 5f8c4a9b8e73c..a7c8c2f6dfe2f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -292,7 +292,7 @@ class HtmlViewEmbedder { // // HTML elements use logical (CSS) pixels, but we have been using physical // pixels, so scale down the head element to match the logical resolution. - final double scale = EngineWindow.browserDevicePixelRatio; + final double scale = EnginePlatformDispatcher.browserDevicePixelRatio; final double inverseScale = 1 / scale; final Matrix4 scaleMatrix = Matrix4.diagonal3Values(inverseScale, inverseScale, 1); diff --git a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart index eaca5c43ca4b9..74f013ed0ebbb 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart @@ -348,7 +348,7 @@ class SkiaObjects { if (_addedCleanupCallback) { return; } - window.rasterizer!.addPostFrameCallback(postFrameCleanUp); + EnginePlatformDispatcher.instance.rasterizer!.addPostFrameCallback(postFrameCleanUp); _addedCleanupCallback = true; } diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 415ea66c86190..0a35dc3a11262 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -466,7 +466,7 @@ flt-glass-pane * { } _localeSubscription = languageChangeEvent.forTarget(html.window) .listen(_languageDidChange); - window._updateLocales(); + EnginePlatformDispatcher.instance._updateLocales(); } /// Called immediately after browser window metrics change. @@ -481,18 +481,18 @@ flt-glass-pane * { void _metricsDidChange(html.Event? event) { if(isMobile && !window.isRotation() && textEditing.isEditing) { window.computeOnScreenKeyboardInsets(); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); } else { window._computePhysicalSize(); // When physical size changes this value has to be recalculated. window.computeOnScreenKeyboardInsets(); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); } } /// Called immediately after browser window language change. void _languageDidChange(html.Event event) { - window._updateLocales(); + EnginePlatformDispatcher.instance._updateLocales(); if (ui.window.onLocaleChanged != null) { ui.window.onLocaleChanged!(); } diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 0720dd4a2a864..3c2cdc86cba7e 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -668,8 +668,8 @@ class _OffscreenCanvas { height: heightInPixels, ); _glCanvas!.className = 'gl-canvas'; - final double cssWidth = widthInPixels / EngineWindow.browserDevicePixelRatio; - final double cssHeight = heightInPixels / EngineWindow.browserDevicePixelRatio; + final double cssWidth = widthInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; + final double cssHeight = heightInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; _glCanvas!.style ..position = 'absolute' ..width = '${cssWidth}px' diff --git a/lib/web_ui/lib/src/engine/html/surface_stats.dart b/lib/web_ui/lib/src/engine/html/surface_stats.dart index 911a825ad03ab..6e524a4f6e266 100644 --- a/lib/web_ui/lib/src/engine/html/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/html/surface_stats.dart @@ -125,9 +125,9 @@ void _debugRepaintSurfaceStatsOverlay(PersistedScene scene) { ..fill(); final double physicalScreenWidth = - html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; + html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; + html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; @@ -296,9 +296,9 @@ void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { return pixels; }).fold(0, (int total, int pixels) => total + pixels); final double physicalScreenWidth = - html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; + html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; + html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; final double screenPixelRatio = pixelCount / physicsScreenPixelCount; diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index 558e53f6f927a..ad9e11204dc43 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -81,7 +81,7 @@ class Keyboard { final html.KeyboardEvent keyboardEvent = event; - if (window._onPlatformMessage == null) { + if (EnginePlatformDispatcher.instance._onPlatformMessage == null) { return; } @@ -135,7 +135,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - window.invokeOnPlatformMessage('flutter/keyevent', + EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } @@ -157,7 +157,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - window.invokeOnPlatformMessage('flutter/keyevent', + EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } } diff --git a/lib/web_ui/lib/src/engine/navigation/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart index 0a578162a9096..5cfc6c6c0af8f 100644 --- a/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -151,8 +151,8 @@ class MultiEntriesBrowserHistory extends BrowserHistory { currentPath); } _lastSeenSerialCount = _currentSerialCount; - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRouteInformation', { @@ -272,8 +272,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _setupFlutterEntry(urlStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(_popRouteMethodCall), (_) {}, @@ -291,8 +291,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _userProvidedRouteName = null; // Send a 'pushRoute' platform message so the app handles it accordingly. - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRoute', newRouteName), diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart new file mode 100644 index 0000000000000..d1e72189df398 --- /dev/null +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -0,0 +1,923 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of engine; + +/// Requests that the browser schedule a frame. +/// +/// This may be overridden in tests, for example, to pump fake frames. +ui.VoidCallback? scheduleFrameCallback; + +/// Platform event dispatcher. +/// +/// This is the central entry point for platform messages and configuration +/// events from the platform. +class EnginePlatformDispatcher extends ui.PlatformDispatcher { + /// Private constructor, since only dart:ui is supposed to create one of + /// these. + EnginePlatformDispatcher._() { + _addBrightnessMediaQueryListener(); + } + + /// The [EnginePlatformDispatcher] singleton. + static EnginePlatformDispatcher get instance => _instance; + static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher._(); + + /// The current platform configuration. + @override + ui.PlatformConfiguration get configuration => _configuration; + ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(locales: parseBrowserLanguages()); + + /// Receives all events related to platform configuration changes. + @override + ui.VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; + ui.VoidCallback? _onPlatformConfigurationChanged; + Zone? _onPlatformConfigurationChangedZone; + @override + set onPlatformConfigurationChanged(ui.VoidCallback? callback) { + _onPlatformConfigurationChanged = callback; + _onPlatformConfigurationChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformConfigurationChanged() { + _invoke(_onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); + } + + /// The current list of windows, + Iterable get views => _windows.values; + Map _windows = {}; + + /// A map of opaque platform window identifiers to window configurations. + /// + /// This should be considered a protected member, only to be used by + /// [PlatformDispatcher] subclasses. + Map _windowConfigurations = {}; + + /// A callback that is invoked whenever the platform's [devicePixelRatio], + /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] + /// values change, for example when the device is rotated or when the + /// application is resized (e.g. when showing applications side-by-side + /// on Android). + /// + /// The engine invokes this callback in the same zone in which the callback + /// was set. + /// + /// The framework registers with this callback and updates the layout + /// appropriately. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// register for notifications when this is called. + /// * [MediaQuery.of], a simpler mechanism for the same. + @override + ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; + ui.VoidCallback? _onMetricsChanged; + Zone? _onMetricsChangedZone; + @override + set onMetricsChanged(ui.VoidCallback? callback) { + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnMetricsChanged() { + if (_onMetricsChanged != null) { + _invoke(_onMetricsChanged, _onMetricsChangedZone); + } + } + + /// Returns device pixel ratio returned by browser. + static double get browserDevicePixelRatio { + double? ratio = html.window.devicePixelRatio as double?; + // Guard against WebOS returning 0 and other browsers returning null. + return (ratio == null || ratio == 0.0) ? 1.0 : ratio; + } + + /// A callback invoked when any window begins a frame. + /// + /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} + /// A callback that is invoked to notify the application that it is an + /// appropriate time to provide a scene using the [SceneBuilder] API and the + /// [PlatformWindow.render] method. + /// When possible, this is driven by the hardware VSync signal of the attached + /// screen with the highest VSync rate. This is only called if + /// [PlatformWindow.scheduleFrame] has been called since the last time this + /// callback was invoked. + /// {@endtemplate} + @override + ui.FrameCallback? get onBeginFrame => _onBeginFrame; + ui.FrameCallback? _onBeginFrame; + Zone? _onBeginFrameZone; + @override + set onBeginFrame(ui.FrameCallback? callback) { + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnBeginFrame(Duration duration) { + _invoke1(_onBeginFrame, _onBeginFrameZone, duration); + } + + /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} + /// A callback that is invoked for each frame after [onBeginFrame] has + /// completed and after the microtask queue has been drained. + /// + /// This can be used to implement a second phase of frame rendering that + /// happens after any deferred work queued by the [onBeginFrame] phase. + /// {@endtemplate} + @override + ui.VoidCallback? get onDrawFrame => _onDrawFrame; + ui.VoidCallback? _onDrawFrame; + Zone? _onDrawFrameZone; + @override + set onDrawFrame(ui.VoidCallback? callback) { + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnDrawFrame() { + _invoke(_onDrawFrame, _onDrawFrameZone); + } + + /// A callback that is invoked when pointer data is available. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [GestureBinding], the Flutter framework class which manages pointer + /// events. + @override + ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + ui.PointerDataPacketCallback? _onPointerDataPacket; + Zone? _onPointerDataPacketZone; + @override + set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPointerDataPacket(ui.PointerDataPacket dataPacket) { + _invoke1(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket); + } + + /// A callback that is invoked to report the [FrameTiming] of recently + /// rasterized frames. + /// + /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use + /// [Window.onReportTimings] directly because + /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. + /// + /// This can be used to see if the application has missed frames (through + /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high + /// latencies (through [FrameTiming.totalSpan]). + /// + /// Unlike [Timeline], the timing information here is available in the release + /// mode (additional to the profile and the debug mode). Hence this can be + /// used to monitor the application's performance in the wild. + /// + /// {@macro dart.ui.TimingsCallback.list} + /// + /// If this is null, no additional work will be done. If this is not null, + /// Flutter spends less than 0.1ms every 1 second to report the timings + /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for + /// 60fps), or 0.01% CPU usage per second. + @override + ui.TimingsCallback? get onReportTimings => _onReportTimings; + ui.TimingsCallback? _onReportTimings; + Zone? _onReportTimingsZone; + @override + set onReportTimings(ui.TimingsCallback? callback) { + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnReportTimings(List timings) { + _invoke1>(_onReportTimings, _onReportTimingsZone, timings); + } + + @override + void sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + _sendPlatformMessage(name, data, _zonedPlatformMessageResponseCallback(callback)); + } + + @override + ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + ui.PlatformMessageCallback? _onPlatformMessage; + Zone? _onPlatformMessageZone; + @override + set onPlatformMessage(ui.PlatformMessageCallback? callback) { + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformMessage(String name, ByteData? data, + ui.PlatformMessageResponseCallback callback) { + _invoke3( + _onPlatformMessage, + _onPlatformMessageZone, + name, + data, + callback, + ); + } + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static ui.PlatformMessageResponseCallback? + _zonedPlatformMessageResponseCallback( + ui.PlatformMessageResponseCallback? callback) { + if (callback == null) + return null; + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + void _sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + // In widget tests we want to bypass processing of platform messages. + if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { + return; + } + + if (_debugPrintPlatformMessages) { + print('Sent platform message on channel: "$name"'); + } + + if (assertionsEnabled && name == 'flutter/debug-echo') { + // Echoes back the data unchanged. Used for testing purposes. + _replyToPlatformMessage(callback, data); + return; + } + + switch (name) { + /// This should be in sync with shell/common/shell.cc + case 'flutter/skia': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'Skia.setResourceCacheMaxBytes': + if (decoded.arguments is int) { + rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); + } + break; + } + return; + + case 'flutter/assets': + assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison + final String url = utf8.decode(data!.buffer.asUint8List()); + ui.webOnlyAssetManager.load(url).then((ByteData assetData) { + _replyToPlatformMessage(callback, assetData); + }, onError: (dynamic error) { + html.window.console + .warn('Error while trying to load an asset: $error'); + _replyToPlatformMessage(callback, null); + }); + return; + + case 'flutter/platform': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'SystemNavigator.pop': + // TODO(gspencergoog): As multi-window support expands, the pop call + // will need to include the window ID. Right now only one window is + // supported. + (_windows[0] as EngineFlutterWindow).browserHistory.exit().then((_) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + }); + return; + case 'HapticFeedback.vibrate': + final String type = decoded.arguments; + domRenderer.vibrate(_getHapticFeedbackDuration(type)); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setApplicationSwitcherDescription': + final Map arguments = decoded.arguments; + domRenderer.setTitle(arguments['label']); + domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setPreferredOrientations': + final List arguments = decoded.arguments; + domRenderer.setPreferredOrientation(arguments).then((bool success) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(success)); + }); + return; + case 'SystemSound.play': + // There are no default system sounds on web. + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return; + case 'Clipboard.setData': + ClipboardMessageHandler().setDataMethodCall(decoded, callback); + return; + case 'Clipboard.getData': + ClipboardMessageHandler().getDataMethodCall(callback); + return; + } + break; + + // Dispatched by the bindings to delay service worker initialization. + case 'flutter/service_worker': + html.window.dispatchEvent(html.Event('flutter-first-frame')); + return; + + case 'flutter/textinput': + textEditing.channel.handleTextInput(data, callback); + return; + + case 'flutter/mousecursor': + const MethodCodec codec = StandardMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map arguments = decoded.arguments; + switch (decoded.method) { + case 'activateSystemCursor': + MouseCursor.instance!.activateSystemCursor(arguments['kind']); + } + return; + + case 'flutter/web_test_e2e': + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage( + callback, + codec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(codec, data))); + return; + + case 'flutter/platform_views': + if (experimentalUseSkia) { + rasterizer!.surface.viewEmbedder + .handlePlatformViewCall(data, callback); + } else { + ui.handlePlatformViewCall(data!, callback!); + } + return; + + case 'flutter/accessibility': + // In widget tests we want to bypass processing of platform messages. + final StandardMessageCodec codec = StandardMessageCodec(); + accessibilityAnnouncements.handleMessage(codec, data); + _replyToPlatformMessage(callback, codec.encodeMessage(true)); + return; + + case 'flutter/navigation': + // TODO(gspencergoog): As multi-window support expands, the navigation call + // will need to include the window ID. Right now only one window is + // supported. + (_windows[0] as EngineFlutterWindow).handleNavigationMessage(data).then((bool handled) { + if (handled) { + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + } else { + callback?.call(null); + } + }); + + // As soon as Flutter starts taking control of the app navigation, we + // should reset _defaultRouteName to "/" so it doesn't have any + // further effect after this point. + _defaultRouteName = '/'; + return; + } + + if (pluginMessageCallHandler != null) { + pluginMessageCallHandler!(name, data, callback); + return; + } + + // Passing [null] to [callback] indicates that the platform message isn't + // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is + // handled. + _replyToPlatformMessage(callback, null); + } + + + + int _getHapticFeedbackDuration(String type) { + switch (type) { + case 'HapticFeedbackType.lightImpact': + return DomRenderer.vibrateLightImpact; + case 'HapticFeedbackType.mediumImpact': + return DomRenderer.vibrateMediumImpact; + case 'HapticFeedbackType.heavyImpact': + return DomRenderer.vibrateHeavyImpact; + case 'HapticFeedbackType.selectionClick': + return DomRenderer.vibrateSelectionClick; + default: + return DomRenderer.vibrateLongPress; + } + } + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] + /// and [onDrawFrame] callbacks be invoked. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + @override + void scheduleFrame() { + if (scheduleFrameCallback == null) { + throw new Exception( + 'scheduleFrameCallback must be initialized first.'); + } + scheduleFrameCallback!(); + } + + /// Updates the application's rendering on the GPU with the newly provided + /// [Scene]. This function must be called within the scope of the + /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function + /// is called a second time during a single [onBeginFrame]/[onDrawFrame] + /// callback sequence or called outside the scope of those callbacks, the call + /// will be ignored. + /// + /// To record graphical operations, first create a [PictureRecorder], then + /// construct a [Canvas], passing that [PictureRecorder] to its constructor. + /// After issuing all the graphical operations, call the + /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain + /// the final [Picture] that represents the issued graphical operations. + /// + /// Next, create a [SceneBuilder], and add the [Picture] to it using + /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can + /// then obtain a [Scene] object, which you can display to the user via this + /// [render] function. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + /// * [RendererBinding], the Flutter framework class which manages layout and + /// painting. + @override + void render(ui.Scene scene, [ui.FlutterView? view]) { + if (experimentalUseSkia) { + // "Build finish" and "raster start" happen back-to-back because we + // render on the same thread, so there's no overhead from hopping to + // another thread. + // + // CanvasKit works differently from the HTML renderer in that in HTML + // we update the DOM in SceneBuilder.build, which is these function calls + // here are CanvasKit-only. + _frameTimingsOnBuildFinish(); + _frameTimingsOnRasterStart(); + + final LayerScene layerScene = scene as LayerScene; + rasterizer!.draw(layerScene.layerTree); + } else { + final SurfaceScene surfaceScene = scene as SurfaceScene; + domRenderer.renderScene(surfaceScene.webOnlyRootElement); + } + _frameTimingsOnRasterFinish(); + } + + /// Additional accessibility features that may be enabled by the platform. + ui.AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; + + /// A callback that is invoked when the value of [accessibilityFeatures] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; + ui.VoidCallback? _onAccessibilityFeaturesChanged; + Zone? _onAccessibilityFeaturesChangedZone; + set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnAccessibilityFeaturesChanged() { + _invoke(_onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); + } + + /// Change the retained semantics data about this window. + /// + /// If [semanticsEnabled] is true, the user has requested that this function + /// be called whenever the semantic content of this window changes. + /// + /// In either case, this function disposes the given update, which means the + /// semantics update cannot be used further. + void updateSemantics(ui.SemanticsUpdate update) { + EngineSemanticsOwner.instance.updateSemantics(update); + } + + /// We use the first locale in the [locales] list instead of the browser's + /// built-in `navigator.language` because browsers do not agree on the + /// implementation. + /// + /// See also: + /// + /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, + /// which explains browser quirks in the implementation notes. + ui.Locale get locale => locales.first; + + /// The full system-reported supported locales of the device. + /// + /// This establishes the language and formatting conventions that application + /// should, if possible, use to render their user interface. + /// + /// The list is ordered in order of priority, with lower-indexed locales being + /// preferred over higher-indexed ones. The first element is the primary [locale]. + /// + /// The [onLocaleChanged] callback is called whenever this value changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + List get locales => configuration.locales; + + /// Performs the platform-native locale resolution. + /// + /// Each platform may return different results. + /// + /// If the platform fails to resolve a locale, then this will return null. + /// + /// This method returns synchronously and is a direct call to + /// platform specific APIs without invoking method channels. + ui.Locale? computePlatformResolvedLocale(List supportedLocales) { + // TODO(garyq): Implement on web. + return null; + } + + /// A callback that is invoked whenever [locale] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; + ui.VoidCallback? _onLocaleChanged; + Zone? _onLocaleChangedZone; + set onLocaleChanged(ui.VoidCallback? callback) { + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; + } + + /// The locale used when we fail to get the list from the browser. + static const ui.Locale _defaultLocale = const ui.Locale('en', 'US'); + + /// Sets locales to an empty list. + /// + /// The empty list is not a valid value for locales. This is only used for + /// testing locale update logic. + void debugResetLocales() { + _configuration = _configuration.copyWith(locales: const []); + } + + // Called by DomRenderer when browser languages change. + void _updateLocales() { + _configuration = _configuration.copyWith(locales: parseBrowserLanguages()); + } + + static List parseBrowserLanguages() { + // TODO(yjbanov): find a solution for IE + var languages = html.window.navigator.languages; + if (languages == null || languages.isEmpty) { + // To make it easier for the app code, let's not leave the locales list + // empty. This way there's fewer corner cases for apps to handle. + return const [_defaultLocale]; + } + + final List locales = []; + for (final String language in languages) { + final List parts = language.split('-'); + if (parts.length > 1) { + locales.add(ui.Locale(parts.first, parts.last)); + } else { + locales.add(ui.Locale(language)); + } + } + + assert(locales.isNotEmpty); + return locales; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnLocaleChanged() { + _invoke(_onLocaleChanged, _onLocaleChangedZone); + } + + /// The system-reported text scale. + /// + /// This establishes the text scaling factor to use when rendering text, + /// according to the user's platform preferences. + /// + /// The [onTextScaleFactorChanged] callback is called whenever this value + /// changes. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + double get textScaleFactor => configuration.textScaleFactor; + + /// The setting indicating whether time should always be shown in the 24-hour + /// format. + /// + /// This option is used by [showTimePicker]. + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + /// A callback that is invoked whenever [textScaleFactor] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + ui.VoidCallback? _onTextScaleFactorChanged; + Zone? _onTextScaleFactorChangedZone; + set onTextScaleFactorChanged(ui.VoidCallback? callback) { + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnTextScaleFactorChanged() { + _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); + } + + /// The setting indicating the current brightness mode of the host platform. + /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. + ui.Brightness get platformBrightness => configuration.platformBrightness; + + /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] + /// callback if [_platformBrightness] changed. + void _updatePlatformBrightness(ui.Brightness value) { + if (configuration.platformBrightness != value) { + _configuration = configuration.copyWith(platformBrightness: value); + invokeOnPlatformConfigurationChanged(); + invokeOnPlatformBrightnessChanged(); + } + } + + /// Reference to css media query that indicates the user theme preference on the web. + final html.MediaQueryList _brightnessMediaQuery = + html.window.matchMedia('(prefers-color-scheme: dark)'); + + /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. + /// + /// Updates the [_platformBrightness] with the new user preference. + html.EventListener? _brightnessMediaQueryListener; + + /// Set the callback function for listening changes in [_brightnessMediaQuery] value. + void _addBrightnessMediaQueryListener() { + _updatePlatformBrightness(_brightnessMediaQuery.matches + ? ui.Brightness.dark + : ui.Brightness.light); + + _brightnessMediaQueryListener = (html.Event event) { + final html.MediaQueryListEvent mqEvent = + event as html.MediaQueryListEvent; + _updatePlatformBrightness( + mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); + }; + _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); + registerHotRestartListener(() { + _removeBrightnessMediaQueryListener(); + }); + } + + /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. + void _removeBrightnessMediaQueryListener() { + _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); + _brightnessMediaQueryListener = null; + } + + /// A callback that is invoked whenever [platformBrightness] changes value. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this callback is invoked. + ui.VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; + ui.VoidCallback? _onPlatformBrightnessChanged; + Zone? _onPlatformBrightnessChangedZone; + set onPlatformBrightnessChanged(ui.VoidCallback? callback) { + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformBrightnessChanged() { + _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); + } + + /// Whether the user has requested that [updateSemantics] be called when + /// the semantic contents of window changes. + /// + /// The [onSemanticsEnabledChanged] callback is called whenever this value + /// changes. + bool get semanticsEnabled => configuration.semanticsEnabled; + + /// A callback that is invoked when the value of [semanticsEnabled] changes. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + ui.VoidCallback? _onSemanticsEnabledChanged; + Zone? _onSemanticsEnabledChangedZone; + set onSemanticsEnabledChanged(ui.VoidCallback? callback) { + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsEnabledChanged() { + _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); + } + + /// A callback that is invoked whenever the user requests an action to be + /// performed. + /// + /// This callback is used when the user expresses the action they wish to + /// perform based on the semantics supplied by [updateSemantics]. + /// + /// The framework invokes this callback in the same zone in which the + /// callback was set. + ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + ui.SemanticsActionCallback? _onSemanticsAction; + Zone? _onSemanticsActionZone; + set onSemanticsAction(ui.SemanticsActionCallback? callback) { + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsAction( + int id, ui.SemanticsAction action, ByteData? args) { + _invoke3( + _onSemanticsAction, _onSemanticsActionZone, id, action, args); + } + + /// The route or path that the embedder requested when the application was + /// launched. + /// + /// This will be the string "`/`" if no particular route was requested. + /// + /// ## Android + /// + /// On Android, calling + /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `createFlutterView` method in your `FlutterActivity` + /// subclass is a suitable time to set the value. The application's + /// `AndroidManifest.xml` file must also be updated to have a suitable + /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). + /// + /// ## iOS + /// + /// On iOS, calling + /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) + /// will set this value. The value must be set sufficiently early, i.e. before + /// the [runApp] call is executed in Dart, for this to have any effect on the + /// framework. The `application:didFinishLaunchingWithOptions:` method is a + /// suitable time to set this value. + /// + /// See also: + /// + /// * [Navigator], a widget that handles routing. + /// * [SystemChannels.navigation], which handles subsequent navigation + /// requests from the embedder. + String get defaultRouteName { + return _defaultRouteName ??= (_windows[0]! as EngineFlutterWindow).browserHistory.currentPath; + } + + /// Lazily initialized when the `defaultRouteName` getter is invoked. + /// + /// The reason for the lazy initialization is to give enough time for the app + /// to set [locationStrategy] in `lib/src/ui/initialization.dart`. + String? _defaultRouteName; + + @visibleForTesting + late Rasterizer? rasterizer = + experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; + + /// In Flutter, platform messages are exchanged between threads so the + /// messages and responses have to be exchanged asynchronously. We simulate + /// that by adding a zero-length delay to the reply. + void _replyToPlatformMessage( + ui.PlatformMessageResponseCallback? callback, + ByteData? data, + ) { + Future.delayed(Duration.zero).then((_) { + if (callback != null) { + callback(data); + } + }); + } +} + +bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { + final MethodCall decoded = codec.decodeMethodCall(data); + double ratio = double.parse(decoded.arguments); + switch (decoded.method) { + case 'setDevicePixelRatio': + window.debugOverrideDevicePixelRatio(ratio); + EnginePlatformDispatcher.instance.onMetricsChanged!(); + return true; + } + return false; +} + +/// Invokes [callback] inside the given [zone]. +void _invoke(void callback()?, Zone? zone) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(); + } else { + zone!.runGuarded(callback); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg]. +void _invoke1(void callback(A a)?, Zone? zone, A arg) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(arg); + } else { + zone!.runUnaryGuarded(callback, arg); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. +void _invoke3( + void callback(A1 a1, A2 a2, A3 a3)?, + Zone? zone, + A1 arg1, + A2 arg2, + A3 arg3, + ) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone!, Zone.current)) { + callback(arg1, arg2, arg3); + } else { + zone.runGuarded(() { + callback(arg1, arg2, arg3); + }); + } +} + diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index f6bb53110491b..ef0eb4667d779 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -125,9 +125,7 @@ class PointerBinding { void _onPointerData(Iterable data) { final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList()); - if (window._onPointerDataPacket != null) { - window.invokeOnPointerDataPacket(packet); - } + EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(packet); } } diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart index 312938dbc57ef..eeeab3d576d1c 100644 --- a/lib/web_ui/lib/src/engine/profiler.dart +++ b/lib/web_ui/lib/src/engine/profiler.dart @@ -110,7 +110,7 @@ class Profiler { /// Whether we are collecting [ui.FrameTiming]s. bool get _frameTimingsEnabled { - return window._onReportTimings != null; + return EnginePlatformDispatcher.instance._onReportTimings != null; } /// Collects frame timings from frames. @@ -202,7 +202,7 @@ void _frameTimingsOnRasterFinish() { _rasterFinishMicros = -1; if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) { _frameTimingsLastSubmitTime = now; - window.invokeOnReportTimings(_frameTimings); + EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings); _frameTimings = []; } } diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index 77fbb6daf5e47..616663a25d9ac 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -53,11 +53,11 @@ class Incrementable extends RoleManager { final int newInputValue = int.parse(_element.value!); if (newInputValue > _currentSurrogateValue) { _currentSurrogateValue += 1; - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.increase, null); } else if (newInputValue < _currentSurrogateValue) { _currentSurrogateValue -= 1; - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.decrease, null); } }); diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 50624ddd4ba09..9cae3a9189c11 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -53,20 +53,20 @@ class Scrollable extends RoleManager { final int semanticsId = semanticsObject.id; if (doScrollForward) { if (semanticsObject.isVerticalScrollContainer) { - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollUp, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollLeft, null); } } else { if (semanticsObject.isVerticalScrollContainer) { - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollDown, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollRight, null); } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index 57c02b5bf22db..f3b626e9b0535 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1253,8 +1253,8 @@ class EngineSemanticsOwner { _gestureModeClock?.datetime = null; } - if (window._onSemanticsEnabledChanged != null) { - window.invokeOnSemanticsEnabledChanged(); + if (EnginePlatformDispatcher.instance._onSemanticsEnabledChanged != null) { + EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); } } diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 3f58195b1d46e..8439dd3f70f08 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -40,7 +40,7 @@ class Tappable extends RoleManager { GestureMode.browserGestures) { return; } - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); }; element.addEventListener('click', _clickListener); diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 243a154c708b6..91b5d8ba9dc5b 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -148,7 +148,7 @@ class TextField extends RoleManager { } textEditing.useCustomEditableElement(textEditingElement); - window + EnginePlatformDispatcher.instance .invokeOnSemanticsAction(semanticsObject.id, ui.SemanticsAction.tap, null); }); } @@ -186,7 +186,7 @@ class TextField extends RoleManager { if (offsetX * offsetX + offsetY * offsetY < kTouchSlop) { // Recognize it as a tap that requires a keyboard. - window.invokeOnSemanticsAction( + EnginePlatformDispatcher.instance.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); } } else { 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 ce8b31d5fd23a..baa4b5e506d77 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 @@ -276,8 +276,8 @@ class EngineAutofillForm { /// Sends the 'TextInputClient.updateEditingStateWithTag' message to the framework. void _sendAutofillEditingState(String? tag, EditingState editingState) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1363,7 +1363,7 @@ class TextEditingChannel { throw StateError( 'Unsupported method call on the flutter/textinput channel: ${call.method}'); } - window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + EnginePlatformDispatcher.instance._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); } /// Used for submitting the forms attached on the DOM. @@ -1392,8 +1392,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.updateEditingState' message to the framework. void updateEditingState(int? clientId, EditingState? editingState) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall('TextInputClient.updateEditingState', [ @@ -1408,8 +1408,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.performAction' message to the framework. void performAction(int? clientId, String? inputAction) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1424,8 +1424,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.onConnectionClosed' message to the framework. void onConnectionClosed(int? clientId) { - if (window._onPlatformMessage != null) { - window.invokeOnPlatformMessage( + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 277f5484e2f07..22a252c578179 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -482,13 +482,13 @@ final ByteData? _fontChangeMessage = JSONMessageCodec().encodeMessage( sendFontChangeMessage() async { - if (window._onPlatformMessage != null) + if (EnginePlatformDispatcher.instance._onPlatformMessage != null) if (!_fontChangeScheduled) { _fontChangeScheduled = true; // Batch updates into next animationframe. html.window.requestAnimationFrame((num _) { _fontChangeScheduled = false; - window.invokeOnPlatformMessage( + EnginePlatformDispatcher.instance.invokeOnPlatformMessage( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 19597f6ae32de..ea0d5743c3701 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -6,56 +6,103 @@ part of engine; /// When set to true, all platform messages will be printed to the console. -const bool _debugPrintPlatformMessages = false; +const bool/*!*/ _debugPrintPlatformMessages = false; -/// Requests that the browser schedule a frame. -/// -/// This may be overridden in tests, for example, to pump fake frames. -ui.VoidCallback? scheduleFrameCallback; +/// The Web implementation of [ui.Window]. +// TODO(gspencergoog): Once the framework no longer uses ui.Window, make this extend +// ui.SingletonFlutterWindow instead. +class EngineFlutterWindow extends ui.Window { + EngineFlutterWindow(this._windowId, this.platformDispatcher) { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + engineDispatcher._windows[_windowId] = this; + engineDispatcher._windowConfigurations[_windowId] = ui.ViewConfiguration(); + _addUrlStrategyListener(); + } -typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); + final Object _windowId; + final ui.PlatformDispatcher platformDispatcher; -/// A JavaScript hook to customize the URL strategy of a Flutter app. -// -// Keep this js name in sync with flutter_web_plugins. Find it at: -// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart -// -// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 -@JS('_flutter_web_set_location_strategy') -external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); + void _addUrlStrategyListener() { + _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { + assert( + _browserHistory == null, + 'Cannot set URL strategy more than once.', + ); + final UrlStrategy? strategy = + jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + }); + registerHotRestartListener(() { + _jsSetUrlStrategy = null; + }); + } -UrlStrategy? _createDefaultUrlStrategy() { - return ui.debugEmulateFlutterTesterEnvironment - ? null - : const HashUrlStrategy(); -} + /// Handles the browser history integration to allow users to use the back + /// button, etc. + @visibleForTesting + BrowserHistory get browserHistory { + return _browserHistory ??= + MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); + } -/// The Web implementation of [ui.Window]. -class EngineWindow extends ui.Window { - EngineWindow() { - _addBrightnessMediaQueryListener(); - _addUrlStrategyListener(); + BrowserHistory? _browserHistory; + + Future _useSingleEntryBrowserHistory() async { + if (_browserHistory is SingleEntryBrowserHistory) { + return; + } + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); } - @override - double get devicePixelRatio => - _debugDevicePixelRatio ?? browserDevicePixelRatio; - - /// Returns device pixel ratio returned by browser. - static double get browserDevicePixelRatio { - double? ratio = html.window.devicePixelRatio as double?; - // Guard against WebOS returning 0 and other browsers returning null. - return (ratio == null || ratio == 0.0) ? 1.0 : ratio; + @visibleForTesting + Future debugInitializeHistory( + UrlStrategy? strategy, { + required bool useSingle, + }) async { + await _browserHistory?.tearDown(); + if (useSingle) { + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } else { + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } } - /// Overrides the default device pixel ratio. - /// - /// This is useful in tests to emulate screens of different dimensions. - void debugOverrideDevicePixelRatio(double value) { - _debugDevicePixelRatio = value; + @visibleForTesting + Future debugResetHistory() async { + await _browserHistory?.tearDown(); + _browserHistory = null; } - double? _debugDevicePixelRatio; + Future handleNavigationMessage( + ByteData? data, + ) async { + final MethodCall decoded = JSONMethodCodec().decodeMethodCall(data); + final Map arguments = decoded.arguments; + + switch (decoded.method) { + case 'routeUpdated': + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + return true; + case 'routeInformationUpdated': + assert(browserHistory is MultiEntriesBrowserHistory); + browserHistory.setRouteName( + arguments['location'], + state: arguments['state'], + ); + return true; + } + return false; + } + + @override + ui.ViewConfiguration get viewConfiguration { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + assert(engineDispatcher._windowConfigurations.containsKey(_windowId)); + return engineDispatcher._windowConfigurations[_windowId] ?? ui.ViewConfiguration(); + } @override ui.Size get physicalSize { @@ -164,713 +211,55 @@ class EngineWindow extends ui.Window { /// Overrides the value of [physicalSize] in tests. ui.Size? webOnlyDebugPhysicalSizeOverride; +} - /// Handles the browser history integration to allow users to use the back - /// button, etc. - @visibleForTesting - BrowserHistory get browserHistory { - return _browserHistory ??= - MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); - } - - BrowserHistory? _browserHistory; - - Future _useSingleEntryBrowserHistory() async { - if (_browserHistory is SingleEntryBrowserHistory) { - return; - } - final UrlStrategy? strategy = _browserHistory?.urlStrategy; - await _browserHistory?.tearDown(); - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } - - /// Lazily initialized when the `defaultRouteName` getter is invoked. - /// - /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] - /// in `lib/src/ui/initialization.dart`. - String? _defaultRouteName; - - @override - String get defaultRouteName { - return _defaultRouteName ??= browserHistory.currentPath; - } - - @override - void scheduleFrame() { - if (scheduleFrameCallback == null) { - throw new Exception('scheduleFrameCallback must be initialized first.'); - } - scheduleFrameCallback!(); - } - - @override - ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - ui.VoidCallback? _onTextScaleFactorChanged; - Zone? _onTextScaleFactorChangedZone; - @override - set onTextScaleFactorChanged(ui.VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnTextScaleFactorChanged() { - _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); - } - - @override - ui.VoidCallback? get onPlatformBrightnessChanged => - _onPlatformBrightnessChanged; - ui.VoidCallback? _onPlatformBrightnessChanged; - Zone? _onPlatformBrightnessChangedZone; - @override - set onPlatformBrightnessChanged(ui.VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformBrightnessChanged() { - _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); - } - - @override - ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; - ui.VoidCallback? _onMetricsChanged; - Zone _onMetricsChangedZone = Zone.root; - @override - set onMetricsChanged(ui.VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnMetricsChanged() { - if (window._onMetricsChanged != null) { - _invoke(_onMetricsChanged, _onMetricsChangedZone); - } - } - - @override - ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; - ui.VoidCallback? _onLocaleChanged; - Zone? _onLocaleChangedZone; - @override - set onLocaleChanged(ui.VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; - } - - /// The locale used when we fail to get the list from the browser. - static const _defaultLocale = const ui.Locale('en', 'US'); - - /// We use the first locale in the [locales] list instead of the browser's - /// built-in `navigator.language` because browsers do not agree on the - /// implementation. - /// - /// See also: - /// - /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, - /// which explains browser quirks in the implementation notes. - @override - ui.Locale get locale => _locales!.first; - - @override - List? get locales => _locales; - List? _locales = parseBrowserLanguages(); - - /// Sets locales to `null`. - /// - /// `null` is not a valid value for locales. This is only used for testing - /// locale update logic. - void debugResetLocales() { - _locales = null; - } - - // Called by DomRenderer when browser languages change. - void _updateLocales() { - _locales = parseBrowserLanguages(); - } - - static List parseBrowserLanguages() { - // TODO(yjbanov): find a solution for IE - var languages = html.window.navigator.languages; - if (languages == null || languages.isEmpty) { - // To make it easier for the app code, let's not leave the locales list - // empty. This way there's fewer corner cases for apps to handle. - return const [_defaultLocale]; - } - - final List locales = []; - for (final String language in languages) { - final List parts = language.split('-'); - if (parts.length > 1) { - locales.add(ui.Locale(parts.first, parts.last)); - } else { - locales.add(ui.Locale(language)); - } - } - - assert(locales.isNotEmpty); - return locales; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnLocaleChanged() { - _invoke(_onLocaleChanged, _onLocaleChangedZone); - } - - @override - ui.FrameCallback? get onBeginFrame => _onBeginFrame; - ui.FrameCallback? _onBeginFrame; - Zone? _onBeginFrameZone; - @override - set onBeginFrame(ui.FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnBeginFrame(Duration duration) { - _invoke1(_onBeginFrame, _onBeginFrameZone, duration); - } - - @override - ui.TimingsCallback? get onReportTimings => _onReportTimings; - ui.TimingsCallback? _onReportTimings; - Zone? _onReportTimingsZone; - @override - set onReportTimings(ui.TimingsCallback? callback) { - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnReportTimings(List timings) { - _invoke1>( - _onReportTimings, _onReportTimingsZone, timings); - } - - @override - ui.VoidCallback? get onDrawFrame => _onDrawFrame; - ui.VoidCallback? _onDrawFrame; - Zone? _onDrawFrameZone; - @override - set onDrawFrame(ui.VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnDrawFrame() { - _invoke(_onDrawFrame, _onDrawFrameZone); - } - - @override - ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - ui.PointerDataPacketCallback? _onPointerDataPacket; - Zone? _onPointerDataPacketZone; - @override - set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPointerDataPacket(ui.PointerDataPacket packet) { - _invoke1( - _onPointerDataPacket, _onPointerDataPacketZone, packet); - } - - @override - ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - ui.VoidCallback? _onSemanticsEnabledChanged; - Zone? _onSemanticsEnabledChangedZone; - @override - set onSemanticsEnabledChanged(ui.VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsEnabledChanged() { - _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); - } - - @override - ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - ui.SemanticsActionCallback? _onSemanticsAction; - Zone? _onSemanticsActionZone; - @override - set onSemanticsAction(ui.SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsAction( - int id, ui.SemanticsAction action, ByteData? args) { - _invoke3( - _onSemanticsAction, _onSemanticsActionZone, id, action, args); - } - - @override - ui.VoidCallback? get onAccessibilityFeaturesChanged => - _onAccessibilityFeaturesChanged; - ui.VoidCallback? _onAccessibilityFeaturesChanged; - Zone? _onAccessibilityFeaturesChangedZone; - @override - set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnAccessibilityFeaturesChanged() { - _invoke( - _onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); - } - - @override - ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - ui.PlatformMessageCallback? _onPlatformMessage; - Zone? _onPlatformMessageZone; - @override - set onPlatformMessage(ui.PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformMessage(String name, ByteData? data, - ui.PlatformMessageResponseCallback callback) { - _invoke3( - _onPlatformMessage, - _onPlatformMessageZone, - name, - data, - callback, - ); - } - - @override - void sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - _sendPlatformMessage( - name, data, _zonedPlatformMessageResponseCallback(callback)); - } - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? - _zonedPlatformMessageResponseCallback( - ui.PlatformMessageResponseCallback? callback) { - if (callback == null) { - return null; - } - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; - } - - void _sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - // In widget tests we want to bypass processing of platform messages. - if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { - return; - } - - if (_debugPrintPlatformMessages) { - print('Sent platform message on channel: "$name"'); - } - - if (assertionsEnabled && name == 'flutter/debug-echo') { - // Echoes back the data unchanged. Used for testing purpopses. - _replyToPlatformMessage(callback, data); - return; - } - - switch (name) { - /// This should be in sync with shell/common/shell.cc - case 'flutter/skia': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'Skia.setResourceCacheMaxBytes': - if (decoded.arguments is int) { - rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); - } - break; - } - - return; - case 'flutter/assets': - assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison - final String url = utf8.decode(data!.buffer.asUint8List()); - ui.webOnlyAssetManager.load(url).then((ByteData assetData) { - _replyToPlatformMessage(callback, assetData); - }, onError: (dynamic error) { - html.window.console - .warn('Error while trying to load an asset: $error'); - _replyToPlatformMessage(callback, null); - }); - return; - - case 'flutter/platform': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'SystemNavigator.pop': - browserHistory.exit().then((_) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - }); - return; - case 'HapticFeedback.vibrate': - final String? type = decoded.arguments; - domRenderer.vibrate(_getHapticFeedbackDuration(type)); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setApplicationSwitcherDescription': - final Map arguments = decoded.arguments; - domRenderer.setTitle(arguments['label']); - domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setPreferredOrientations': - final List? arguments = decoded.arguments; - domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(success)); - }); - return; - case 'SystemSound.play': - // There are no default system sounds on web. - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - return; - case 'Clipboard.setData': - ClipboardMessageHandler().setDataMethodCall(decoded, callback); - return; - case 'Clipboard.getData': - ClipboardMessageHandler().getDataMethodCall(callback); - return; - } - break; - - // Dispatched by the bindings to delay service worker initialization. - case 'flutter/service_worker': - html.window.dispatchEvent(html.Event('flutter-first-frame')); - return; - - case 'flutter/textinput': - textEditing.channel.handleTextInput(data, callback); - return; - - case 'flutter/mousecursor': - const MethodCodec codec = StandardMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map? arguments = decoded.arguments; - switch (decoded.method) { - case 'activateSystemCursor': - MouseCursor.instance!.activateSystemCursor(arguments!['kind']); - } - return; - - case 'flutter/web_test_e2e': - const MethodCodec codec = JSONMethodCodec(); - _replyToPlatformMessage( - callback, - codec.encodeSuccessEnvelope( - _handleWebTestEnd2EndMessage(codec, data))); - return; - - case 'flutter/platform_views': - if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder - .handlePlatformViewCall(data, callback); - } else { - ui.handlePlatformViewCall(data!, callback!); - } - return; - - case 'flutter/accessibility': - // In widget tests we want to bypass processing of platform messages. - final StandardMessageCodec codec = StandardMessageCodec(); - accessibilityAnnouncements.handleMessage(codec, data); - _replyToPlatformMessage(callback, codec.encodeMessage(true)); - return; - - case 'flutter/navigation': - _handleNavigationMessage(data, callback).then((handled) { - if (!handled && callback != null) { - callback(null); - } - }); - // As soon as Flutter starts taking control of the app navigation, we - // should reset [_defaultRouteName] to "/" so it doesn't have any - // further effect after this point. - _defaultRouteName = '/'; - return; - } - - if (pluginMessageCallHandler != null) { - pluginMessageCallHandler!(name, data, callback); - return; - } - - // Passing [null] to [callback] indicates that the platform message isn't - // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is - // handled. - _replyToPlatformMessage(callback, null); - } - - @visibleForTesting - Future debugInitializeHistory( - UrlStrategy? strategy, { - required bool useSingle, - }) async { - await _browserHistory?.tearDown(); - if (useSingle) { - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } else { - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - } - } - - @visibleForTesting - Future debugResetHistory() async { - await _browserHistory?.tearDown(); - _browserHistory = null; - } - - Future _handleNavigationMessage( - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) async { - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map arguments = decoded.arguments; +typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); - switch (decoded.method) { - case 'routeUpdated': - await _useSingleEntryBrowserHistory(); - browserHistory.setRouteName(arguments['routeName']); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - case 'routeInformationUpdated': - assert(browserHistory is MultiEntriesBrowserHistory); - browserHistory.setRouteName( - arguments['location'], - state: arguments['state'], - ); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return true; - } - return false; - } +/// A JavaScript hook to customize the URL strategy of a Flutter app. +// +// Keep this js name in sync with flutter_web_plugins. Find it at: +// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +// +// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 +@JS('_flutter_web_set_location_strategy') +external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); - int _getHapticFeedbackDuration(String? type) { - switch (type) { - case 'HapticFeedbackType.lightImpact': - return DomRenderer.vibrateLightImpact; - case 'HapticFeedbackType.mediumImpact': - return DomRenderer.vibrateMediumImpact; - case 'HapticFeedbackType.heavyImpact': - return DomRenderer.vibrateHeavyImpact; - case 'HapticFeedbackType.selectionClick': - return DomRenderer.vibrateSelectionClick; - default: - return DomRenderer.vibrateLongPress; - } - } +UrlStrategy? _createDefaultUrlStrategy() { + return ui.debugEmulateFlutterTesterEnvironment + ? null + : const HashUrlStrategy(); +} - /// In Flutter, platform messages are exchanged between threads so the - /// messages and responses have to be exchanged asynchronously. We simulate - /// that by adding a zero-length delay to the reply. - void _replyToPlatformMessage( - ui.PlatformMessageResponseCallback? callback, - ByteData? data, - ) { - Future.delayed(Duration.zero).then((_) { - if (callback != null) { - callback(data); - } - }); - } +/// The Web implementation of [ui.Window]. +class EngineSingletonFlutterWindow extends EngineFlutterWindow { + EngineSingletonFlutterWindow(Object windowId, ui.PlatformDispatcher platformDispatcher) : super(windowId, platformDispatcher); @override - ui.Brightness get platformBrightness => _platformBrightness; - ui.Brightness _platformBrightness = ui.Brightness.light; - - /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] - /// callback if [_platformBrightness] changed. - void _updatePlatformBrightness(ui.Brightness newPlatformBrightness) { - ui.Brightness previousPlatformBrightness = _platformBrightness; - _platformBrightness = newPlatformBrightness; - - if (previousPlatformBrightness != _platformBrightness && - onPlatformBrightnessChanged != null) { - invokeOnPlatformBrightnessChanged(); - } - } + double get devicePixelRatio => _debugDevicePixelRatio ?? EnginePlatformDispatcher.browserDevicePixelRatio; - /// Reference to css media query that indicates the user theme preference on the web. - final html.MediaQueryList _brightnessMediaQuery = - html.window.matchMedia('(prefers-color-scheme: dark)'); - - /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. + /// Overrides the default device pixel ratio. /// - /// Updates the [_platformBrightness] with the new user preference. - html.EventListener? _brightnessMediaQueryListener; - - /// Set the callback function for listening changes in [_brightnessMediaQuery] value. - void _addBrightnessMediaQueryListener() { - _updatePlatformBrightness(_brightnessMediaQuery.matches - ? ui.Brightness.dark - : ui.Brightness.light); - - _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = - event as html.MediaQueryListEvent; - _updatePlatformBrightness( - mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); - }; - _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); - registerHotRestartListener(() { - _removeBrightnessMediaQueryListener(); - }); - } - - void _addUrlStrategyListener() { - _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { - assert( - _browserHistory == null, - 'Cannot set URL strategy more than once.', - ); - final UrlStrategy? strategy = - jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - }); - registerHotRestartListener(() { - _jsSetUrlStrategy = null; - }); - } - - /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. - void _removeBrightnessMediaQueryListener() { - _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); - _brightnessMediaQueryListener = null; - } - - @override - void render(ui.Scene scene) { - if (experimentalUseSkia) { - // "Build finish" and "raster start" happen back-to-back because we - // render on the same thread, so there's no overhead from hopping to - // another thread. - // - // CanvasKit works differently from the HTML renderer in that in HTML - // we update the DOM in SceneBuilder.build, which is these function calls - // here are CanvasKit-only. - _frameTimingsOnBuildFinish(); - _frameTimingsOnRasterStart(); - - final LayerScene layerScene = scene as LayerScene; - rasterizer!.draw(layerScene.layerTree); - } else { - final SurfaceScene surfaceScene = scene as SurfaceScene; - domRenderer.renderScene(surfaceScene.webOnlyRootElement); - } - _frameTimingsOnRasterFinish(); - } - - @visibleForTesting - late Rasterizer? rasterizer = - experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; -} - -bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { - final MethodCall decoded = codec.decodeMethodCall(data); - double ratio = double.parse(decoded.arguments); - switch (decoded.method) { - case 'setDevicePixelRatio': - window.debugOverrideDevicePixelRatio(ratio); - window.onMetricsChanged!(); - return true; - } - return false; -} - -/// Invokes [callback] inside the given [zone]. -void _invoke(void callback()?, Zone? zone) { - if (callback == null) { - return; + /// This is useful in tests to emulate screens of different dimensions. + void debugOverrideDevicePixelRatio(double value) { + _debugDevicePixelRatio = value; } - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(); - } else { - zone!.runGuarded(callback); - } + double? _debugDevicePixelRatio; } -/// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1(void callback(A a)?, Zone? zone, A arg) { - if (callback == null) { - return; - } - - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(arg); - } else { - zone!.runUnaryGuarded(callback, arg); - } -} +/// A type of [FlutterView] that can be hosted inside of a [FlutterWindow]. +class EngineFlutterWindowView extends ui.FlutterWindow { + EngineFlutterWindowView._(this._viewId, this.platformDispatcher); -/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, - A1 arg1, A2 arg2, A3 arg3) { - if (callback == null) { - return; - } + final Object _viewId; - assert(zone != null); + final ui.PlatformDispatcher platformDispatcher; - if (identical(zone, Zone.current)) { - callback(arg1, arg2, arg3); - } else { - zone!.runGuarded(() { - callback(arg1, arg2, arg3); - }); + @override + ui.ViewConfiguration get viewConfiguration { + final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; + assert(engineDispatcher._windowConfigurations.containsKey(_viewId)); + return engineDispatcher._windowConfigurations[_viewId] ?? ui.ViewConfiguration(); } } @@ -879,7 +268,7 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, /// `dart:ui` window delegates to this value. However, this value has a wider /// API surface, providing Web-specific functionality that the standard /// `dart:ui` version does not. -final EngineWindow window = EngineWindow(); +final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); /// The Web implementation of [ui.WindowPadding]. class WindowPadding implements ui.WindowPadding { diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart new file mode 100644 index 0000000000000..1ec3fdb09da82 --- /dev/null +++ b/lib/web_ui/lib/src/ui/platform_dispatcher.dart @@ -0,0 +1,420 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.10 +part of ui; + +typedef VoidCallback = void Function(); +typedef FrameCallback = void Function(Duration duration); +typedef TimingsCallback = void Function(List timings); +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); +typedef PlatformMessageResponseCallback = void Function(ByteData? data); +typedef PlatformMessageCallback = void Function( + String name, ByteData? data, PlatformMessageResponseCallback? callback); +typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); + +abstract class PlatformDispatcher { + static PlatformDispatcher get instance => engine.EnginePlatformDispatcher.instance; + + PlatformConfiguration get configuration; + VoidCallback? get onPlatformConfigurationChanged; + set onPlatformConfigurationChanged(VoidCallback? callback); + + Iterable get views; + + VoidCallback? get onMetricsChanged; + set onMetricsChanged(VoidCallback? callback); + + FrameCallback? get onBeginFrame; + set onBeginFrame(FrameCallback? callback); + + VoidCallback? get onDrawFrame; + set onDrawFrame(VoidCallback? callback); + + PointerDataPacketCallback? get onPointerDataPacket; + set onPointerDataPacket(PointerDataPacketCallback? callback); + + TimingsCallback? get onReportTimings; + set onReportTimings(TimingsCallback? callback); + + void sendPlatformMessage( + String name, + ByteData? data, + PlatformMessageResponseCallback? callback, + ); + + PlatformMessageCallback? get onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback); + + void setIsolateDebugName(String name) {} + + ByteData? getPersistentIsolateData() => null; + + void scheduleFrame(); + + void render(Scene scene, [FlutterView view]); + + AccessibilityFeatures get accessibilityFeatures; + + VoidCallback? get onAccessibilityFeaturesChanged; + set onAccessibilityFeaturesChanged(VoidCallback? callback); + + void updateSemantics(SemanticsUpdate update); + + Locale get locale; + + List get locales => configuration.locales; + + Locale? computePlatformResolvedLocale(List supportedLocales); + + VoidCallback? get onLocaleChanged; + set onLocaleChanged(VoidCallback? callback); + + String get initialLifecycleState => 'AppLifecycleState.resumed'; + + bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; + + double get textScaleFactor => configuration.textScaleFactor; + + VoidCallback? get onTextScaleFactorChanged; + set onTextScaleFactorChanged(VoidCallback? callback); + + Brightness get platformBrightness => configuration.platformBrightness; + + VoidCallback? get onPlatformBrightnessChanged; + set onPlatformBrightnessChanged(VoidCallback? callback); + + bool get semanticsEnabled => configuration.semanticsEnabled; + + VoidCallback? get onSemanticsEnabledChanged; + set onSemanticsEnabledChanged(VoidCallback? callback); + + SemanticsActionCallback? get onSemanticsAction; + set onSemanticsAction(SemanticsActionCallback? callback); + + String get defaultRouteName; +} + +class PlatformConfiguration { + const PlatformConfiguration({ + this.accessibilityFeatures = const AccessibilityFeatures._(0), + this.alwaysUse24HourFormat = false, + this.semanticsEnabled = false, + this.platformBrightness = Brightness.light, + this.textScaleFactor = 1.0, + this.locales = const [], + this.defaultRouteName = '/', + }); + + PlatformConfiguration copyWith({ + AccessibilityFeatures? accessibilityFeatures, + bool? alwaysUse24HourFormat, + bool? semanticsEnabled, + Brightness? platformBrightness, + double? textScaleFactor, + List? locales, + String? defaultRouteName, + }) { + return PlatformConfiguration( + accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, + alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, + semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, + platformBrightness: platformBrightness ?? this.platformBrightness, + textScaleFactor: textScaleFactor ?? this.textScaleFactor, + locales: locales ?? this.locales, + defaultRouteName: defaultRouteName ?? this.defaultRouteName, + ); + } + + final AccessibilityFeatures accessibilityFeatures; + final bool alwaysUse24HourFormat; + final bool semanticsEnabled; + final Brightness platformBrightness; + final double textScaleFactor; + final List locales; + final String defaultRouteName; +} + +class ViewConfiguration { + const ViewConfiguration({ + this.window, + this.devicePixelRatio = 1.0, + this.geometry = Rect.zero, + this.visible = false, + this.viewInsets = WindowPadding.zero, + this.viewPadding = WindowPadding.zero, + this.systemGestureInsets = WindowPadding.zero, + this.padding = WindowPadding.zero, + }); + + ViewConfiguration copyWith({ + FlutterWindow? window, + double? devicePixelRatio, + Rect? geometry, + bool? visible, + WindowPadding? viewInsets, + WindowPadding? viewPadding, + WindowPadding? systemGestureInsets, + WindowPadding? padding, + }) { + return ViewConfiguration( + window: window ?? this.window, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, + geometry: geometry ?? this.geometry, + visible: visible ?? this.visible, + viewInsets: viewInsets ?? this.viewInsets, + viewPadding: viewPadding ?? this.viewPadding, + systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, + padding: padding ?? this.padding, + ); + } + + final FlutterWindow? window; + final double devicePixelRatio; + final Rect geometry; + final bool visible; + final WindowPadding viewInsets; + final WindowPadding viewPadding; + final WindowPadding systemGestureInsets; + final WindowPadding padding; + + @override + String toString() { + return '$runtimeType[window: $window, geometry: $geometry]'; + } +} + +enum FramePhase { + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish, +} + +class FrameTiming { + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + + FrameTiming._(this._timestamps) + : assert(_timestamps.length == FramePhase.values.length); + + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + + Duration get buildDuration => + _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + + Duration get rasterDuration => + _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + + Duration get totalSpan => + _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + +enum AppLifecycleState { + resumed, + inactive, + paused, + detached, +} + +abstract class WindowPadding { + const factory WindowPadding._( + {required double left, + required double top, + required double right, + required double bottom}) = engine.WindowPadding; + + double get left; + double get top; + double get right; + double get bottom; + + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); + + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +class Locale { + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + + final String? scriptCode; + + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + return other is Locale + && other.languageCode == languageCode + && other.scriptCode == scriptCode + && other.countryCode == countryCode; + } + + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode); + + @override + String toString() => _rawToString('_'); + + // TODO(yjbanov): implement to match flutter native. + String toLanguageTag() => _rawToString('-'); + + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null) { + out.write('$separator$scriptCode'); + } + if (_countryCode != null) { + out.write('$separator$countryCode'); + } + return out.toString(); + } +} \ No newline at end of file diff --git a/lib/web_ui/lib/src/ui/text.dart b/lib/web_ui/lib/src/ui/text.dart index abf00f154674b..eaa9f449db7a4 100644 --- a/lib/web_ui/lib/src/ui/text.dart +++ b/lib/web_ui/lib/src/ui/text.dart @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Synced 2019-05-30T14:20:57.833907. // @dart = 2.10 part of ui; diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index fa58ff01e5537..cc7a45dcc65db 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -1,264 +1,131 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Synced 2019-05-30T14:20:57.841444. // @dart = 2.10 part of ui; -typedef VoidCallback = void Function(); -typedef FrameCallback = void Function(Duration duration); -typedef TimingsCallback = void Function(List timings); -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); -typedef PlatformMessageResponseCallback = void Function(ByteData? data); -typedef PlatformMessageCallback = void Function( - String name, ByteData? data, PlatformMessageResponseCallback? callback); - -enum AppLifecycleState { - resumed, - inactive, - paused, - detached, +abstract class FlutterView { + PlatformDispatcher get platformDispatcher; + ViewConfiguration get viewConfiguration; + double get devicePixelRatio => viewConfiguration.devicePixelRatio; + Rect get physicalGeometry => viewConfiguration.geometry; + Size get physicalSize => viewConfiguration.geometry.size; + WindowPadding get viewInsets => viewConfiguration.viewInsets; + WindowPadding get viewPadding => viewConfiguration.viewPadding; + WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; + WindowPadding get padding => viewConfiguration.padding; + void render(Scene scene) => platformDispatcher.render(scene, this); } -abstract class WindowPadding { - const factory WindowPadding._({ - required double left, - required double top, - required double right, - required double bottom, - }) = engine.WindowPadding; - - double get left; - double get top; - double get right; - double get bottom; - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); +abstract class FlutterWindow extends FlutterView { + @override + PlatformDispatcher get platformDispatcher; @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } + ViewConfiguration get viewConfiguration; } -class Locale { - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - final String? scriptCode; - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; +abstract class SingletonFlutterWindow extends FlutterWindow { + VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; + set onMetricsChanged(VoidCallback? callback) { + platformDispatcher.onMetricsChanged = callback; + } - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is Locale - && other.languageCode == languageCode - && other.scriptCode == scriptCode - && other.countryCode == countryCode; + Locale? get locale => platformDispatcher.locale; + List? get locales => platformDispatcher.locales; + + Locale? computePlatformResolvedLocale(List supportedLocales) { + return platformDispatcher.computePlatformResolvedLocale(supportedLocales); } - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode); + VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; + set onLocaleChanged(VoidCallback? callback) { + platformDispatcher.onLocaleChanged = callback; + } - @override - String toString() => _rawToString('_'); + String get initialLifecycleState => platformDispatcher.initialLifecycleState; - // TODO(yjbanov): implement to match flutter native. - String toLanguageTag() => _rawToString('-'); + double get textScaleFactor => platformDispatcher.textScaleFactor; + bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null) { - out.write('$separator$scriptCode'); - } - if (_countryCode != null) { - out.write('$separator$countryCode'); - } - return out.toString(); + VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; + set onTextScaleFactorChanged(VoidCallback? callback) { + platformDispatcher.onTextScaleFactorChanged = callback; } -} -abstract class Window { - double get devicePixelRatio; - Size get physicalSize; - WindowPadding get viewInsets => WindowPadding.zero; - - WindowPadding get viewPadding => WindowPadding.zero; - - WindowPadding get systemGestureInsets => WindowPadding.zero; - WindowPadding get padding => WindowPadding.zero; - double get textScaleFactor => _textScaleFactor; - double _textScaleFactor = 1.0; - bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; - bool _alwaysUse24HourFormat = false; - VoidCallback? get onTextScaleFactorChanged; - set onTextScaleFactorChanged(VoidCallback? callback); - Brightness get platformBrightness; - VoidCallback? get onPlatformBrightnessChanged; - set onPlatformBrightnessChanged(VoidCallback? callback); - VoidCallback? get onMetricsChanged; - set onMetricsChanged(VoidCallback? callback); - Locale? get locale; - List? get locales; - Locale? computePlatformResolvedLocale(List supportedLocales) { - // TODO(garyq): Implement on web. - return null; + Brightness get platformBrightness => platformDispatcher.platformBrightness; + + VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; + set onPlatformBrightnessChanged(VoidCallback? callback) { + platformDispatcher.onPlatformBrightnessChanged = callback; } - VoidCallback? get onLocaleChanged; - set onLocaleChanged(VoidCallback? callback); - void scheduleFrame(); - FrameCallback? get onBeginFrame; - set onBeginFrame(FrameCallback? callback); - TimingsCallback? get onReportTimings; - set onReportTimings(TimingsCallback? callback); - VoidCallback? get onDrawFrame; - set onDrawFrame(VoidCallback? callback); - PointerDataPacketCallback? get onPointerDataPacket; - set onPointerDataPacket(PointerDataPacketCallback? callback); - String get defaultRouteName; - bool get semanticsEnabled => engine.EngineSemanticsOwner.instance.semanticsEnabled; - VoidCallback? get onSemanticsEnabledChanged; - set onSemanticsEnabledChanged(VoidCallback? callback); - SemanticsActionCallback? get onSemanticsAction; - set onSemanticsAction(SemanticsActionCallback? callback); - VoidCallback? get onAccessibilityFeaturesChanged; - set onAccessibilityFeaturesChanged(VoidCallback? callback); - PlatformMessageCallback? get onPlatformMessage; - set onPlatformMessage(PlatformMessageCallback? callback); - void updateSemantics(SemanticsUpdate update) { - engine.EngineSemanticsOwner.instance.updateSemantics(update); + FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; + set onBeginFrame(FrameCallback? callback) { + platformDispatcher.onBeginFrame = callback; } + VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; + set onDrawFrame(VoidCallback? callback) { + platformDispatcher.onDrawFrame = callback; + } + + TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; + set onReportTimings(TimingsCallback? callback) { + platformDispatcher.onReportTimings = callback; + } + + PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; + set onPointerDataPacket(PointerDataPacketCallback? callback) { + platformDispatcher.onPointerDataPacket = callback; + } + + String get defaultRouteName => platformDispatcher.defaultRouteName; + + void scheduleFrame() => platformDispatcher.scheduleFrame(); + void render(Scene scene) => platformDispatcher.render(scene, this); + + bool get semanticsEnabled => platformDispatcher.semanticsEnabled; + + VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; + set onSemanticsEnabledChanged(VoidCallback? callback) { + platformDispatcher.onSemanticsEnabledChanged = callback; + } + + SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; + set onSemanticsAction(SemanticsActionCallback? callback) { + platformDispatcher.onSemanticsAction = callback; + } + + AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; + + VoidCallback? get onAccessibilityFeaturesChanged => + platformDispatcher.onAccessibilityFeaturesChanged; + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + platformDispatcher.onAccessibilityFeaturesChanged = callback; + } + + void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); + void sendPlatformMessage( String name, ByteData? data, PlatformMessageResponseCallback? callback, - ); - AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; - AccessibilityFeatures _accessibilityFeatures = AccessibilityFeatures._(0); - void render(Scene scene); - - String get initialLifecycleState => 'AppLifecycleState.resumed'; + ) { + platformDispatcher.sendPlatformMessage(name, data, callback); + } - void setIsolateDebugName(String name) {} + PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback) { + platformDispatcher.onPlatformMessage = callback; + } - ByteData? getPersistentIsolateData() => null; + void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } +abstract class Window extends SingletonFlutterWindow {} + class AccessibilityFeatures { const AccessibilityFeatures._(this._index); @@ -271,6 +138,7 @@ class AccessibilityFeatures { // A bitfield which represents each enabled feature. final int _index; + bool get accessibleNavigation => _kAccessibleNavigation & _index != 0; bool get invertColors => _kInvertColorsIndex & _index != 0; bool get disableAnimations => _kDisableAnimationsIndex & _index != 0; @@ -352,7 +220,6 @@ class PluginUtilities { } } -// TODO(flutter_web): probably dont implement this one. class IsolateNameServer { // This class is only a namespace, and should not be instantiated or // extended directly. @@ -371,50 +238,4 @@ class IsolateNameServer { } } -enum FramePhase { - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish, -} - -class FrameTiming { - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - FrameTiming._(List timestamps) - : assert(timestamps.length == FramePhase.values.length), - _timestamps = timestamps; - - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - Window get window => engine.window; diff --git a/lib/web_ui/lib/ui.dart b/lib/web_ui/lib/ui.dart index 13d5ee2813b20..b616c223212d5 100644 --- a/lib/web_ui/lib/ui.dart +++ b/lib/web_ui/lib/ui.dart @@ -29,6 +29,7 @@ part 'src/ui/natives.dart'; part 'src/ui/painting.dart'; part 'src/ui/path.dart'; part 'src/ui/path_metrics.dart'; +part 'src/ui/platform_dispatcher.dart'; part 'src/ui/pointer.dart'; part 'src/ui/semantics.dart'; part 'src/ui/test_embedding.dart'; diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index a47246c24894b..6bd2321768ae4 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -50,7 +50,7 @@ void _tests() { when(mockRasterizer.addPostFrameCallback(any)).thenAnswer((_) { addPostFrameCallbackCount++; }); - window.rasterizer = mockRasterizer; + EnginePlatformDispatcher.instance.rasterizer = mockRasterizer; // Trigger first create final TestSkiaObject testObject = TestSkiaObject(); diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index f5be42ae61db7..ff2f7674d4a88 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -292,7 +292,7 @@ void testMain() { expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); expect(strategy.currentEntry.url, '/home'); - await routeInfomrationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page1', 'page1 state'); // Should have two history entries now. expect(strategy.history, hasLength(2)); expect(strategy.currentEntryIndex, 1); @@ -329,8 +329,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInfomrationUpdated('/page1', 'page1 state'); - await routeInfomrationUpdated('/page2', 'page2 state'); + await routeInformationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -426,8 +426,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInfomrationUpdated('/page1', 'page1 state'); - await routeInfomrationUpdated('/page2', 'page2 state'); + await routeInformationUpdated('/page1', 'page1 state'); + await routeInformationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -522,7 +522,7 @@ Future routeUpdated(String routeName) { return completer.future; } -Future routeInfomrationUpdated(String location, dynamic state) { +Future routeInformationUpdated(String location, dynamic state) { final Completer completer = Completer(); window.sendPlatformMessage( 'flutter/navigation', diff --git a/lib/web_ui/test/engine/surface/platform_view_test.dart b/lib/web_ui/test/engine/surface/platform_view_test.dart index eeda48709da7e..c806d67658a78 100644 --- a/lib/web_ui/test/engine/surface/platform_view_test.dart +++ b/lib/web_ui/test/engine/surface/platform_view_test.dart @@ -15,7 +15,7 @@ import 'package:test/test.dart'; import '../../matchers.dart'; const MethodCodec codec = StandardMethodCodec(); -final EngineWindow window = EngineWindow(); +final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 15e381440125a..e61c9f1e42289 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -31,7 +31,7 @@ void testMain() { expect(window.onTextScaleFactorChanged, same(callback)); }); - window.invokeOnTextScaleFactorChanged(); + EnginePlatformDispatcher.instance.invokeOnTextScaleFactorChanged(); }); test('onPlatformBrightnessChanged preserves the zone', () { @@ -47,7 +47,7 @@ void testMain() { expect(window.onPlatformBrightnessChanged, same(callback)); }); - window.invokeOnPlatformBrightnessChanged(); + EnginePlatformDispatcher.instance.invokeOnPlatformBrightnessChanged(); }); test('onMetricsChanged preserves the zone', () { @@ -63,7 +63,7 @@ void testMain() { expect(window.onMetricsChanged, same(callback)); }); - window.invokeOnMetricsChanged(); + EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); }); test('onLocaleChanged preserves the zone', () { @@ -79,7 +79,7 @@ void testMain() { expect(window.onLocaleChanged, same(callback)); }); - window.invokeOnLocaleChanged(); + EnginePlatformDispatcher.instance.invokeOnLocaleChanged(); }); test('onBeginFrame preserves the zone', () { @@ -95,7 +95,7 @@ void testMain() { expect(window.onBeginFrame, same(callback)); }); - window.invokeOnBeginFrame(null); + EnginePlatformDispatcher.instance.invokeOnBeginFrame(null); }); test('onReportTimings preserves the zone', () { @@ -111,7 +111,7 @@ void testMain() { expect(window.onReportTimings, same(callback)); }); - window.invokeOnReportTimings(null); + EnginePlatformDispatcher.instance.invokeOnReportTimings(null); }); test('onDrawFrame preserves the zone', () { @@ -127,7 +127,7 @@ void testMain() { expect(window.onDrawFrame, same(callback)); }); - window.invokeOnDrawFrame(); + EnginePlatformDispatcher.instance.invokeOnDrawFrame(); }); test('onPointerDataPacket preserves the zone', () { @@ -143,7 +143,7 @@ void testMain() { expect(window.onPointerDataPacket, same(callback)); }); - window.invokeOnPointerDataPacket(null); + EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(null); }); test('onSemanticsEnabledChanged preserves the zone', () { @@ -159,7 +159,7 @@ void testMain() { expect(window.onSemanticsEnabledChanged, same(callback)); }); - window.invokeOnSemanticsEnabledChanged(); + EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); }); test('onSemanticsAction preserves the zone', () { @@ -175,7 +175,7 @@ void testMain() { expect(window.onSemanticsAction, same(callback)); }); - window.invokeOnSemanticsAction(null, null, null); + EnginePlatformDispatcher.instance.invokeOnSemanticsAction(null, null, null); }); test('onAccessibilityFeaturesChanged preserves the zone', () { @@ -191,7 +191,7 @@ void testMain() { expect(window.onAccessibilityFeaturesChanged, same(callback)); }); - window.invokeOnAccessibilityFeaturesChanged(); + EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged(); }); test('onPlatformMessage preserves the zone', () { @@ -207,7 +207,7 @@ void testMain() { expect(window.onPlatformMessage, same(callback)); }); - window.invokeOnPlatformMessage(null, null, null); + EnginePlatformDispatcher.instance.invokeOnPlatformMessage(null, null, null); }); test('sendPlatformMessage preserves the zone', () async { @@ -290,8 +290,8 @@ void testMain() { // Trigger a change notification (reset locales because the notification // doesn't actually change the list of languages; the test only observes // that the list is populated again). - window.debugResetLocales(); - expect(window.locales, null); + EnginePlatformDispatcher.instance.debugResetLocales(); + expect(window.locales, isEmpty); expect(localeChangedCount, 0); html.window.dispatchEvent(html.Event('languagechange')); expect(window.locales, isNotEmpty); diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index a072d025974fd..a9dca67448268 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -227,8 +227,8 @@ void testMain() async { ); final SceneBuilder sb = SceneBuilder(); - sb.pushTransform(Matrix4.diagonal3Values(EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); + sb.pushTransform(Matrix4.diagonal3Values(EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); sb.pushTransform(Matrix4.rotationZ(math.pi / 2).toFloat64()); sb.pushOffset(0, -500); sb.pushClipRect(canvasSize); diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 04dce71d0b986..d9f706f3ff9e5 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -412,8 +412,8 @@ void _testCullRectComputation() { final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushTransform(Matrix4.diagonal3Values( - EngineWindow.browserDevicePixelRatio, - EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); + EnginePlatformDispatcher.browserDevicePixelRatio, + EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); // TODO(yjbanov): see the TODO below. // final double screenWidth = html.window.innerWidth.toDouble(); diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index ef0a755f550cf..3d164d1800401 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -71,7 +71,7 @@ void testMain() { expect(window.browserHistory.currentPath, '/'); // Perform some navigation operations. - routeInfomrationUpdated('/foo/bar', null); + routeInformationUpdated('/foo/bar', null); // Path should not be updated because URL strategy is disabled. expect(window.browserHistory.currentPath, '/'); }); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 7761bee4ea7ea..e2b26c2f2f8ab 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -153,7 +153,7 @@ bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) { platform_data_.viewport_metrics = metrics; if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - platform_configuration->window()->UpdateWindowMetrics(metrics); + platform_configuration->get_window(0)->UpdateWindowMetrics(metrics); return true; } @@ -275,7 +275,7 @@ bool RuntimeController::DispatchPointerDataPacket( if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { TRACE_EVENT1("flutter", "RuntimeController::DispatchPointerDataPacket", "mode", "basic"); - platform_configuration->window()->DispatchPointerDataPacket(packet); + platform_configuration->get_window(0)->DispatchPointerDataPacket(packet); return true; } diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 4767d01fbcb13..89ed112055b74 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -146,7 +146,7 @@ class RuntimeController : public PlatformConfigurationClient { /// If the isolate is not running, these metrics will be saved and /// flushed to the isolate when it starts. /// - /// @param[in] metrics The viewport metrics. + /// @param[in] metrics The window's viewport metrics. /// /// @return If the window metrics were forwarded to the running isolate. /// diff --git a/shell/common/engine.h b/shell/common/engine.h index c5bbfc42c9c74..81771c00935ab 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -193,13 +193,14 @@ class Engine final : public RuntimeDelegate, /// @brief Notifies the shell of the name of the root isolate and its /// port when that isolate is launched, restarted (in the /// cold-restart scenario) or the application itself updates the - /// name of the root isolate (via `Window.setIsolateDebugName` - /// in `window.dart`). The name of the isolate is meaningless to - /// the engine but is used in instrumentation and tooling. - /// Currently, this information is to update the service - /// protocol list of available root isolates running in the VM - /// and their names so that the appropriate isolate can be - /// selected in the tools for debugging and instrumentation. + /// name of the root isolate (via + /// `PlatformDispatcher.setIsolateDebugName` in + /// `platform_dispatcher.dart`). The name of the isolate is + /// meaningless to the engine but is used in instrumentation and + /// tooling. Currently, this information is to update the + /// service protocol list of available root isolates running in + /// the VM and their names so that the appropriate isolate can + /// be selected in the tools for debugging and instrumentation. /// /// @param[in] isolate_name The isolate name /// @param[in] isolate_port The isolate port @@ -543,8 +544,8 @@ class Engine final : public RuntimeDelegate, /// "main.dart", the entrypoint is "main" and the port name /// "1234". Once launched, the isolate may re-christen itself /// using a name it selects via `setIsolateDebugName` in - /// `window.dart`. This name is purely advisory and only used by - /// instrumentation and reporting purposes. + /// `platform_dispatcher.dart`. This name is purely advisory and + /// only used by instrumentation and reporting purposes. /// /// @return The debug name of the root isolate. /// diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 492ddb8960d03..9c72ea87e0e42 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -15,7 +15,7 @@ void nativeOnPointerDataPacket(List sequences) native 'NativeOnPointerDataP @pragma('vm:entry-point') void reportTimingsMain() { - window.onReportTimings = (List timings) { + PlatformDispatcher.instance.onReportTimings = (List timings) { List timestamps = []; for (FrameTiming t in timings) { for (FramePhase phase in FramePhase.values) { @@ -28,15 +28,15 @@ void reportTimingsMain() { @pragma('vm:entry-point') void onBeginFrameMain() { - window.onBeginFrame = (Duration beginTime) { + PlatformDispatcher.instance.onBeginFrame = (Duration beginTime) { nativeOnBeginFrame(beginTime.inMicroseconds); }; } @pragma('vm:entry-point') void onPointerDataPacketMain() { - window.onPointerDataPacket = (PointerDataPacket packet) { - List sequence= []; + PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket packet) { + List sequence = []; for (PointerData data in packet.data) { sequence.add(PointerChange.values.indexOf(data.change)); } @@ -62,7 +62,7 @@ void _reportMetrics(double devicePixelRatio, double width, double height) native @pragma('vm:entry-point') void dummyReportTimingsMain() { - window.onReportTimings = (List timings) {}; + PlatformDispatcher.instance.onReportTimings = (List timings) {}; } @pragma('vm:entry-point') @@ -102,7 +102,7 @@ void testSkiaResourceCacheSendsResponse() { "method": "Skia.setResourceCacheMaxBytes", "args": 10000 }'''; - window.sendPlatformMessage( + PlatformDispatcher.instance.sendPlatformMessage( 'flutter/skia', Uint8List.fromList(utf8.encode(jsonRequest)).buffer.asByteData(), callback, @@ -120,17 +120,24 @@ void canCreateImageFromDecompressedData() { (int i) => i % 4 < 2 ? 0x00 : 0xFF, )); - decodeImageFromPixels( - pixels, imageWidth, imageHeight, PixelFormat.rgba8888, - (Image image) { - notifyWidthHeight(image.width, image.height); - }); + pixels, + imageWidth, + imageHeight, + PixelFormat.rgba8888, + (Image image) { + notifyWidthHeight(image.width, image.height); + }, + ); } @pragma('vm:entry-point') void canAccessIsolateLaunchData() { - notifyMessage(utf8.decode(window.getPersistentIsolateData().buffer.asUint8List())); + notifyMessage( + utf8.decode( + PlatformDispatcher.instance.getPersistentIsolateData().buffer.asUint8List(), + ), + ); } void notifyMessage(String string) native 'NotifyMessage'; @@ -145,9 +152,12 @@ void sendFixtureMapping(List list) native 'SendFixtureMapping'; @pragma('vm:entry-point') void canDecompressImageFromAsset() { - decodeImageFromList(Uint8List.fromList(getFixtureImage()), (Image result) { - notifyWidthHeight(result.width, result.height); - }); + decodeImageFromList( + Uint8List.fromList(getFixtureImage()), + (Image result) { + notifyWidthHeight(result.width, result.height); + }, + ); } List getFixtureImage() native 'GetFixtureImage'; diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 17d0eaf6e50ae..aadfd6db0e683 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -5,7 +5,6 @@ #define FML_USED_ON_EMBEDDER #include "flutter/shell/common/shell_test.h" -#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/transform_layer.h" @@ -13,6 +12,7 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/shell/common/vsync_waiter_fallback.h" #include "flutter/testing/testing.h" diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index fbf0647eb1191..904d8611a4471 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1327,7 +1327,7 @@ TEST_F(ShellTest, WaitForFirstFrameInlined) { DestroyShell(std::move(shell), std::move(task_runners)); } -static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) { +static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) { size_t bytes = 0; fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 48d9243cf86c2..8c25da13d9e2d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1168,7 +1168,6 @@ public void removeFlutterEngineAttachmentListener( .send(); } - // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI private void sendViewportMetricsToFlutter() { if (!isAttachedToFlutterEngine()) { Log.w( diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 3b8a61bf39c0c..d653a9eccb79d 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -6,7 +6,6 @@ #include #include - #include #include "unicode/uchar.h" @@ -36,8 +35,9 @@ namespace flutter { namespace { bool CheckException(JNIEnv* env) { - if (env->ExceptionCheck() == JNI_FALSE) + if (env->ExceptionCheck() == JNI_FALSE) { return true; + } jthrowable exception = env->ExceptionOccurred(); env->ExceptionClear(); @@ -1174,10 +1174,10 @@ void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView( } case clip_rect: { const SkRect& rect = (*iter)->GetRect(); - env->CallVoidMethod(mutatorsStack, - g_mutators_stack_push_cliprect_method, - (int)rect.left(), (int)rect.top(), - (int)rect.right(), (int)rect.bottom()); + env->CallVoidMethod( + mutatorsStack, g_mutators_stack_push_cliprect_method, + static_cast(rect.left()), static_cast(rect.top()), + static_cast(rect.right()), static_cast(rect.bottom())); break; } // TODO(cyanglaz): Implement other mutators. @@ -1301,16 +1301,16 @@ PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale( } fml::jni::ScopedJavaLocalRef j_locales_data = fml::jni::VectorToStringArray(env, supported_locales_data); - jobjectArray result = (jobjectArray)env->CallObjectMethod( + jobjectArray result = static_cast(env->CallObjectMethod( java_object.obj(), g_compute_platform_resolved_locale_method, - j_locales_data.obj()); + j_locales_data.obj())); FML_CHECK(CheckException(env)); int length = env->GetArrayLength(result); for (int i = 0; i < length; i++) { out->emplace_back(fml::jni::JavaStringToString( - env, (jstring)env->GetObjectArrayElement(result, i))); + env, static_cast(env->GetObjectArrayElement(result, i)))); } return out; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ed7e1d79fd56d..99c8f75c419e4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -923,18 +923,18 @@ - (CGFloat)statusBarPadding { } - (void)viewDidLayoutSubviews { - CGSize viewSize = self.view.bounds.size; + CGRect viewBounds = self.view.bounds; CGFloat scale = [UIScreen mainScreen].scale; // Purposefully place this not visible. - _scrollView.get().frame = CGRectMake(0.0, 0.0, viewSize.width, 0.0); + _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0); _scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); // First time since creation that the dimensions of its view is known. bool firstViewBoundsUpdate = !_viewportMetrics.physical_width; _viewportMetrics.device_pixel_ratio = scale; - _viewportMetrics.physical_width = viewSize.width * scale; - _viewportMetrics.physical_height = viewSize.height * scale; + _viewportMetrics.physical_width = viewBounds.size.width * scale; + _viewportMetrics.physical_height = viewBounds.size.height * scale; [self updateViewportPadding]; [self updateViewportMetrics]; @@ -1105,14 +1105,18 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification { } auto platformView = [_engine.get() platformView]; int32_t flags = 0; - if (UIAccessibilityIsInvertColorsEnabled()) + if (UIAccessibilityIsInvertColorsEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kInvertColors); - if (UIAccessibilityIsReduceMotionEnabled()) + } + if (UIAccessibilityIsReduceMotionEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kReduceMotion); - if (UIAccessibilityIsBoldTextEnabled()) + } + if (UIAccessibilityIsBoldTextEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); - if (UIAccessibilityDarkerSystemColorsEnabled()) + } + if (UIAccessibilityDarkerSystemColorsEnabled()) { flags |= static_cast(flutter::AccessibilityFeatureFlag::kHighContrast); + } #if TARGET_OS_SIMULATOR // There doesn't appear to be any way to determine whether the accessibility // inspector is enabled on the simulator. We conservatively always turn on the diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index b5356181e3bce..997c42b2e8f19 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -411,16 +411,19 @@ - (void)updateWindowMetrics { return; } NSView* view = _viewController.view; - CGSize scaledSize = [view convertRectToBacking:view.bounds].size; + CGRect scaledBounds = [view convertRectToBacking:view.bounds]; + CGSize scaledSize = scaledBounds.size; double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; - const FlutterWindowMetricsEvent event = { - .struct_size = sizeof(event), + const FlutterWindowMetricsEvent windowMetricsEvent = { + .struct_size = sizeof(windowMetricsEvent), .width = static_cast(scaledSize.width), .height = static_cast(scaledSize.height), .pixel_ratio = pixelRatio, + .left = static_cast(scaledBounds.origin.x), + .top = static_cast(scaledBounds.origin.y), }; - FlutterEngineSendWindowMetricsEvent(_engine, &event); + FlutterEngineSendWindowMetricsEvent(_engine, &windowMetricsEvent); } - (void)sendPointerEvent:(const FlutterPointerEvent&)event { diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 06b45ac5372e1..dfb880c0aaea5 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1,8 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER #define RAPIDJSON_HAS_STDSTRING 1 @@ -661,8 +659,9 @@ FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { return kSuccess; } -void PopulateSnapshotMappingCallbacks(const FlutterProjectArgs* args, - flutter::Settings& settings) { +void PopulateSnapshotMappingCallbacks( + const FlutterProjectArgs* args, + flutter::Settings& settings) { // NOLINT(google-runtime-references) // There are no ownership concerns here as all mappings are owned by the // embedder and not the engine. auto make_mapping_callback = [](const uint8_t* mapping, size_t size) { diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 51d832bfe5397..f2cf40f914b73 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -461,6 +461,10 @@ typedef struct { size_t height; /// Scale factor for the physical screen. double pixel_ratio; + /// Horizontal physical location of the left side of the window on the screen. + size_t left; + /// Vertical physical location of the top of the window on the screen. + size_t top; } FlutterWindowMetricsEvent; /// The phase of the pointer event. diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 5dae4e494745c..e662257977683 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -34,7 +34,7 @@ void sayHiFromCustomEntrypoint3() native 'SayHiFromCustomEntrypoint3'; @pragma('vm:entry-point') void invokePlatformTaskRunner() { - window.sendPlatformMessage('OhHi', null, null); + PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null); } @@ -59,19 +59,19 @@ void notifySemanticsEnabled(bool enabled) native 'NotifySemanticsEnabled'; void notifyAccessibilityFeatures(bool reduceMotion) native 'NotifyAccessibilityFeatures'; void notifySemanticsAction(int nodeId, int action, List data) native 'NotifySemanticsAction'; -/// Returns a future that completes when `window.onSemanticsEnabledChanged` -/// fires. +/// Returns a future that completes when +/// `PlatformDispatcher.instance.onSemanticsEnabledChanged` fires. Future get semanticsChanged { final Completer semanticsChanged = Completer(); - window.onSemanticsEnabledChanged = semanticsChanged.complete; + PlatformDispatcher.instance.onSemanticsEnabledChanged = semanticsChanged.complete; return semanticsChanged.future; } -/// Returns a future that completes when `window.onAccessibilityFeaturesChanged` -/// fires. +/// Returns a future that completes when +/// `PlatformDispatcher.instance.onAccessibilityFeaturesChanged` fires. Future get accessibilityFeaturesChanged { final Completer featuresChanged = Completer(); - window.onAccessibilityFeaturesChanged = featuresChanged.complete; + PlatformDispatcher.instance.onAccessibilityFeaturesChanged = featuresChanged.complete; return featuresChanged.future; } @@ -84,7 +84,7 @@ class SemanticsActionData { Future get semanticsAction { final Completer actionReceived = Completer(); - window.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { + PlatformDispatcher.instance.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { actionReceived.complete(SemanticsActionData(id, action, args)); }; return actionReceived.future; @@ -93,18 +93,18 @@ Future get semanticsAction { @pragma('vm:entry-point') void a11y_main() async { // ignore: non_constant_identifier_names // Return initial state (semantics disabled). - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); // Await semantics enabled from embedder. await semanticsChanged; - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); // Return initial state of accessibility features. - notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); // Await accessibility features changed from embedder. await accessibilityFeaturesChanged; - notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); // Fire semantics update. final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder() @@ -143,7 +143,7 @@ void a11y_main() async { // ignore: non_constant_identifier_names label: 'Archive', hint: 'archive message', ); - window.updateSemantics(builder.build()); + PlatformDispatcher.instance.updateSemantics(builder.build()); signalNativeTest(); // Await semantics action from embedder. @@ -153,13 +153,13 @@ void a11y_main() async { // ignore: non_constant_identifier_names // Await semantics disabled from embedder. await semanticsChanged; - notifySemanticsEnabled(window.semanticsEnabled); + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); } @pragma('vm:entry-point') void platform_messages_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { callback(data); }; signalNativeTest(); @@ -167,7 +167,7 @@ void platform_messages_response() { @pragma('vm:entry-point') void platform_messages_no_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { var list = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); signalNativeMessage(utf8.decode(list)); // This does nothing because no one is listening on the other side. But complete the loop anyway @@ -179,7 +179,7 @@ void platform_messages_no_response() { @pragma('vm:entry-point') void null_platform_messages() { - window.onPlatformMessage = + PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { // This checks if the platform_message null data is converted to Flutter null. signalNativeMessage((null == data).toString()); @@ -198,7 +198,7 @@ Picture CreateSimplePicture() { @pragma('vm:entry-point') void can_composite_platform_views() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pushOffset(1.0, 2.0); @@ -206,15 +206,15 @@ void can_composite_platform_views() { builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_opacity() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Root node @@ -236,24 +236,24 @@ void can_composite_platform_views_with_opacity() { builder.pop(); signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_with_opacity() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOpacity(127); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateColoredBox(Color color, Size size) { @@ -267,7 +267,7 @@ Picture CreateColoredBox(Color color, Size size) { @pragma('vm:entry-point') void can_composite_platform_views_with_known_scene() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color blue = Color.fromARGB(127, 0, 0, 255); Color gray = Color.fromARGB(127, 127, 127, 127); @@ -298,17 +298,17 @@ void can_composite_platform_views_with_known_scene() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_root_layer_only() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -319,17 +319,17 @@ void can_composite_platform_views_with_root_layer_only() { builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_platform_layer_on_bottom() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -345,17 +345,17 @@ void can_composite_platform_views_with_platform_layer_on_bottom() { builder.pop(); builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_render_scene_without_custom_compositor() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color green = Color.fromARGB(127, 0, 255, 0); Color blue = Color.fromARGB(127, 0, 0, 255); @@ -380,9 +380,9 @@ void can_render_scene_without_custom_compositor() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateGradientBox(Size size) { @@ -417,7 +417,7 @@ Picture CreateGradientBox(Size size) { @pragma('vm:entry-point') void render_gradient() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); SceneBuilder builder = SceneBuilder(); @@ -428,14 +428,14 @@ void render_gradient() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void render_gradient_on_non_root_backing_store() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); Color red = Color.fromARGB(127, 255, 0, 0); @@ -452,14 +452,14 @@ void render_gradient_on_non_root_backing_store() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void verify_b141980393() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { // The platform view in the test case is screen sized but with margins of 31 // and 37 points from the top and bottom. double top_margin = 31.0; @@ -478,14 +478,14 @@ void verify_b141980393() { builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_display_platform_view_with_pixel_ratio() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushTransform(Float64List.fromList([ 2.0, 0.0, 0.0, 0.0, @@ -499,15 +499,15 @@ void can_display_platform_view_with_pixel_ratio() { builder.pop(); // offset builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(128, 255, 0, 0), Size(400.0, 300.0))); builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void can_receive_locale_updates() { - window.onLocaleChanged = (){ - signalNativeCount(window.locales.length); + PlatformDispatcher.instance.onLocaleChanged = (){ + signalNativeCount(PlatformDispatcher.instance.locales.length); }; signalNativeTest(); } @@ -515,7 +515,7 @@ void can_receive_locale_updates() { // Verifies behavior tracked in https://github.com/flutter/flutter/issues/43732 @pragma('vm:entry-point') void verify_b143464703() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base @@ -542,14 +542,14 @@ void verify_b143464703() { builder.pop(); // 1 builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void push_frames_over_and_over() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(255, 128, 128, 128), Size(1024.0, 600.0))); @@ -557,17 +557,17 @@ void push_frames_over_and_over() { builder.addPlatformView(42, width: 1024.0, height: 540.0); builder.pop(); builder.pop(); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(800.0, 600.0))); @@ -581,14 +581,14 @@ void platform_view_mutators() { builder.pop(); // opacity builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators_with_pixel_ratio() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); @@ -602,29 +602,29 @@ void platform_view_mutators_with_pixel_ratio() { builder.pop(); // opacity builder.pop(); // base - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void empty_scene() { - window.onBeginFrame = (Duration duration) { - window.render(SceneBuilder().build()); + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.views.first.render(SceneBuilder().build()); signalNativeTest(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_with_no_container() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); signalNativeTest(); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } Picture CreateArcEndCapsPicture() { @@ -646,29 +646,29 @@ Picture CreateArcEndCapsPicture() { @pragma('vm:entry-point') void arc_end_caps_correct() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateArcEndCapsPicture()); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_clips() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(10.0, 10.0, 390.0, 290.0)); builder.addPlatformView(42, width: 400.0, height: 300.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_complex_clips() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 1024.0, 600.0)); @@ -679,9 +679,9 @@ void scene_builder_with_complex_clips() { builder.addPlatformView(42, width: 1024.0, height: 600.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(1024.0, 600.0))); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } void sendObjectToNativeCode(dynamic object) native 'SendObjectToNativeCode'; @@ -695,43 +695,43 @@ void objects_can_be_posted() { @pragma('vm:entry-point') void empty_scene_posts_zero_layers_to_compositor() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Should not render anything. builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 300.0, 200.0)); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void compositor_can_post_only_platform_views() { - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPlatformView(42, width: 300.0, height: 200.0); builder.addPlatformView(24, width: 300.0, height: 200.0); - window.render(builder.build()); + PlatformDispatcher.instance.views.first.render(builder.build()); }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } @pragma('vm:entry-point') void render_targets_are_recycled() { int frame_count = 0; - window.onBeginFrame = (Duration duration) { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); for (int i = 0; i < 10; i++) { builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(30.0, 20.0))); builder.addPlatformView(42 + i, width: 30.0, height: 20.0); } - window.render(builder.build()); - window.scheduleFrame(); + PlatformDispatcher.instance.views.first.render(builder.build()); + PlatformDispatcher.instance.scheduleFrame(); frame_count++; if (frame_count == 8) { signalNativeTest(); } }; - window.scheduleFrame(); + PlatformDispatcher.instance.scheduleFrame(); } void nativeArgumentsCallback(List args) native 'NativeArgumentsCallback'; diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 21eda23623664..ab87370927149 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -27,6 +27,7 @@ part '../../lib/ui/hooks.dart'; part '../../lib/ui/lerp.dart'; part '../../lib/ui/natives.dart'; part '../../lib/ui/painting.dart'; +part '../../lib/ui/platform_dispatcher.dart'; part '../../lib/ui/pointer.dart'; part '../../lib/ui/semantics.dart'; part '../../lib/ui/text.dart'; @@ -44,15 +45,22 @@ void main() { PlatformMessageCallback? originalOnPlatformMessage; VoidCallback? originalOnTextScaleFactorChanged; - double? oldDPR; - Size? oldSize; - WindowPadding? oldPadding; - WindowPadding? oldInsets; - WindowPadding? oldSystemGestureInsets; + Object? oldWindowId; + double? oldDevicePixelRatio; + Rect? oldGeometry; + + WindowPadding? oldPadding; + WindowPadding? oldInsets; + WindowPadding? oldSystemGestureInsets; void setUp() { - oldDPR = window.devicePixelRatio; - oldSize = window.physicalSize; + PlatformDispatcher.instance._viewConfigurations.clear(); + PlatformDispatcher.instance._views.clear(); + PlatformDispatcher.instance._viewConfigurations[0] = const ViewConfiguration(); + PlatformDispatcher.instance._views[0] = FlutterWindow._(0, PlatformDispatcher.instance); + oldWindowId = window._windowId; + oldDevicePixelRatio = window.devicePixelRatio; + oldGeometry = window.viewConfiguration.geometry; oldPadding = window.viewPadding; oldInsets = window.viewInsets; oldSystemGestureInsets = window.systemGestureInsets; @@ -69,34 +77,35 @@ void main() { originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged; } - void tearDown() { - _updateWindowMetrics( - oldDPR!, // DPR - oldSize!.width, // width - oldSize!.height, // height - oldPadding!.top, // padding top - oldPadding!.right, // padding right - oldPadding!.bottom, // padding bottom - oldPadding!.left, // padding left - oldInsets!.top, // inset top - oldInsets!.right, // inset right - oldInsets!.bottom, // inset bottom - oldInsets!.left, // inset left - oldSystemGestureInsets!.top, // system gesture inset top - oldSystemGestureInsets!.right, // system gesture inset right - oldSystemGestureInsets!.bottom, // system gesture inset bottom - oldSystemGestureInsets!.left, // system gesture inset left - ); - window.onMetricsChanged = originalOnMetricsChanged; - window.onLocaleChanged = originalOnLocaleChanged; - window.onBeginFrame = originalOnBeginFrame; - window.onDrawFrame = originalOnDrawFrame; - window.onReportTimings = originalOnReportTimings; - window.onPointerDataPacket = originalOnPointerDataPacket; - window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; - window.onSemanticsAction = originalOnSemanticsAction; - window.onPlatformMessage = originalOnPlatformMessage; - window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; + tearDown() { + _updateWindowMetrics( + oldWindowId!, // window id + oldDevicePixelRatio!, // device pixel ratio + oldGeometry!.width, // width + oldGeometry!.height, // height + oldPadding!.top, // padding top + oldPadding!.right, // padding right + oldPadding!.bottom, // padding bottom + oldPadding!.left, // padding left + oldInsets!.top, // inset top + oldInsets!.right, // inset right + oldInsets!.bottom, // inset bottom + oldInsets!.left, // inset left + oldSystemGestureInsets!.top, // system gesture inset top + oldSystemGestureInsets!.right, // system gesture inset right + oldSystemGestureInsets!.bottom, // system gesture inset bottom + oldSystemGestureInsets!.left, // system gesture inset left + ); + window.onMetricsChanged = originalOnMetricsChanged; + window.onLocaleChanged = originalOnLocaleChanged; + window.onBeginFrame = originalOnBeginFrame; + window.onDrawFrame = originalOnDrawFrame; + window.onReportTimings = originalOnReportTimings; + window.onPointerDataPacket = originalOnPointerDataPacket; + window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; + window.onSemanticsAction = originalOnSemanticsAction; + window.onPlatformMessage = originalOnPlatformMessage; + window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; } void test(String description, void Function() testFunction) { @@ -155,7 +164,8 @@ void main() { window.onMetricsChanged!(); _updateWindowMetrics( - 0.1234, // DPR + 0, // window id + 0.1234, // device pixel ratio 0.0, // width 0.0, // height 0.0, // padding top @@ -234,7 +244,7 @@ void main() { late Zone innerZone; late Zone runZone; - window._setNeedsReportTimings = (bool _) {}; + PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; runZoned(() { innerZone = Zone.current; @@ -265,7 +275,7 @@ void main() { _dispatchPointerDataPacket(testData); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); - expectIterablesEqual(data.data, _unpackPointerDataPacket(testData).data); + expectIterablesEqual(data.data, PlatformDispatcher._unpackPointerDataPacket(testData).data); }); test('onSemanticsEnabledChanged preserves callback zone', () { @@ -281,11 +291,12 @@ void main() { }; }); - _updateSemanticsEnabled(window._semanticsEnabled); + final bool newValue = !window.semanticsEnabled; // needed? + _updateSemanticsEnabled(newValue); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); expectNotEquals(enabled, null); - expectEquals(enabled, window._semanticsEnabled); + expectEquals(enabled, newValue); }); test('onSemanticsAction preserves callback zone', () { @@ -331,47 +342,45 @@ void main() { test('onTextScaleFactorChanged preserves callback zone', () { late Zone innerZone; - late Zone runZone; - late double textScaleFactor; + late Zone runZoneTextScaleFactor; + late Zone runZonePlatformBrightness; + late double? textScaleFactor; + late Brightness? platformBrightness; runZoned(() { innerZone = Zone.current; window.onTextScaleFactorChanged = () { - runZone = Zone.current; + runZoneTextScaleFactor = Zone.current; textScaleFactor = window.textScaleFactor; }; + window.onPlatformBrightnessChanged = () { + runZonePlatformBrightness = Zone.current; + platformBrightness = window.platformBrightness; + }; }); window.onTextScaleFactorChanged!(); - _updateTextScaleFactor(0.5); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(textScaleFactor, 0.5); - }); - test('onThemeBrightnessMode preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late Brightness platformBrightness; + _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); + expectNotEquals(runZoneTextScaleFactor, null); + expectIdentical(runZoneTextScaleFactor, innerZone); + expectEquals(textScaleFactor, 0.5); - runZoned(() { - innerZone = Zone.current; - window.onPlatformBrightnessChanged = () { - runZone = Zone.current; - platformBrightness = window.platformBrightness; - }; - }); + textScaleFactor = null; + platformBrightness = null; window.onPlatformBrightnessChanged!(); - _updatePlatformBrightness('dark'); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); + _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); + expectNotEquals(runZonePlatformBrightness, null); + expectIdentical(runZonePlatformBrightness, innerZone); expectEquals(platformBrightness, Brightness.dark); + }); test('Window padding/insets/viewPadding/systemGestureInsets', () { _updateWindowMetrics( - 1.0, // DPR + 0, // window id + 0, // screen id 800.0, // width 600.0, // height 50.0, // padding top @@ -394,7 +403,8 @@ void main() { expectEquals(window.systemGestureInsets.bottom, 0.0); _updateWindowMetrics( - 1.0, // DPR + 0, // window id + 0, // screen id 800.0, // width 600.0, // height 50.0, // padding top @@ -405,10 +415,10 @@ void main() { 0.0, // inset right 400.0, // inset bottom 0.0, // inset left - 0.0, // system gesture insets top - 0.0, // system gesture insets right - 44.0, // system gesture insets bottom - 0.0, // system gesture insets left + 0.0, // system gesture inset top + 0.0, // system gesture inset right + 44.0, // system gesture inset bottom + 0.0, // system gesture inset left ); expectEquals(window.viewInsets.bottom, 400.0); diff --git a/testing/scenario_app/lib/src/animated_color_square.dart b/testing/scenario_app/lib/src/animated_color_square.dart index f1e9865f55309..b85f20d4d442b 100644 --- a/testing/scenario_app/lib/src/animated_color_square.dart +++ b/testing/scenario_app/lib/src/animated_color_square.dart @@ -16,10 +16,10 @@ import 'scenario.dart'; class AnimatedColorSquareScenario extends Scenario { /// Creates the AnimatedColorSquare scenario. /// - /// The [window] parameter must not be null. - AnimatedColorSquareScenario(Window window) - : assert(window != null), - super(window); + /// The [dispatcher] parameter must not be null. + AnimatedColorSquareScenario(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); static const double _squareSize = 200; /// Used to animate the red value in the color of the square. diff --git a/testing/scenario_app/lib/src/channel_util.dart b/testing/scenario_app/lib/src/channel_util.dart index 986494f5ff4e9..24f05f2fc0658 100644 --- a/testing/scenario_app/lib/src/channel_util.dart +++ b/testing/scenario_app/lib/src/channel_util.dart @@ -10,13 +10,13 @@ import 'package:meta/meta.dart'; /// Util method to replicate the behavior of a `MethodChannel` in the Flutter /// framework. void sendJsonMethodCall({ - @required Window window, + @required PlatformDispatcher dispatcher, @required String channel, @required String method, dynamic arguments, PlatformMessageResponseCallback callback, }) { - window.sendPlatformMessage( + dispatcher.sendPlatformMessage( channel, // This recreates a combination of OptionalMethodChannel, JSONMethodCodec, // and _DefaultBinaryMessenger in the framework. diff --git a/testing/scenario_app/lib/src/initial_route_reply.dart b/testing/scenario_app/lib/src/initial_route_reply.dart index c42e7608da93d..ffabb8d072ced 100644 --- a/testing/scenario_app/lib/src/initial_route_reply.dart +++ b/testing/scenario_app/lib/src/initial_route_reply.dart @@ -15,14 +15,14 @@ class InitialRouteReply extends Scenario { /// Creates the InitialRouteReply. /// /// The [window] parameter must not be null. - InitialRouteReply(Window window) - : assert(window != null), - super(window); + InitialRouteReply(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); @override void onBeginFrame(Duration duration) { sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'initial_route_test_channel', method: window.defaultRouteName, ); diff --git a/testing/scenario_app/lib/src/locale_initialization.dart b/testing/scenario_app/lib/src/locale_initialization.dart index 6157de30be903..7d9faf6415f7a 100644 --- a/testing/scenario_app/lib/src/locale_initialization.dart +++ b/testing/scenario_app/lib/src/locale_initialization.dart @@ -12,9 +12,9 @@ import 'scenario.dart'; /// Sends the recieved locale data back as semantics information. class LocaleInitialization extends Scenario { /// Constructor - LocaleInitialization(Window window) - : assert(window != null), - super(window); + LocaleInitialization(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); int _tapCount = 0; @@ -82,10 +82,11 @@ class LocaleInitialization extends Scenario { /// Send changing information via semantics on each successive tap. @override void onPointerDataPacket(PointerDataPacket packet) { - String label; + String label = ''; switch(_tapCount) { case 1: { // Set label to string data we wish to pass on first frame. + label = '1'; break; } // Expand for other test cases. diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index e9802438eb5e4..fd6f5f47887e9 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -32,11 +32,11 @@ List _to64(num value) { class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -56,11 +56,11 @@ class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewNoOverlayIntersectionScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -84,11 +84,11 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatf class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewPartialIntersectionScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewPartialIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier . @@ -112,11 +112,11 @@ class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatfor class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -128,7 +128,7 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -153,11 +153,11 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewOneOverlayTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -169,7 +169,7 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -199,12 +199,12 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewWithoutOverlaysScenario(Window window, String text, { this.firstId, this.secondId }) - : assert(window != null), - super(window) { - createPlatformView(window, text, firstId); - createPlatformView(window, text, secondId); + /// The [dispatcher] parameter must not be null. + MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.firstId, this.secondId }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, firstId); + createPlatformView(dispatcher, text, secondId); } /// The platform view identifier to use for the first platform view. @@ -220,10 +220,10 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -245,11 +245,11 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewMaxOverlaysScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + /// The [dispatcher] parameter must not be null. + PlatformViewMaxOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -261,7 +261,7 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce builder.pushOffset(0, 0); - _addPlatformViewtoScene(builder, id, 500, 500); + _addPlatformViewToScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -296,12 +296,12 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewScenario(Window window, {this.firstId, this.secondId}) - : assert(window != null), - super(window) { - createPlatformView(window, 'platform view 1', firstId); - createPlatformView(window, 'platform view 2', secondId); + /// The [dispatcher] parameter must not be null. + MultiPlatformViewScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, 'platform view 1', firstId); + createPlatformView(dispatcher, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -317,7 +317,7 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); finishBuilderByAddingPlatformViewAndPicture(builder, secondId); @@ -332,13 +332,13 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - MultiPlatformViewBackgroundForegroundScenario(Window window, {this.firstId, this.secondId}) - : assert(window != null), - super(window) { + /// The [dispatcher] parameter must not be null. + MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) + : assert(dispatcher != null), + super(dispatcher) { _nextFrame = _firstFrame; - createPlatformView(window, 'platform view 1', firstId); - createPlatformView(window, 'platform view 2', secondId); + createPlatformView(dispatcher, 'platform view 1', firstId); + createPlatformView(dispatcher, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -360,10 +360,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -386,10 +386,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewtoScene(builder, firstId, 500, 500); + _addPlatformViewToScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewtoScene(builder, secondId, 500, 500); + _addPlatformViewToScene(builder, secondId, 500, 500); final Scene scene = builder.build(); window.render(scene); scene.dispose(); @@ -419,10 +419,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP /// Platform view with clip rect. class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Constructs a platform view with clip rect scenario. - PlatformViewClipRectScenario(Window window, String text, { this.id }) - : assert(window != null), - super(window) { - createPlatformView(window, text, id); + PlatformViewClipRectScenario(PlatformDispatcher dispatcher, String text, { this.id }) + : assert(dispatcher != null), + super(dispatcher) { + createPlatformView(dispatcher, text, id); } /// The platform view identifier. @@ -440,8 +440,8 @@ class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenar /// Platform view with clip rrect. class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipRRectScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewClipRRectScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -466,8 +466,8 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Platform view with clip path. class PlatformViewClipPathScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipPathScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewClipPathScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -490,8 +490,8 @@ class PlatformViewClipPathScenario extends PlatformViewScenario { /// Platform view with transform. class PlatformViewTransformScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewTransformScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewTransformScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -512,8 +512,8 @@ class PlatformViewTransformScenario extends PlatformViewScenario { /// Platform view with opacity. class PlatformViewOpacityScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewOpacityScenario(Window window, String text, { int id = 0 }) - : super(window, text, id: id); + PlatformViewOpacityScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) + : super(dispatcher, text, id: id); @override void onBeginFrame(Duration duration) { @@ -536,16 +536,16 @@ class PlatformViewForTouchIOSScenario extends Scenario VoidCallback _nextFrame; /// Creates the PlatformView scenario. /// - /// The [window] parameter must not be null. - PlatformViewForTouchIOSScenario(Window window, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) - : assert(window != null), + /// The [dispatcher] parameter must not be null. + PlatformViewForTouchIOSScenario(PlatformDispatcher dispatcher, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) + : assert(dispatcher != null), _accept = accept, _viewId = id, - super(window) { + super(dispatcher) { if (rejectUntilTouchesEnded) { - createPlatformView(window, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); + createPlatformView(dispatcher, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); } else { - createPlatformView(window, text, id); + createPlatformView(dispatcher, text, id); } _nextFrame = _firstFrame; } @@ -625,7 +625,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { /// It prepare a TextPlatformView so it can be added to the SceneBuilder in `onBeginFrame`. /// Call this method in the constructor of the platform view related scenarios /// to perform necessary set up. - void createPlatformView(Window window, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { + void createPlatformView(PlatformDispatcher dispatcher, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { const int _valueTrue = 1; const int _valueInt32 = 3; const int _valueFloat64 = 6; @@ -691,7 +691,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ...utf8.encode(text), ]); - window.sendPlatformMessage( + dispatcher.sendPlatformMessage( 'flutter/platform_views', message.buffer.asByteData(), (ByteData response) { @@ -703,7 +703,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ); } - void _addPlatformViewtoScene( + void _addPlatformViewToScene( SceneBuilder sceneBuilder, int viewId, double width, @@ -729,7 +729,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { Offset overlayOffset, }) { overlayOffset ??= const Offset(50, 50); - _addPlatformViewtoScene( + _addPlatformViewToScene( sceneBuilder, viewId, 500, diff --git a/testing/scenario_app/lib/src/poppable_screen.dart b/testing/scenario_app/lib/src/poppable_screen.dart index 6ecd655b02995..f53cce7890b66 100644 --- a/testing/scenario_app/lib/src/poppable_screen.dart +++ b/testing/scenario_app/lib/src/poppable_screen.dart @@ -14,10 +14,10 @@ import 'scenario.dart'; class PoppableScreenScenario extends Scenario with PlatformEchoMixin { /// Creates the PoppableScreenScenario. /// - /// The [window] parameter must not be null. - PoppableScreenScenario(Window window) - : assert(window != null), - super(window); + /// The [dispatcher] parameter must not be null. + PoppableScreenScenario(PlatformDispatcher dispatcher) + : assert(dispatcher != null), + super(dispatcher); // Rect for the pop button. Only defined once onMetricsChanged is called. Rect _buttonRect; @@ -74,7 +74,7 @@ class PoppableScreenScenario extends Scenario with PlatformEchoMixin { void _pop() { sendJsonMethodCall( - window: window, + dispatcher: dispatcher, // 'flutter/platform' is the hardcoded name of the 'platform' // `SystemChannel` from the `SystemNavigator` API. // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/system_navigator.dart. diff --git a/testing/scenario_app/lib/src/scenario.dart b/testing/scenario_app/lib/src/scenario.dart index ccf2be02778de..94319e692ea0e 100644 --- a/testing/scenario_app/lib/src/scenario.dart +++ b/testing/scenario_app/lib/src/scenario.dart @@ -8,31 +8,31 @@ import 'dart:ui'; /// A scenario to run for testing. abstract class Scenario { - /// Creates a new scenario using a specific Window instance. - Scenario(this.window); + /// Creates a new scenario using a specific FlutterWindow instance. + Scenario(this.dispatcher); /// The window used by this scenario. May be mocked. - final Window window; + final PlatformDispatcher dispatcher; /// [true] if a screenshot is taken in the next frame. bool _didScheduleScreenshot = false; /// Called by the program when a frame is ready to be drawn. /// - /// See [Window.onBeginFrame] for more details. + /// See [PlatformDispatcher.onBeginFrame] for more details. void onBeginFrame(Duration duration) {} /// Called by the program when the microtasks from [onBeginFrame] have been /// flushed. /// - /// See [Window.onDrawFrame] for more details. + /// See [PlatformDispatcher.onDrawFrame] for more details. void onDrawFrame() { Future.delayed(const Duration(seconds: 1), () { if (_didScheduleScreenshot) { - window.sendPlatformMessage('take_screenshot', null, null); + dispatcher.sendPlatformMessage('take_screenshot', null, null); } else { _didScheduleScreenshot = true; - window.scheduleFrame(); + dispatcher.scheduleFrame(); } }); } @@ -45,18 +45,18 @@ abstract class Scenario { /// Called by the program when the window metrics have changed. /// - /// See [Window.onMetricsChanged]. + /// See [PlatformDispatcher.onMetricsChanged]. void onMetricsChanged() {} /// Called by the program when a pointer event is received. /// - /// See [Window.onPointerDataPacket]. + /// See [PlatformDispatcher.onPointerDataPacket]. void onPointerDataPacket(PointerDataPacket packet) {} /// Called by the program when an engine side platform channel message is /// received. /// - /// See [Window.onPlatformMessage]. + /// See [PlatformDispatcher.onPlatformMessage]. void onPlatformMessage( String name, ByteData data, diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index 6aa553380d34c..69c4d034953b2 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -19,30 +19,30 @@ typedef ScenarioFactory = Scenario Function(); int _viewId = 0; Map _scenarios = { - 'animated_color_square': () => AnimatedColorSquareScenario(window), - 'locale_initialization': () => LocaleInitialization(window), - 'platform_view': () => PlatformViewScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(window, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), - 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_cliprect': () => PlatformViewClipRectScenario(window, 'PlatformViewClipRect', id: _viewId++), - 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(window, 'PlatformViewClipRRect', id: _viewId++), - 'platform_view_clippath': () => PlatformViewClipPathScenario(window, 'PlatformViewClipPath', id: _viewId++), - 'platform_view_transform': () => PlatformViewTransformScenario(window, 'PlatformViewTransform', id: _viewId++), - 'platform_view_opacity': () => PlatformViewOpacityScenario(window, 'PlatformViewOpacity', id: _viewId++), - 'platform_view_multiple': () => MultiPlatformViewScenario(window, firstId: 6, secondId: _viewId++), - 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(window, firstId: _viewId++, secondId: _viewId++), - 'poppable_screen': () => PoppableScreenScenario(window), - 'platform_view_rotate': () => PlatformViewScenario(window, 'Rotate Platform View', id: _viewId++), - 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false), - 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: true), - 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), - 'tap_status_bar': () => TouchesScenario(window), - 'text_semantics_focus': () => SendTextFocusScemantics(window), - 'initial_route_reply': () => InitialRouteReply(window), + 'animated_color_square': () => AnimatedColorSquareScenario(PlatformDispatcher.instance), + 'locale_initialization': () => LocaleInitialization(PlatformDispatcher.instance), + 'platform_view': () => PlatformViewScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), + 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_cliprect': () => PlatformViewClipRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRect', id: _viewId++), + 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRRect', id: _viewId++), + 'platform_view_clippath': () => PlatformViewClipPathScenario(PlatformDispatcher.instance, 'PlatformViewClipPath', id: _viewId++), + 'platform_view_transform': () => PlatformViewTransformScenario(PlatformDispatcher.instance, 'PlatformViewTransform', id: _viewId++), + 'platform_view_opacity': () => PlatformViewOpacityScenario(PlatformDispatcher.instance, 'PlatformViewOpacity', id: _viewId++), + 'platform_view_multiple': () => MultiPlatformViewScenario(PlatformDispatcher.instance, firstId: 6, secondId: _viewId++), + 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher.instance, firstId: _viewId++, secondId: _viewId++), + 'poppable_screen': () => PoppableScreenScenario(PlatformDispatcher.instance), + 'platform_view_rotate': () => PlatformViewScenario(PlatformDispatcher.instance, 'Rotate Platform View', id: _viewId++), + 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false), + 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: true), + 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), + 'tap_status_bar': () => TouchesScenario(PlatformDispatcher.instance), + 'text_semantics_focus': () => SendTextFocusSemantics(PlatformDispatcher.instance), + 'initial_route_reply': () => InitialRouteReply(PlatformDispatcher.instance), }; Map _currentScenarioParams = {}; diff --git a/testing/scenario_app/lib/src/send_text_focus_semantics.dart b/testing/scenario_app/lib/src/send_text_focus_semantics.dart index 529dafcea5416..94997cf0701e4 100644 --- a/testing/scenario_app/lib/src/send_text_focus_semantics.dart +++ b/testing/scenario_app/lib/src/send_text_focus_semantics.dart @@ -11,9 +11,9 @@ import 'channel_util.dart'; import 'scenario.dart'; /// A scenario that sends back messages when touches are received. -class SendTextFocusScemantics extends Scenario { +class SendTextFocusSemantics extends Scenario { /// Constructor for `SendTextFocusScemantics`. - SendTextFocusScemantics(Window window) : super(window); + SendTextFocusSemantics(PlatformDispatcher dispatcher) : super(dispatcher); @override void onBeginFrame(Duration duration) { @@ -79,7 +79,7 @@ class SendTextFocusScemantics extends Scenario { // This mimics the framework which shows the FlutterTextInputView before // updating the TextInputSemanticsObject. sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'flutter/textinput', method: 'TextInput.setClient', arguments: [ @@ -91,7 +91,7 @@ class SendTextFocusScemantics extends Scenario { ); sendJsonMethodCall( - window: window, + dispatcher: dispatcher, channel: 'flutter/textinput', method: 'TextInput.show', ); diff --git a/testing/scenario_app/lib/src/touches_scenario.dart b/testing/scenario_app/lib/src/touches_scenario.dart index dbbf50ff15581..6eb35eebe9b28 100644 --- a/testing/scenario_app/lib/src/touches_scenario.dart +++ b/testing/scenario_app/lib/src/touches_scenario.dart @@ -10,7 +10,7 @@ import 'scenario.dart'; /// A scenario that sends back messages when touches are received. class TouchesScenario extends Scenario { /// Constructor for `TouchesScenario`. - TouchesScenario(Window window) : super(window); + TouchesScenario(PlatformDispatcher dispatcher) : super(dispatcher); @override void onPointerDataPacket(PointerDataPacket packet) { From 0d151816c3feb5c1e0299c3e8bfa5bf68fa50937 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Fri, 9 Oct 2020 16:34:08 -0700 Subject: [PATCH 055/219] Remove uses of Dart VM bytecode mode from Flutter engine (#21741) --- flutter_frontend_server/lib/server.dart | 2 -- shell/platform/fuchsia/dart_runner/dart_runner.cc | 4 ---- shell/platform/fuchsia/dart_runner/kernel/BUILD.gn | 5 ----- shell/platform/fuchsia/flutter/component.cc | 11 ----------- shell/platform/fuchsia/flutter/kernel/BUILD.gn | 5 ----- 5 files changed, 27 deletions(-) diff --git a/flutter_frontend_server/lib/server.dart b/flutter_frontend_server/lib/server.dart index 861ad11c572e5..1ced42a4d0de4 100644 --- a/flutter_frontend_server/lib/server.dart +++ b/flutter_frontend_server/lib/server.dart @@ -147,8 +147,6 @@ Future starter( '--target=flutter', '--track-widget-creation', '--enable-asserts', - '--gen-bytecode', - '--bytecode-options=source-positions,local-var-info,debugger-stops,instance-field-initializers,keep-unreachable-code,avoid-closure-call-instructions', ]); compiler ??= _FlutterFrontendCompiler( output, diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.cc b/shell/platform/fuchsia/dart_runner/dart_runner.cc index 959803f5a8f19..e73f2baa4d4c8 100644 --- a/shell/platform/fuchsia/dart_runner/dart_runner.cc +++ b/shell/platform/fuchsia/dart_runner/dart_runner.cc @@ -47,10 +47,6 @@ const char* kDartVMArgs[] = { "--precompilation", #else "--enable_mirrors=false", - - // The interpreter is enabled unconditionally. If an app is built for - // debugging (that is, with no bytecode), the VM will fall back on ASTs. - "--enable_interpreter", #endif // No asserts in debug or release product. diff --git a/shell/platform/fuchsia/dart_runner/kernel/BUILD.gn b/shell/platform/fuchsia/dart_runner/kernel/BUILD.gn index 7bab46d61592f..97f4c508529be 100644 --- a/shell/platform/fuchsia/dart_runner/kernel/BUILD.gn +++ b/shell/platform/fuchsia/dart_runner/kernel/BUILD.gn @@ -22,10 +22,6 @@ compile_platform("kernel_platform_files") { args = [ "--enable-experiment=non-nullable", "--nnbd-agnostic", - - # TODO(dartbug.com/36342): enable bytecode for core libraries when performance of bytecode - # pipeline is on par with default pipeline and continuously tracked. - # "--bytecode", "--target=dart_runner", "dart:core", ] @@ -75,7 +71,6 @@ template("create_kernel_core_snapshot") { # TODO(FL-117): Re-enable causal async stack traces when this issue is # addressed. "--no_causal_async_stacks", - "--use_bytecode_compiler", "--enable_mirrors=false", "--deterministic", "--snapshot_kind=core-jit", diff --git a/shell/platform/fuchsia/flutter/component.cc b/shell/platform/fuchsia/flutter/component.cc index f317d7b988d5e..d60dff5c8d111 100644 --- a/shell/platform/fuchsia/flutter/component.cc +++ b/shell/platform/fuchsia/flutter/component.cc @@ -406,17 +406,6 @@ Application::Application( // addressed. settings_.dart_flags = {"--no_causal_async_stacks"}; - // Disable code collection as it interferes with JIT code warmup - // by decreasing usage counters and flushing code which is still useful. - settings_.dart_flags.push_back("--no-collect_code"); - - if (!flutter::DartVM::IsRunningPrecompiledCode()) { - // The interpreter is enabled unconditionally in JIT mode. If an app is - // built for debugging (that is, with no bytecode), the VM will fall back on - // ASTs. - settings_.dart_flags.push_back("--enable_interpreter"); - } - // Don't collect CPU samples from Dart VM C++ code. settings_.dart_flags.push_back("--no_profile_vm"); diff --git a/shell/platform/fuchsia/flutter/kernel/BUILD.gn b/shell/platform/fuchsia/flutter/kernel/BUILD.gn index f221f4af1977c..724b40a590299 100644 --- a/shell/platform/fuchsia/flutter/kernel/BUILD.gn +++ b/shell/platform/fuchsia/flutter/kernel/BUILD.gn @@ -22,10 +22,6 @@ compile_platform("kernel_platform_files") { args = [ "--enable-experiment=non-nullable", "--nnbd-agnostic", - - # TODO(dartbug.com/36342): enable bytecode for core libraries when performance of bytecode - # pipeline is on par with default pipeline and continuously tracked. - # "--bytecode", "--target=flutter_runner", "dart:core", ] @@ -79,7 +75,6 @@ template("core_snapshot") { # TODO(FL-117): Re-enable causal async stack traces when this issue is # addressed. "--no_causal_async_stacks", - "--use_bytecode_compiler", "--enable_mirrors=false", "--deterministic", "--snapshot_kind=core-jit", From 8be6cc0390099f53e6a3e91667d53dccc356c10a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 20:32:01 -0400 Subject: [PATCH 056/219] Roll Skia from 88cda17bbeb8 to 61003cde7688 (4 revisions) (#21744) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index dad4842135764..bf62a3e3be63a 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '88cda17bbeb8f34273184fd8cd40bc282a001719', + 'skia_revision': '61003cde76882c43ab593a5fd889fc90b7d10abf', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 5bd08f3cce802..66d761f6288e3 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 5631ff969ea0de3e53374d25f5256624 +Signature: 3355cd28697392bb51f37379f2504269 UNUSED LICENSES: @@ -6481,6 +6481,7 @@ FILE: ../../../third_party/skia/infra/bots/gen_tasks_logic/task_builder.go FILE: ../../../third_party/skia/infra/bots/task_drivers/canary/canary.go FILE: ../../../third_party/skia/infra/bots/task_drivers/cifuzz/cifuzz.go FILE: ../../../third_party/skia/infra/bots/task_drivers/cifuzz/cifuzz_test.go +FILE: ../../../third_party/skia/infra/bots/task_drivers/compile_wasm_gm_tests/compile_wasm_gm_tests.go FILE: ../../../third_party/skia/infra/bots/task_drivers/fm_driver/fm_driver.go FILE: ../../../third_party/skia/infra/bots/task_drivers/g3_canary/g3_canary.go FILE: ../../../third_party/skia/infra/bots/task_drivers/perf_puppeteer_canvas/perf_puppeteer_canvas.go From a2046aeb6c2022098a5dc69482ad7e6c2c8c5d55 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Fri, 9 Oct 2020 19:57:04 -0700 Subject: [PATCH 057/219] Revert "fix On iOS, dialog titles are announced twice (#19826)" (#21714) --- .../darwin/ios/framework/Source/SemanticsObject.h | 2 +- .../ios/framework/Source/SemanticsObject.mm | 15 +++++++-------- .../ios/framework/Source/accessibility_bridge.mm | 7 ++----- .../framework/Source/accessibility_bridge_test.mm | 13 ++++++------- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h index dded845d70137..8df516483f270 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h @@ -93,7 +93,7 @@ constexpr int32_t kRootNodeId = 0; - (BOOL)nodeWillCauseScroll:(const flutter::SemanticsNode*)node; - (BOOL)nodeShouldTriggerAnnouncement:(const flutter::SemanticsNode*)node; - (void)collectRoutes:(NSMutableArray*)edges; -- (SemanticsObject*)routeFocusObject; +- (NSString*)routeName; - (BOOL)onCustomAccessibilityAction:(FlutterCustomAccessibilityAction*)action; @end diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index 546e9c5c2c727..d41f0a40aee6f 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -282,21 +282,20 @@ - (BOOL)onCustomAccessibilityAction:(FlutterCustomAccessibilityAction*)action { return YES; } -- (SemanticsObject*)routeFocusObject { - // Returns the first SemanticObject in this branch that has - // the NamesRoute flag with a non-nil semantic label. Otherwise - // returns nil. +- (NSString*)routeName { + // Returns the first non-null and non-empty semantic label of a child + // with an NamesRoute flag. Otherwise returns nil. if ([self node].HasFlag(flutter::SemanticsFlags::kNamesRoute)) { NSString* newName = [self accessibilityLabel]; if (newName != nil && [newName length] > 0) { - return self; + return newName; } } if ([self hasChildren]) { for (SemanticsObject* child in self.children) { - SemanticsObject* focusObject = [child routeFocusObject]; - if (focusObject != nil) { - return focusObject; + NSString* newName = [child routeName]; + if (newName != nil && [newName length] > 0) { + return newName; } } } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index bad9aca0f1e29..33ea01d1881fa 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -198,12 +198,9 @@ void PostAccessibilityNotification(UIAccessibilityNotifications notification, // We should send out only one notification per semantics update. if (routeChanged) { if (!ios_delegate_->IsFlutterViewControllerPresentingModalViewController(view_controller_)) { - SemanticsObject* nextToFocus = [lastAdded routeFocusObject]; - if (!nextToFocus && root) { - nextToFocus = FindFirstFocusable(root); - } + NSString* routeName = [lastAdded routeName]; ios_delegate_->PostAccessibilityNotification(UIAccessibilityScreenChangedNotification, - nextToFocus); + routeName); } } else if (layoutChanged) { SemanticsObject* nextToFocus = nil; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 7f73bc0a5cf22..41970e67c8f73 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -328,9 +328,7 @@ - (void)testAnnouncesRouteChanges { bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions); XCTAssertEqual([accessibility_notifications count], 1ul); - SemanticsObject* focusObject = accessibility_notifications[0][@"argument"]; - XCTAssertEqual([focusObject uid], 3); - XCTAssertEqualObjects([focusObject accessibilityLabel], @"node3"); + XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node3"); XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue], UIAccessibilityScreenChangedNotification); } @@ -373,7 +371,8 @@ - (void)testAnnouncesRouteChangesWhenNoNamesRoute { flutter::SemanticsNode node1; node1.id = 1; node1.label = "node1"; - node1.flags = static_cast(flutter::SemanticsFlags::kScopesRoute); + node1.flags = static_cast(flutter::SemanticsFlags::kScopesRoute) | + static_cast(flutter::SemanticsFlags::kNamesRoute); node1.childrenInTraversalOrder = {2, 3}; node1.childrenInHitTestOrder = {2, 3}; nodes[node1.id] = node1; @@ -394,9 +393,9 @@ - (void)testAnnouncesRouteChangesWhenNoNamesRoute { // Notification should focus first focusable node, which is node1. XCTAssertEqual([accessibility_notifications count], 1ul); - SemanticsObject* focusObject = accessibility_notifications[0][@"argument"]; - XCTAssertEqual([focusObject uid], 2); - XCTAssertEqualObjects([focusObject accessibilityLabel], @"node2"); + id focusObject = accessibility_notifications[0][@"argument"]; + XCTAssertTrue([focusObject isKindOfClass:[NSString class]]); + XCTAssertEqualObjects(focusObject, @"node1"); XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue], UIAccessibilityScreenChangedNotification); } From 3bd695b18af34be51df97418d5bd02a951320562 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 9 Oct 2020 23:02:02 -0400 Subject: [PATCH 058/219] Roll Skia from 61003cde7688 to 13fc260c7080 (1 revision) (#21746) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index bf62a3e3be63a..0a75ad3cd0e68 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '61003cde76882c43ab593a5fd889fc90b7d10abf', + 'skia_revision': '13fc260c7080d2122a7e7152c9385d914cd20be7', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 66d761f6288e3..ba1665f7c6c97 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 3355cd28697392bb51f37379f2504269 +Signature: c252752f32283d7bfc5847ad8abb5106 UNUSED LICENSES: From ca250c972fbcdb8d449e719ebb58031f3fa7513b Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 10 Oct 2020 04:47:01 -0400 Subject: [PATCH 059/219] Roll Fuchsia Mac SDK from lqn8xmlDn... to gzhbqRUap... (#21749) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0a75ad3cd0e68..ada5dd2ffa211 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'lqn8xmlDnsqD12MQd7B8MnZXCxRxPXPWjgjgFfrKBq0C' + 'version': 'gzhbqRUapF3901tvHWNqpQjDGb2gUWZtoUPCG-H6pKQC' } ], 'condition': 'host_os == "mac"', From 7f0e17b67673c02eb500293600bcc90fd55cd3b9 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 10 Oct 2020 21:52:01 -0400 Subject: [PATCH 060/219] Roll Skia from 13fc260c7080 to aa64c352b349 (1 revision) (#21752) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index ada5dd2ffa211..1c208f30dbe92 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '13fc260c7080d2122a7e7152c9385d914cd20be7', + 'skia_revision': 'aa64c352b349a779e98d903eda594dd0ce502736', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index ba1665f7c6c97..79e80323db799 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: c252752f32283d7bfc5847ad8abb5106 +Signature: 1ddc21dfb8c528f98d2a0fc2bdf73821 UNUSED LICENSES: @@ -2781,7 +2781,6 @@ FILE: ../../../third_party/skia/src/core/SkDrawLooper.cpp FILE: ../../../third_party/skia/src/core/SkFontStream.h FILE: ../../../third_party/skia/src/core/SkGpuBlurUtils.cpp FILE: ../../../third_party/skia/src/core/SkGpuBlurUtils.h -FILE: ../../../third_party/skia/src/core/SkLegacyGpuBlurUtils.cpp FILE: ../../../third_party/skia/src/core/SkMatrixUtils.h FILE: ../../../third_party/skia/src/core/SkMessageBus.h FILE: ../../../third_party/skia/src/core/SkMipmap.cpp From d97e74c7c26fdda0baa01a0a683cbb5b3e198ff3 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 10 Oct 2020 21:57:01 -0400 Subject: [PATCH 061/219] Roll Fuchsia Linux SDK from EBX49sN_X... to YRTc9YoiB... (#21753) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 1c208f30dbe92..2c73a5a0c8c4b 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'EBX49sN_XBGH3GkrllJ1SToL2Hlv89TmoSmmNyPuAfwC' + 'version': 'YRTc9YoiBudrtu-0h7MFAJughhWeGXKEli0Y3Xx4SYgC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index fa7003c59e071..62d4b63b2c5e9 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 527d21c239c82c16b39408812dbfcdf3 +Signature: e093fe5000f28119323cb3791ed0e818 UNUSED LICENSES: From 7fc1a0386ba0ddc736ac9e858ccc552bf4fcde4d Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sun, 11 Oct 2020 03:47:02 -0400 Subject: [PATCH 062/219] Roll Skia from aa64c352b349 to d71dc2d25b8b (1 revision) (#21758) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 2c73a5a0c8c4b..7f334ce223d89 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'aa64c352b349a779e98d903eda594dd0ce502736', + 'skia_revision': 'd71dc2d25b8bbe500deae7fb9efe5e8adc8f23b1', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 79e80323db799..6149b62f7b8d8 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 1ddc21dfb8c528f98d2a0fc2bdf73821 +Signature: d0792396454f76c9f2cc311df8081295 UNUSED LICENSES: From a3f3f989a7a4a1e5c190cd1a6b509351bfedb6ed Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sun, 11 Oct 2020 04:47:02 -0400 Subject: [PATCH 063/219] Roll Fuchsia Mac SDK from gzhbqRUap... to _0R2HD4c8... (#21759) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7f334ce223d89..ca99361fe176c 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'gzhbqRUapF3901tvHWNqpQjDGb2gUWZtoUPCG-H6pKQC' + 'version': '_0R2HD4c8JyNrVSHeGJP-GrIImx04mw25xSdA9qfGioC' } ], 'condition': 'host_os == "mac"', From d4b8d676fc51fffc38a790b263efb6e67c831e33 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sun, 11 Oct 2020 14:07:01 -0400 Subject: [PATCH 064/219] Roll Fuchsia Linux SDK from YRTc9YoiB... to Nw5-0_sVF... (#21760) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index ca99361fe176c..2e0084f3fa22c 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'YRTc9YoiBudrtu-0h7MFAJughhWeGXKEli0Y3Xx4SYgC' + 'version': 'Nw5-0_sVFBupjfHPxLzCaOMkNPcBTz_bGTlaiD4dDCUC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 62d4b63b2c5e9..eb94660601b3f 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: e093fe5000f28119323cb3791ed0e818 +Signature: 09c0425a5b93cf3d0905b11f487e301a UNUSED LICENSES: From 910ce92e63bbc3ce322e12b08ee33f4afce883bc Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sun, 11 Oct 2020 18:22:02 -0400 Subject: [PATCH 065/219] Roll Fuchsia Mac SDK from _0R2HD4c8... to 82ankF-Ht... (#21762) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2e0084f3fa22c..4e279e98e3039 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '_0R2HD4c8JyNrVSHeGJP-GrIImx04mw25xSdA9qfGioC' + 'version': '82ankF-HtxaO_Ah2BD65BSWnJSGDo3w-uggMX2BfkM8C' } ], 'condition': 'host_os == "mac"', From bd03014c2e19466f34fc4ed66ad18a317bbd7282 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 07:27:01 -0400 Subject: [PATCH 066/219] Roll Fuchsia Mac SDK from 82ankF-Ht... to FFpTJfmj1... (#21768) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4e279e98e3039..bc1326879ba81 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '82ankF-HtxaO_Ah2BD65BSWnJSGDo3w-uggMX2BfkM8C' + 'version': 'FFpTJfmj1EGxY5dJDoGNmZ4dwub3GsxHTJHDtKGmQcMC' } ], 'condition': 'host_os == "mac"', From f230fded753739294e08af26ce11de8fa153ba9c Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 12 Oct 2020 08:12:02 -0700 Subject: [PATCH 067/219] Use buildroot clang for scenario app (#21690) --- testing/scenario_app/compile_ios_jit.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testing/scenario_app/compile_ios_jit.sh b/testing/scenario_app/compile_ios_jit.sh index 6c072066bfad3..ebd807b5c3306 100755 --- a/testing/scenario_app/compile_ios_jit.sh +++ b/testing/scenario_app/compile_ios_jit.sh @@ -85,12 +85,15 @@ echo "Compiling JIT Snapshot..." cp "$DEVICE_TOOLS/../gen/flutter/lib/snapshot/vm_isolate_snapshot.bin" "$OUTDIR/App.framework/flutter_assets/vm_snapshot_data" +LLVM_BIN_PATH="${SCRIPT_DIR}/../../../buildtools/mac-x64/clang/bin" SYSROOT=$(xcrun --sdk iphonesimulator --show-sdk-path) echo "Using $SYSROOT as sysroot." echo "Creating stub App using $SYSROOT..." -echo "static const int Moo = 88;" | xcrun clang -x c \ +# Use buildroot clang so we can override the linker to use in our LUCI recipe. +# See: https://github.com/flutter/flutter/issues/65901 +echo "static const int Moo = 88;" | "$LLVM_BIN_PATH/clang" -x c \ -arch x86_64 \ -fembed-bitcode-marker \ -isysroot "$SYSROOT" \ @@ -110,4 +113,3 @@ rm -rf "$SCRIPT_DIR/ios/Scenarios/App.framework" rm -rf "$SCRIPT_DIR/ios/Scenarios/Flutter.framework" cp -R "$OUTDIR/App.framework" "$SCRIPT_DIR/ios/Scenarios" cp -R "$DEVICE_TOOLS/../Flutter.framework" "$SCRIPT_DIR/ios/Scenarios" - From 766d682824f6d4d51101acc532d29e208023ef9b Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 12:07:01 -0400 Subject: [PATCH 068/219] Roll Fuchsia Linux SDK from Nw5-0_sVF... to h-DeV4tgE... (#21771) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index bc1326879ba81..a41d7f81435b8 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'Nw5-0_sVFBupjfHPxLzCaOMkNPcBTz_bGTlaiD4dDCUC' + 'version': 'h-DeV4tgEk6jHzUlsoZri9lklKQJDEAk7UVXOEpNKFMC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index eb94660601b3f..2b6d1410e748c 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 09c0425a5b93cf3d0905b11f487e301a +Signature: 1298619133eae5b6bd80c2c69ae6c950 UNUSED LICENSES: From cff4ee74aca163b0d47f1d02ea260cec55c6298f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 12:32:02 -0400 Subject: [PATCH 069/219] Roll Skia from d71dc2d25b8b to ceb6214a556a (5 revisions) (#21772) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index a41d7f81435b8..2d42cd4005d84 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'd71dc2d25b8bbe500deae7fb9efe5e8adc8f23b1', + 'skia_revision': 'ceb6214a556a147deaea561c3dc72d45586ec1fa', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 6149b62f7b8d8..6c86495cfada4 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: d0792396454f76c9f2cc311df8081295 +Signature: 80a5ff3da2cf7f74dc2fd885112b353f UNUSED LICENSES: From fd1ba9ccf05c474e490c1513e246710849286d19 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Mon, 12 Oct 2020 10:08:27 -0700 Subject: [PATCH 070/219] Ignore analysis warning for doc comment (#21773) This is to unblock a Dart -> engine roll. --- testing/scenario_app/lib/src/scenarios.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index 69c4d034953b2..7431752d4bbe1 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -14,7 +14,7 @@ import 'scenario.dart'; import 'send_text_focus_semantics.dart'; import 'touches_scenario.dart'; -typedef ScenarioFactory = Scenario Function(); +typedef ScenarioFactory = Scenario Function(); // ignore: public_member_api_docs int _viewId = 0; From 978cfd6d5663e750b20690094e31e5ef293ddf0a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 14:32:02 -0400 Subject: [PATCH 071/219] Roll Skia from ceb6214a556a to 9213e610ed92 (8 revisions) (#21774) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 2d42cd4005d84..49ec925510ff0 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'ceb6214a556a147deaea561c3dc72d45586ec1fa', + 'skia_revision': '9213e610ed92422d0b39dc4ff12303ca037131d2', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 6c86495cfada4..c73c8d7923307 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 80a5ff3da2cf7f74dc2fd885112b353f +Signature: dd91721a42021f83633b4bd9f44efac5 UNUSED LICENSES: @@ -4066,6 +4066,7 @@ FILE: ../../../third_party/skia/experimental/skrive/src/reader/BinaryReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/JsonReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/StreamReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/StreamReader.h +FILE: ../../../third_party/skia/experimental/svg/utils/SvgTool.cpp FILE: ../../../third_party/skia/gm/3d.cpp FILE: ../../../third_party/skia/gm/bc1_transparency.cpp FILE: ../../../third_party/skia/gm/bicubic.cpp From 106842989b3b4b7c940152139b2949120f868dfd Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Mon, 12 Oct 2020 11:32:21 -0700 Subject: [PATCH 072/219] Roll Dart SDK from 06536d68ca0f to e256855d07ba (6 revisions) (#21775) --- DEPS | 6 +++--- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index 49ec925510ff0..241ea5420be14 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '06536d68ca0f27528b0bf729f4b8d673ed14beda', + 'dart_revision': 'e256855d07ba9cad5048f012f38a05446ca2fe09', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -193,7 +193,7 @@ deps = { Var('dart_git') + '/dart2js_info.git@0632a623b08e1f601c7eba99e0186a581ae799e9', 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@v0.35.0', + Var('dart_git') + '/dartdoc.git@8f5f30e58bbc0f11f104888ee87f11cbd6b82cc7', 'src/third_party/dart/third_party/pkg/ffi': Var('dart_git') + '/ffi.git@454ab0f9ea6bd06942a983238d8a6818b1357edb', @@ -349,7 +349,7 @@ deps = { Var('dart_git') + '/package_config.git@9c586d04bd26fef01215fd10e7ab96a3050cfa64', 'src/third_party/dart/tools/sdks': - {'packages': [{'version': 'version:2.10.0-110.3.beta', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, + {'packages': [{'version': 'version:2.11.0-190.0.dev', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, # WARNING: end of dart dependencies list that is cleaned up automatically - see create_updated_flutter_deps.py. diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 301be45916a8c..a346f76afe6bf 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: e1e238bf83ff046e90191a5521ba595f +Signature: 4f38a8a9ed9b09a6bd9f88ed98a9fe08 UNUSED LICENSES: From 190fd8eb5131c76fdbda260846416e05aab669b9 Mon Sep 17 00:00:00 2001 From: Yuqian Li Date: Mon, 12 Oct 2020 12:02:30 -0700 Subject: [PATCH 073/219] Reland "Create root isolate asynchronously (#20142)" (#21747) This reverts commit 5585ed99039efb3705025e1c58170cdb86af111b. Additionally, the following _flutter.runInView deadlock is fixed. Previously, a deadlock would occur when service protocol _flutter.runInView is used to restart the engine wihtout tearing down the shell: the shared mutex of the service protocol will be locked during the restart as it's in the middle of handling a service protocol message; if ServiceProtocol::AddHandler is also called during the restart, the deadlock happens as AddHandler also requires such lock. test/integration.shard/background_isolate_test.dart would fail without this fix. --- runtime/runtime_controller.cc | 144 ++++++++++++++++++++----------- runtime/runtime_controller.h | 13 ++- runtime/runtime_delegate.h | 2 + shell/common/engine.cc | 4 + shell/common/engine.h | 12 +++ shell/common/engine_unittests.cc | 4 +- shell/common/shell.cc | 29 ++++++- shell/common/shell.h | 4 + 8 files changed, 155 insertions(+), 57 deletions(-) diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index e2b26c2f2f8ab..36e4e1b4e74e1 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -18,7 +18,10 @@ namespace flutter { RuntimeController::RuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners) - : client_(client), vm_(nullptr), task_runners_(p_task_runners) {} + : client_(client), + vm_(nullptr), + task_runners_(p_task_runners), + weak_factory_(this) {} RuntimeController::RuntimeController( RuntimeDelegate& p_client, @@ -52,49 +55,81 @@ RuntimeController::RuntimeController( platform_data_(std::move(p_platform_data)), isolate_create_callback_(p_isolate_create_callback), isolate_shutdown_callback_(p_isolate_shutdown_callback), - persistent_isolate_data_(std::move(p_persistent_isolate_data)) { - // Create the root isolate as soon as the runtime controller is initialized. + persistent_isolate_data_(std::move(p_persistent_isolate_data)), + weak_factory_(this) { + // Create the root isolate as soon as the runtime controller is initialized, + // but not using a synchronous way to avoid blocking the platform thread a + // long time as it is waiting while creating `Shell` on that platform thread. // It will be run at a later point when the engine provides a run // configuration and then runs the isolate. - auto strong_root_isolate = - DartIsolate::CreateRootIsolate( - vm_->GetVMData()->GetSettings(), // - isolate_snapshot_, // - task_runners_, // - std::make_unique(this), // - snapshot_delegate_, // - hint_freed_delegate_, // - io_manager_, // - unref_queue_, // - image_decoder_, // - p_advisory_script_uri, // - p_advisory_script_entrypoint, // - nullptr, // - isolate_create_callback_, // - isolate_shutdown_callback_ // - ) - .lock(); - - FML_CHECK(strong_root_isolate) << "Could not create root isolate."; - - // The root isolate ivar is weak. - root_isolate_ = strong_root_isolate; - - strong_root_isolate->SetReturnCodeCallback([this](uint32_t code) { - root_isolate_return_code_ = {true, code}; - }); - - if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - tonic::DartState::Scope scope(strong_root_isolate); - platform_configuration->DidCreateIsolate(); - if (!FlushRuntimeStateToIsolate()) { - FML_DLOG(ERROR) << "Could not setup initial isolate state."; - } - } else { - FML_DCHECK(false) << "RuntimeController created without window binding."; - } - - FML_DCHECK(Dart_CurrentIsolate() == nullptr); + create_and_config_root_isolate_ = + std::async(std::launch::deferred, [self = weak_factory_.GetWeakPtr()]() { + if (!self) { + return; + } + + auto strong_root_isolate = + DartIsolate::CreateRootIsolate( + self->vm_->GetVMData()->GetSettings(), // + self->isolate_snapshot_, // + self->task_runners_, // + std::make_unique(self.get()), // + self->snapshot_delegate_, // + self->hint_freed_delegate_, // + self->io_manager_, // + self->unref_queue_, // + self->image_decoder_, // + self->advisory_script_uri_, // + self->advisory_script_entrypoint_, // + nullptr, // + self->isolate_create_callback_, // + self->isolate_shutdown_callback_ // + ) + .lock(); + + FML_CHECK(strong_root_isolate) << "Could not create root isolate."; + + // The root isolate ivar is weak. + self->root_isolate_ = strong_root_isolate; + + strong_root_isolate->SetReturnCodeCallback([self](uint32_t code) { + if (!self) { + return; + } + + self->root_isolate_return_code_ = {true, code}; + }); + + if (auto* platform_configuration = + self->GetPlatformConfigurationIfAvailable()) { + tonic::DartState::Scope scope(strong_root_isolate); + platform_configuration->DidCreateIsolate(); + if (!self->FlushRuntimeStateToIsolate()) { + FML_DLOG(ERROR) << "Could not setup initial isolate state."; + } + } else { + FML_DCHECK(false) + << "RuntimeController created without window binding."; + } + + FML_DCHECK(Dart_CurrentIsolate() == nullptr); + + self->client_.OnRootIsolateCreated(); + return; + }); + + // We're still trying to create the root isolate as soon as possible here on + // the UI thread although it's deferred a little bit by + // std::async(std::launch::deferred, ...). So the callers of `GetRootIsolate` + // should get a quick return after this UI thread task. + task_runners_.GetUITaskRunner()->PostTask( + [self = weak_factory_.GetWeakPtr()]() { + if (!self) { + return; + } + + self->GetRootIsolate(); + }); } RuntimeController::~RuntimeController() { @@ -110,8 +145,8 @@ RuntimeController::~RuntimeController() { } } -bool RuntimeController::IsRootIsolateRunning() const { - std::shared_ptr root_isolate = root_isolate_.lock(); +bool RuntimeController::IsRootIsolateRunning() { + std::shared_ptr root_isolate = GetRootIsolate().lock(); if (root_isolate) { return root_isolate->GetPhase() == DartIsolate::Phase::Running; } @@ -238,7 +273,7 @@ bool RuntimeController::ReportTimings(std::vector timings) { } bool RuntimeController::NotifyIdle(int64_t deadline, size_t freed_hint) { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); if (!root_isolate) { return false; } @@ -298,7 +333,7 @@ bool RuntimeController::DispatchSemanticsAction(int32_t id, PlatformConfiguration* RuntimeController::GetPlatformConfigurationIfAvailable() { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); return root_isolate ? root_isolate->platform_configuration() : nullptr; } @@ -360,17 +395,17 @@ RuntimeController::ComputePlatformResolvedLocale( } Dart_Port RuntimeController::GetMainPort() { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); return root_isolate ? root_isolate->main_port() : ILLEGAL_PORT; } std::string RuntimeController::GetIsolateName() { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); return root_isolate ? root_isolate->debug_name() : ""; } bool RuntimeController::HasLivePorts() { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); if (!root_isolate) { return false; } @@ -379,11 +414,20 @@ bool RuntimeController::HasLivePorts() { } tonic::DartErrorHandleType RuntimeController::GetLastError() { - std::shared_ptr root_isolate = root_isolate_.lock(); + std::shared_ptr root_isolate = GetRootIsolate().lock(); return root_isolate ? root_isolate->GetLastError() : tonic::kNoError; } std::weak_ptr RuntimeController::GetRootIsolate() { + std::shared_ptr root_isolate = root_isolate_.lock(); + if (root_isolate) { + return root_isolate_; + } + + // Root isolate is not yet created, get it and do some configuration. + FML_DCHECK(create_and_config_root_isolate_.valid()); + create_and_config_root_isolate_.get(); + return root_isolate_; } diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 89ed112055b74..20e7abc532984 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_ #define FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_ +#include #include #include @@ -348,7 +349,7 @@ class RuntimeController : public PlatformConfigurationClient { /// /// @return True if root isolate running, False otherwise. /// - virtual bool IsRootIsolateRunning() const; + virtual bool IsRootIsolateRunning(); //---------------------------------------------------------------------------- /// @brief Dispatch the specified platform message to running root @@ -430,7 +431,10 @@ class RuntimeController : public PlatformConfigurationClient { /// @brief Get a weak pointer to the root Dart isolate. This isolate may /// only be locked on the UI task runner. Callers use this /// accessor to transition to the root isolate to the running - /// phase. + /// phase. Note that it might take times if the isolate is not yet + /// created, which should be done in a subsequence task after + /// constructing `RuntimeController`, or it should get a quick + /// return otherwise. /// /// @return The root isolate reference. /// @@ -480,11 +484,16 @@ class RuntimeController : public PlatformConfigurationClient { std::string advisory_script_entrypoint_; std::function idle_notification_callback_; PlatformData platform_data_; + std::future create_and_config_root_isolate_; + // Note that `root_isolate_` is created asynchronously from the constructor of + // `RuntimeController`, be careful to use it directly while it might have not + // been created yet. Call `GetRootIsolate()` instead which guarantees that. std::weak_ptr root_isolate_; std::pair root_isolate_return_code_ = {false, 0}; const fml::closure isolate_create_callback_; const fml::closure isolate_shutdown_callback_; std::shared_ptr persistent_isolate_data_; + fml::WeakPtrFactory weak_factory_; PlatformConfiguration* GetPlatformConfigurationIfAvailable(); diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 20059827b8150..55978b4dbc39f 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -32,6 +32,8 @@ class RuntimeDelegate { virtual FontCollection& GetFontCollection() = 0; + virtual void OnRootIsolateCreated() = 0; + virtual void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) = 0; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 348e69a00a9aa..310ebc4ce925b 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -501,6 +501,10 @@ void Engine::HandlePlatformMessage(fml::RefPtr message) { } } +void Engine::OnRootIsolateCreated() { + delegate_.OnRootIsolateCreated(); +} + void Engine::UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) { delegate_.UpdateIsolateDescription(isolate_name, isolate_port); diff --git a/shell/common/engine.h b/shell/common/engine.h index 81771c00935ab..b35a7ff85ab07 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -189,6 +189,15 @@ class Engine final : public RuntimeDelegate, /// virtual void OnPreEngineRestart() = 0; + //-------------------------------------------------------------------------- + /// @brief Notifies the shell that the root isolate is created. + /// Currently, this information is to add to the service + /// protocol list of available root isolates running in the VM + /// and their names so that the appropriate isolate can be + /// selected in the tools for debugging and instrumentation. + /// + virtual void OnRootIsolateCreated() = 0; + //-------------------------------------------------------------------------- /// @brief Notifies the shell of the name of the root isolate and its /// port when that isolate is launched, restarted (in the @@ -800,6 +809,9 @@ class Engine final : public RuntimeDelegate, // |RuntimeDelegate| void HandlePlatformMessage(fml::RefPtr message) override; + // |RuntimeDelegate| + void OnRootIsolateCreated() override; + // |RuntimeDelegate| void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) override; diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 3390507f68d9b..4ce3c6d6d7aad 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -27,6 +27,7 @@ class MockDelegate : public Engine::Delegate { MOCK_METHOD1(OnEngineHandlePlatformMessage, void(fml::RefPtr)); MOCK_METHOD0(OnPreEngineRestart, void()); + MOCK_METHOD0(OnRootIsolateCreated, void()); MOCK_METHOD2(UpdateIsolateDescription, void(const std::string, int64_t)); MOCK_METHOD1(SetNeedsReportTimings, void(bool)); MOCK_METHOD1(ComputePlatformResolvedLocale, @@ -49,6 +50,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { void(SemanticsNodeUpdates, CustomAccessibilityActionUpdates)); MOCK_METHOD1(HandlePlatformMessage, void(fml::RefPtr)); MOCK_METHOD0(GetFontCollection, FontCollection&()); + MOCK_METHOD0(OnRootIsolateCreated, void()); MOCK_METHOD2(UpdateIsolateDescription, void(const std::string, int64_t)); MOCK_METHOD1(SetNeedsReportTimings, void(bool)); MOCK_METHOD1(ComputePlatformResolvedLocale, @@ -60,7 +62,7 @@ class MockRuntimeController : public RuntimeController { public: MockRuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners) : RuntimeController(client, p_task_runners) {} - MOCK_CONST_METHOD0(IsRootIsolateRunning, bool()); + MOCK_METHOD0(IsRootIsolateRunning, bool()); MOCK_METHOD1(DispatchPlatformMessage, bool(fml::RefPtr)); }; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index c250ca25a8552..787d69b51653d 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -564,8 +564,6 @@ bool Shell::Setup(std::unique_ptr platform_view, is_setup_ = true; - vm_->GetServiceProtocol()->AddHandler(this, GetServiceProtocolDescription()); - PersistentCache::GetCacheForProcess()->AddWorkerTaskRunner( task_runners_.GetIOTaskRunner()); @@ -1150,6 +1148,23 @@ void Shell::OnPreEngineRestart() { latch.Wait(); } +// |Engine::Delegate| +void Shell::OnRootIsolateCreated() { + if (is_added_to_service_protocol_) { + return; + } + auto description = GetServiceProtocolDescription(); + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetPlatformTaskRunner(), + [self = weak_factory_.GetWeakPtr(), + description = std::move(description)]() { + if (self) { + self->vm_->GetServiceProtocol()->AddHandler(self.get(), description); + } + }); + is_added_to_service_protocol_ = true; +} + // |Engine::Delegate| void Shell::UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) { @@ -1295,9 +1310,15 @@ bool Shell::HandleServiceProtocolMessage( // |ServiceProtocol::Handler| ServiceProtocol::Handler::Description Shell::GetServiceProtocolDescription() const { + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + if (!weak_engine_) { + return ServiceProtocol::Handler::Description(); + } + return { - engine_->GetUIIsolateMainPort(), - engine_->GetUIIsolateName(), + weak_engine_->GetUIIsolateMainPort(), + weak_engine_->GetUIIsolateName(), }; } diff --git a/shell/common/shell.h b/shell/common/shell.h index a345b0461e5fc..8e8fd18b48b3b 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -411,6 +411,7 @@ class Shell final : public PlatformView::Delegate, > service_protocol_handlers_; bool is_setup_ = false; + bool is_added_to_service_protocol_ = false; uint64_t next_pointer_flow_id_ = 0; bool first_frame_rasterized_ = false; @@ -537,6 +538,9 @@ class Shell final : public PlatformView::Delegate, // |Engine::Delegate| void OnPreEngineRestart() override; + // |Engine::Delegate| + void OnRootIsolateCreated() override; + // |Engine::Delegate| void UpdateIsolateDescription(const std::string isolate_name, int64_t isolate_port) override; From 10ed563183a8c435b747bb8d7a5e85d8c5588584 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 17:12:02 -0400 Subject: [PATCH 074/219] Roll Skia from 9213e610ed92 to 840e8ea7403e (11 revisions) (#21779) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 241ea5420be14..58903a017abab 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '9213e610ed92422d0b39dc4ff12303ca037131d2', + 'skia_revision': '840e8ea7403ecf2385e6b009f78d83d9d413f5fd', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index c73c8d7923307..dadb11af1824b 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: dd91721a42021f83633b4bd9f44efac5 +Signature: 53857a7e3f9670e277f3f12a883a3a55 UNUSED LICENSES: From e18cd968786d4a394d9aea8e8d6cee7762e8c1a7 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 12 Oct 2020 19:02:02 -0400 Subject: [PATCH 075/219] Roll Skia from 840e8ea7403e to ab6e62c131e9 (7 revisions) (#21783) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 58903a017abab..cf8f5fed06b0b 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '840e8ea7403ecf2385e6b009f78d83d9d413f5fd', + 'skia_revision': 'ab6e62c131e977abfb90c0b64a7efb63d44b93bb', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index dadb11af1824b..37fc3bf428f2c 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 53857a7e3f9670e277f3f12a883a3a55 +Signature: 4497174024f9e23872a78ab6764ef421 UNUSED LICENSES: From 931a04683d6eb49fc92059b2384ac5b1618d5422 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 12 Oct 2020 16:04:05 -0700 Subject: [PATCH 076/219] Fix documentation build for window changes. (#21780) * Fix documentation build for window changes. * Add missing interfaces for web_ui --- lib/ui/window.dart | 239 +++++++++++++++++++++++++++--- lib/web_ui/lib/src/ui/window.dart | 132 ++++++++++++++++- 2 files changed, 353 insertions(+), 18 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 910866379d814..a2372728346a7 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -125,7 +125,7 @@ abstract class FlutterView { /// system UI. The [padding] and [viewInsets] properties provide information /// about how much of each side of the view may be obscured by system UI. /// - /// This value is the same as [physicalGeometry.size]. + /// This value is the same as the `size` member of [physicalGeometry]. /// /// See also: /// @@ -309,10 +309,10 @@ class SingletonFlutterWindow extends FlutterWindow { : super._(windowId, platformDispatcher); /// A callback that is invoked whenever the [devicePixelRatio], - /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], - /// [PlatformDispatcher.screens], or [systemGestureInsets] values change. + /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], or + /// [systemGestureInsets] values change. /// - /// {@template flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// See [PlatformDispatcher.onMetricsChanged] for more information. VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; @@ -374,7 +374,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked whenever [locale] changes value. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -424,7 +424,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked whenever [textScaleFactor] changes value. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -448,7 +448,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked whenever [platformBrightness] changes value. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -466,7 +466,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// time to provide a scene using the [SceneBuilder] API and the [render] /// method. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// When possible, this is driven by the hardware VSync signal. This is only /// called if [scheduleFrame] has been called since the last time this @@ -493,7 +493,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked for each frame after [onBeginFrame] has /// completed and after the microtask queue has been drained. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// This can be used to implement a second phase of frame rendering that /// happens after any deferred work queued by the [onBeginFrame] phase. @@ -515,10 +515,10 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked to report the [FrameTiming] of recently /// rasterized frames. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use - /// [FlutterWindow.onReportTimings] directly because + /// [SingletonFlutterWindow.onReportTimings] directly because /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. /// /// This can be used to see if the window has missed frames (through @@ -542,7 +542,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked when pointer data is available. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -615,7 +615,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked when the value of [semanticsEnabled] changes. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -627,7 +627,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked whenever the user requests an action to be /// performed. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// This callback is used when the user expresses the action they wish to /// perform based on the semantics supplied by [updateSemantics]. @@ -644,7 +644,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// A callback that is invoked when the value of [accessibilityFeatures] changes. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -684,7 +684,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// Called whenever this window receives a message from a platform-specific /// plugin. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// The `name` parameter determines which plugin sent the message. The `data` /// parameter is the payload and is typically UTF-8 encoded JSON but can be @@ -704,7 +704,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// Set the debug name associated with this platform dispatcher's root /// isolate. /// - /// {@macro flutter.lib.ui.window.forwardWarning} + /// {@macro flutter.lib.ui.window.accessorForwardWarning} /// /// Normally debug names are automatically generated from the Dart port, entry /// point, and source file. For example: `main.dart$main-1234`. @@ -798,9 +798,214 @@ class AccessibilityFeatures { /// [SingletonFlutterWindow]. /// /// This class will be removed once the framework no longer refers to it. +// In order for the documentation build to succeed, this interface duplicates +// all of the methods with documentation, overrides them, and calls the super +// implementation. Once this merges into the framework and the framework +// references to it can be updated, this class will be removed entirely. class Window extends SingletonFlutterWindow { Window._(Object windowId, PlatformDispatcher platformDispatcher) : super._(windowId, platformDispatcher); + + @override + // ignore: unnecessary_overrides + double get devicePixelRatio => super.devicePixelRatio; + + @override + // ignore: unnecessary_overrides + Rect get physicalGeometry => super.physicalGeometry; + + @override + // ignore: unnecessary_overrides + Size get physicalSize => super.physicalSize; + + @override + // ignore: unnecessary_overrides + WindowPadding get viewInsets => super.viewInsets; + + @override + // ignore: unnecessary_overrides + WindowPadding get viewPadding => super.viewPadding; + + @override + // ignore: unnecessary_overrides + WindowPadding get systemGestureInsets => super.systemGestureInsets; + + @override + // ignore: unnecessary_overrides + WindowPadding get padding => super.padding; + + @override + // ignore: unnecessary_overrides + void render(Scene scene) => super.render(scene); + + @override + // ignore: unnecessary_overrides + VoidCallback? get onMetricsChanged => super.onMetricsChanged; + @override + // ignore: unnecessary_overrides + set onMetricsChanged(VoidCallback? callback) { + super.onMetricsChanged = callback; + } + + @override + // ignore: unnecessary_overrides + Locale? get locale => super.locale; + + @override + // ignore: unnecessary_overrides + List? get locales => super.locales; + + @override + // ignore: unnecessary_overrides + Locale? computePlatformResolvedLocale(List supportedLocales) { + return super.computePlatformResolvedLocale(supportedLocales); + } + + @override + // ignore: unnecessary_overrides + VoidCallback? get onLocaleChanged => super.onLocaleChanged; + @override + // ignore: unnecessary_overrides + set onLocaleChanged(VoidCallback? callback) { + super.onLocaleChanged = callback; + } + + @override + // ignore: unnecessary_overrides + String get initialLifecycleState => super.initialLifecycleState; + + @override + // ignore: unnecessary_overrides + double get textScaleFactor => super.textScaleFactor; + + @override + // ignore: unnecessary_overrides + bool get alwaysUse24HourFormat => super.alwaysUse24HourFormat; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onTextScaleFactorChanged => super.onTextScaleFactorChanged; + @override + // ignore: unnecessary_overrides + set onTextScaleFactorChanged(VoidCallback? callback) { + super.onTextScaleFactorChanged = callback; + } + + @override + // ignore: unnecessary_overrides + Brightness get platformBrightness => super.platformBrightness; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onPlatformBrightnessChanged => super.onPlatformBrightnessChanged; + @override + // ignore: unnecessary_overrides + set onPlatformBrightnessChanged(VoidCallback? callback) { + super.onPlatformBrightnessChanged = callback; + } + + @override + // ignore: unnecessary_overrides + FrameCallback? get onBeginFrame => super.onBeginFrame; + @override + // ignore: unnecessary_overrides + set onBeginFrame(FrameCallback? callback) { + super.onBeginFrame = callback; + } + + @override + // ignore: unnecessary_overrides + VoidCallback? get onDrawFrame => super.onDrawFrame; + @override + // ignore: unnecessary_overrides + set onDrawFrame(VoidCallback? callback) { + super.onDrawFrame = callback; + } + + @override + // ignore: unnecessary_overrides + TimingsCallback? get onReportTimings => super.onReportTimings; + @override + // ignore: unnecessary_overrides + set onReportTimings(TimingsCallback? callback) { + super.onReportTimings = callback; + } + + @override + // ignore: unnecessary_overrides + PointerDataPacketCallback? get onPointerDataPacket => super.onPointerDataPacket; + @override + // ignore: unnecessary_overrides + set onPointerDataPacket(PointerDataPacketCallback? callback) { + super.onPointerDataPacket = callback; + } + + @override + // ignore: unnecessary_overrides + String get defaultRouteName => super.defaultRouteName; + + @override + // ignore: unnecessary_overrides + void scheduleFrame() => super.scheduleFrame(); + + @override + // ignore: unnecessary_overrides + bool get semanticsEnabled => super.semanticsEnabled; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onSemanticsEnabledChanged => super.onSemanticsEnabledChanged; + @override + // ignore: unnecessary_overrides + set onSemanticsEnabledChanged(VoidCallback? callback) { + super.onSemanticsEnabledChanged = callback; + } + + @override + // ignore: unnecessary_overrides + SemanticsActionCallback? get onSemanticsAction => super.onSemanticsAction; + @override + // ignore: unnecessary_overrides + set onSemanticsAction(SemanticsActionCallback? callback) { + super.onSemanticsAction = callback; + } + + @override + // ignore: unnecessary_overrides + AccessibilityFeatures get accessibilityFeatures => super.accessibilityFeatures; + + @override + // ignore: unnecessary_overrides + VoidCallback? get onAccessibilityFeaturesChanged => + super.onAccessibilityFeaturesChanged; + @override + // ignore: unnecessary_overrides + set onAccessibilityFeaturesChanged(VoidCallback? callback) { + super.onAccessibilityFeaturesChanged = callback; + } + + @override + // ignore: unnecessary_overrides + void updateSemantics(SemanticsUpdate update) => super.updateSemantics(update); + + @override + // ignore: unnecessary_overrides + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { + super.sendPlatformMessage(name, data, callback); + } + + @override + // ignore: unnecessary_overrides + PlatformMessageCallback? get onPlatformMessage => super.onPlatformMessage; + @override + // ignore: unnecessary_overrides + set onPlatformMessage(PlatformMessageCallback? callback) { + super.onPlatformMessage = callback; + } + + @override + // ignore: unnecessary_overrides + void setIsolateDebugName(String name) => super.setIsolateDebugName(name); } /// Describes the contrast of a theme or color palette. diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index cc7a45dcc65db..d3519f19b13d0 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -124,7 +124,137 @@ abstract class SingletonFlutterWindow extends FlutterWindow { void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } -abstract class Window extends SingletonFlutterWindow {} +// This class will go away entirely once references to it are removed from the +// framework. The many explicit overrides are an artifact of needing to add the +// same overrides to the one in dart:ui in order to get dartdoc to find the docs +// for them. +abstract class Window extends SingletonFlutterWindow { + @override + double get devicePixelRatio; + + @override + Rect get physicalGeometry; + + @override + Size get physicalSize; + + @override + WindowPadding get viewInsets; + + @override + WindowPadding get viewPadding; + + @override + WindowPadding get systemGestureInsets; + + @override + WindowPadding get padding; + + @override + void render(Scene scene); + + @override + VoidCallback? get onMetricsChanged; + @override + set onMetricsChanged(VoidCallback? callback); + + @override + Locale? get locale => super.locale; + + @override + List? get locales => super.locales; + + @override + Locale? computePlatformResolvedLocale(List supportedLocales); + + @override + VoidCallback? get onLocaleChanged; + @override + set onLocaleChanged(VoidCallback? callback); + + @override + String get initialLifecycleState; + + @override + double get textScaleFactor; + + @override + bool get alwaysUse24HourFormat; + + @override + VoidCallback? get onTextScaleFactorChanged; + @override + set onTextScaleFactorChanged(VoidCallback? callback); + + @override + Brightness get platformBrightness; + + @override + VoidCallback? get onPlatformBrightnessChanged; + @override + set onPlatformBrightnessChanged(VoidCallback? callback); + + @override + FrameCallback? get onBeginFrame; + @override + set onBeginFrame(FrameCallback? callback); + + @override + VoidCallback? get onDrawFrame; + @override + set onDrawFrame(VoidCallback? callback); + + @override + TimingsCallback? get onReportTimings; + @override + set onReportTimings(TimingsCallback? callback); + + @override + PointerDataPacketCallback? get onPointerDataPacket; + @override + set onPointerDataPacket(PointerDataPacketCallback? callback); + + @override + String get defaultRouteName; + + @override + void scheduleFrame(); + + @override + bool get semanticsEnabled; + + @override + VoidCallback? get onSemanticsEnabledChanged; + @override + set onSemanticsEnabledChanged(VoidCallback? callback); + + @override + SemanticsActionCallback? get onSemanticsAction; + @override + set onSemanticsAction(SemanticsActionCallback? callback); + + @override + AccessibilityFeatures get accessibilityFeatures; + + @override + VoidCallback? get onAccessibilityFeaturesChanged; + @override + set onAccessibilityFeaturesChanged(VoidCallback? callback); + + @override + void updateSemantics(SemanticsUpdate update); + + @override + void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback); + + @override + PlatformMessageCallback? get onPlatformMessage; + @override + set onPlatformMessage(PlatformMessageCallback? callback); + + @override + void setIsolateDebugName(String name); +} class AccessibilityFeatures { const AccessibilityFeatures._(this._index); From 38d8ebaa46cc5f0cc1828d7fbd7c7019592f9524 Mon Sep 17 00:00:00 2001 From: nturgut Date: Mon, 12 Oct 2020 16:25:57 -0700 Subject: [PATCH 077/219] E2e screenshot tests2 (#21383) * carrying code * more changes for carrying the code * rebase changes onto ios-screenshot tests * adding screenshot capability to text_editing e2e test * address some comments * change enable flag for isUnitTestsScreenshotsAvailable * addressing the reviewer comments * change the dependency for path * add to licencense file * changing goldens commit no. the new commit has the screenshot goldens * update readme file * firefox tests needs LUCI changes * change to release mode since screenshots were taken in release mode * change window size * some argument changes * small comment change * test the chrome linux tests again * use roboto font instead of default font * addressing reviewer comments * change commit for goldens --- ci/licenses_golden/licenses_flutter | 4 + .../web/regular_integration_tests/README.md | 31 +++ .../fonts/RobotoMono-Bold.ttf | Bin 0 -> 87008 bytes .../fonts/RobotoMono-Regular.ttf | Bin 0 -> 86908 bytes .../lib/screenshot_support.dart | 96 ++++++++ .../lib/text_editing_main.dart | 6 +- .../regular_integration_tests/pubspec.yaml | 8 +- .../test_driver/text_editing_integration.dart | 4 +- .../text_editing_integration_test.dart | 6 +- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/dev/integration_tests_manager.dart | 14 +- lib/web_ui/dev/test_platform.dart | 144 +----------- lib/web_ui/dev/test_runner.dart | 38 +++- lib/web_ui/pubspec.yaml | 4 +- web_sdk/web_test_utils/lib/environment.dart | 214 ++++++++++++++++++ web_sdk/web_test_utils/lib/exceptions.dart | 30 +++ .../web_test_utils/lib}/goldens.dart | 122 ++++++---- web_sdk/web_test_utils/lib/image_compare.dart | 141 ++++++++++++ web_sdk/web_test_utils/pubspec.yaml | 10 + 19 files changed, 670 insertions(+), 204 deletions(-) create mode 100644 e2etests/web/regular_integration_tests/fonts/RobotoMono-Bold.ttf create mode 100644 e2etests/web/regular_integration_tests/fonts/RobotoMono-Regular.ttf create mode 100644 e2etests/web/regular_integration_tests/lib/screenshot_support.dart create mode 100644 web_sdk/web_test_utils/lib/environment.dart create mode 100644 web_sdk/web_test_utils/lib/exceptions.dart rename {lib/web_ui/dev => web_sdk/web_test_utils/lib}/goldens.dart (68%) create mode 100644 web_sdk/web_test_utils/lib/image_compare.dart create mode 100644 web_sdk/web_test_utils/pubspec.yaml diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 771fd9a20ca8a..d987a83d8abf8 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1514,6 +1514,10 @@ FILE: ../../../flutter/vulkan/vulkan_window.cc FILE: ../../../flutter/vulkan/vulkan_window.h FILE: ../../../flutter/web_sdk/libraries.json FILE: ../../../flutter/web_sdk/sdk_rewriter.dart +FILE: ../../../flutter/web_sdk/web_test_utils/lib/environment.dart +FILE: ../../../flutter/web_sdk/web_test_utils/lib/exceptions.dart +FILE: ../../../flutter/web_sdk/web_test_utils/lib/goldens.dart +FILE: ../../../flutter/web_sdk/web_test_utils/lib/image_compare.dart ---------------------------------------------------------------------------------------------------- Copyright 2013 The Flutter Authors. All rights reserved. diff --git a/e2etests/web/regular_integration_tests/README.md b/e2etests/web/regular_integration_tests/README.md index ba7f0c6c735d2..2e0aa9616c692 100644 --- a/e2etests/web/regular_integration_tests/README.md +++ b/e2etests/web/regular_integration_tests/README.md @@ -37,3 +37,34 @@ flutter drive -v --target=test_driver/text_editing_integration.dart -d web-serve ``` More details for "Running Flutter Driver tests with Web" can be found in [wiki](https://github.com/flutter/flutter/wiki/Running-Flutter-Driver-tests-with-Web). + +## Adding screenshot tests + +In order to test screenshot tests the tests on the driver side needs to call the `integration_test` package with an `onScreenshot` callback which can do a comparison between the `screenshotBytes` taken during the test and a golden file. We added a utility method that can do this comparison by using a golden in `flutter/goldens` repository. + +In order to use screenshot testing first, import `screenshot_support.dart` from the driver side test (example: `text_editing_integration_test.dart`). Default value for `diffRateFailure` is 0.5. + +``` +import 'package:regular_integration_tests/screenshot_support.dart' as test; + +Future main() async { + final double kMaxDiffRateFailure = 0.1; + await test.runTestWithScreenshots(diffRateFailure = kMaxDiffRateFailure); +} +``` + +In order to run the tests follow these steps: + +1. You can use two different approaches, using [felt](https://github.com/flutter/engine/blob/master/lib/web_ui/dev/README.md) tool will run all the tests, an update all the goldens. For running individual tests, we need to set UPDATE_GOLDENS environment variable. + +``` +felt test --integration-tests-only --update-screenshot-goldens +``` + +``` +UPDATE_GOLDENS=true flutter drive -v --target=test_driver/text_editing_integration.dart -d web-server --release --local-engine=host_debug_unopt +``` + +2. The golden will be under `engine/src/flutter/lib/web_ui/.dart_tool/goldens/engine/web/` directory, you should create a PR for that file and merge it to `flutter/goldens`. + +3. Get the commit SHA and replace the `revision` in this file: `engine/src/flutter/lib/web_ui/dev/goldens_lock.yaml` diff --git a/e2etests/web/regular_integration_tests/fonts/RobotoMono-Bold.ttf b/e2etests/web/regular_integration_tests/fonts/RobotoMono-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..900fce6848210593ee6aa630ce040a8424c23fd4 GIT binary patch literal 87008 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

The engine calls {@code SurfaceTexture.release} on the platform thread, but {@code + * updateTexImage} is called on the raster thread. This wrapper will prevent {@code updateTexImage} + * calls on an abandoned texture. + */ +@Keep +public class SurfaceTextureWrapper { + private SurfaceTexture surfaceTexture; + private boolean released; + + public SurfaceTextureWrapper(@NonNull SurfaceTexture surfaceTexture) { + this.surfaceTexture = surfaceTexture; + this.released = false; + } + + @NonNull + public SurfaceTexture surfaceTexture() { + return surfaceTexture; + } + + // Called by native. + @SuppressWarnings("unused") + public void updateTexImage() { + synchronized (this) { + if (!released) { + surfaceTexture.updateTexImage(); + } + } + } + + public void release() { + synchronized (this) { + if (!released) { + surfaceTexture.release(); + released = true; + } + } + } + + // Called by native. + @SuppressWarnings("unused") + public void attachToGLContext(int texName) { + surfaceTexture.attachToGLContext(texName); + } + + // Called by native. + @SuppressWarnings("unused") + public void detachFromGLContext() { + surfaceTexture.detachFromGLContext(); + } + + // Called by native. + @SuppressWarnings("unused") + public void getTransformMatrix(float[] mtx) { + surfaceTexture.getTransformMatrix(mtx); + } +} diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 50598cd7344f4..b630ad242a51f 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -46,6 +46,7 @@ import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; @@ -876,18 +877,18 @@ public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); - mNativeView.getFlutterJNI().registerTexture(entry.id(), surfaceTexture); + mNativeView.getFlutterJNI().registerTexture(entry.id(), entry.textureWrapper()); return entry; } final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { private final long id; - private final SurfaceTexture surfaceTexture; + private final SurfaceTextureWrapper textureWrapper; private boolean released; SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { this.id = id; - this.surfaceTexture = surfaceTexture; + this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -895,12 +896,12 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur // and also the engine code check for platform thread in // Shell::OnPlatformViewMarkTextureFrameAvailable), // so we explicitly pass a Handler for the current thread. - this.surfaceTexture.setOnFrameAvailableListener(onFrameListener, new Handler()); + this.surfaceTexture().setOnFrameAvailableListener(onFrameListener, new Handler()); } else { // Android documentation states that the listener can be called on an arbitrary thread. // But in practice, versions of Android that predate the newer API will call the listener // on the thread where the SurfaceTexture was constructed. - this.surfaceTexture.setOnFrameAvailableListener(onFrameListener); + this.surfaceTexture().setOnFrameAvailableListener(onFrameListener); } } @@ -921,9 +922,13 @@ public void onFrameAvailable(SurfaceTexture texture) { } }; + public SurfaceTextureWrapper textureWrapper() { + return textureWrapper; + } + @Override public SurfaceTexture surfaceTexture() { - return surfaceTexture; + return textureWrapper.surfaceTexture(); } @Override @@ -945,8 +950,8 @@ public void release() { // Otherwise onFrameAvailableListener might be called after mNativeView==null // (https://github.com/flutter/flutter/issues/20951). See also the check in onFrameAvailable. - surfaceTexture.setOnFrameAvailableListener(null); - surfaceTexture.release(); + surfaceTexture().setOnFrameAvailableListener(null); + textureWrapper.release(); mNativeView.getFlutterJNI().unregisterTexture(id); } } diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 3b8a61bf39c0c..b3b11dbb8ee3e 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -53,7 +53,7 @@ static fml::jni::ScopedJavaGlobalRef* g_flutter_callback_info_class = static fml::jni::ScopedJavaGlobalRef* g_flutter_jni_class = nullptr; -static fml::jni::ScopedJavaGlobalRef* g_surface_texture_class = nullptr; +static fml::jni::ScopedJavaGlobalRef* g_texture_wrapper_class = nullptr; // Called By Native @@ -613,7 +613,8 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeRegisterTexture", - .signature = "(JJLandroid/graphics/SurfaceTexture;)V", + .signature = "(JJLio/flutter/embedding/engine/renderer/" + "SurfaceTextureWrapper;)V", .fnPtr = reinterpret_cast(&RegisterTexture), }, { @@ -857,15 +858,16 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } - g_surface_texture_class = new fml::jni::ScopedJavaGlobalRef( - env, env->FindClass("android/graphics/SurfaceTexture")); - if (g_surface_texture_class->is_null()) { - FML_LOG(ERROR) << "Could not locate SurfaceTexture class"; + g_texture_wrapper_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass( + "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper")); + if (g_texture_wrapper_class->is_null()) { + FML_LOG(ERROR) << "Could not locate SurfaceTextureWrapper class"; return false; } g_attach_to_gl_context_method = env->GetMethodID( - g_surface_texture_class->obj(), "attachToGLContext", "(I)V"); + g_texture_wrapper_class->obj(), "attachToGLContext", "(I)V"); if (g_attach_to_gl_context_method == nullptr) { FML_LOG(ERROR) << "Could not locate attachToGlContext method"; @@ -873,7 +875,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { } g_update_tex_image_method = - env->GetMethodID(g_surface_texture_class->obj(), "updateTexImage", "()V"); + env->GetMethodID(g_texture_wrapper_class->obj(), "updateTexImage", "()V"); if (g_update_tex_image_method == nullptr) { FML_LOG(ERROR) << "Could not locate updateTexImage method"; @@ -881,7 +883,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { } g_get_transform_matrix_method = env->GetMethodID( - g_surface_texture_class->obj(), "getTransformMatrix", "([F)V"); + g_texture_wrapper_class->obj(), "getTransformMatrix", "([F)V"); if (g_get_transform_matrix_method == nullptr) { FML_LOG(ERROR) << "Could not locate getTransformMatrix method"; @@ -889,7 +891,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { } g_detach_from_gl_context_method = env->GetMethodID( - g_surface_texture_class->obj(), "detachFromGLContext", "()V"); + g_texture_wrapper_class->obj(), "detachFromGLContext", "()V"); if (g_detach_from_gl_context_method == nullptr) { FML_LOG(ERROR) << "Could not locate detachFromGlContext method"; diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index 39bdc950e5508..0e47fcf9f88ea 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -188,4 +188,26 @@ column="24"/> + + + + + + + + From 516cbaee8736cb4bbc9fdaa6af4ae9a2ec55c5cc Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Thu, 15 Oct 2020 11:06:08 +1300 Subject: [PATCH 113/219] Explicitly make the X connection for EGL. (#21831) Explicitly make the X connection for EGL. EGL can mistakenly choose the GBM backend when using EGL_DEFAULT_DISPLAY. Fixes https://github.com/flutter/flutter/issues/60429 --- shell/platform/linux/BUILD.gn | 2 ++ shell/platform/linux/config/BUILD.gn | 6 ++--- shell/platform/linux/fl_renderer_x11.cc | 33 +++++++++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 523ea1194899c..0512376aefe7c 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -129,6 +129,7 @@ source_set("flutter_linux") { "//flutter/shell/platform/linux/config:gtk", "//flutter/shell/platform/linux/config:egl", "//flutter/shell/platform/linux/config:wayland-egl", + "//flutter/shell/platform/linux/config:x11", "//third_party/khronos:khronos_headers", ] @@ -172,6 +173,7 @@ executable("flutter_linux_unittests") { configs += [ "//flutter/shell/platform/linux/config:gtk", "//flutter/shell/platform/linux/config:wayland-egl", + "//flutter/shell/platform/linux/config:x11", ] # Set flag to allow public headers to be directly included (library users should not do this) diff --git a/shell/platform/linux/config/BUILD.gn b/shell/platform/linux/config/BUILD.gn index 77402c5315b78..e8fd120e25d3b 100644 --- a/shell/platform/linux/config/BUILD.gn +++ b/shell/platform/linux/config/BUILD.gn @@ -5,10 +5,8 @@ import("//build/config/linux/pkg_config.gni") import("//flutter/shell/platform/glfw/config.gni") -if (build_glfw_shell) { - pkg_config("x11") { - packages = [ "x11" ] - } +pkg_config("x11") { + packages = [ "x11" ] } pkg_config("gtk") { diff --git a/shell/platform/linux/fl_renderer_x11.cc b/shell/platform/linux/fl_renderer_x11.cc index 2c646b16ca223..2e9d7c147996a 100644 --- a/shell/platform/linux/fl_renderer_x11.cc +++ b/shell/platform/linux/fl_renderer_x11.cc @@ -5,14 +5,30 @@ #include "fl_renderer_x11.h" #ifdef GDK_WINDOWING_X11 +#include + #include "flutter/shell/platform/linux/egl_utils.h" struct _FlRendererX11 { FlRenderer parent_instance; + + // Connection to the X server. + Display* display; }; G_DEFINE_TYPE(FlRendererX11, fl_renderer_x11, fl_renderer_get_type()) +static void fl_renderer_x11_dispose(GObject* object) { + FlRendererX11* self = FL_RENDERER_X11(object); + + if (self->display != nullptr) { + XCloseDisplay(self->display); + self->display = nullptr; + } + + G_OBJECT_CLASS(fl_renderer_x11_parent_class)->dispose(object); +} + // Implements FlRenderer::setup_window_attr. static gboolean fl_renderer_x11_setup_window_attr( FlRenderer* renderer, @@ -50,11 +66,17 @@ static gboolean fl_renderer_x11_setup_window_attr( // Implements FlRenderer::create_display. static EGLDisplay fl_renderer_x11_create_display(FlRenderer* renderer) { - // Note the use of EGL_DEFAULT_DISPLAY rather than sharing the existing - // display connection from GTK. This is because this EGL display is going to - // be accessed by a thread from Flutter. The GTK/X11 display connection is not - // thread safe and would cause a crash. - return eglGetDisplay(EGL_DEFAULT_DISPLAY); + FlRendererX11* self = FL_RENDERER_X11(renderer); + + // Create a dedicated connection to the X server because the EGL calls are + // made from Flutter on a different thread to GTK. Re-using the existing + // GTK X11 connection would crash as Xlib is not thread safe. + if (self->display == nullptr) { + Display* display = gdk_x11_get_default_xdisplay(); + self->display = XOpenDisplay(DisplayString(display)); + } + + return eglGetDisplay(self->display); } // Implements FlRenderer::create_surfaces. @@ -99,6 +121,7 @@ static gboolean fl_renderer_x11_create_surfaces(FlRenderer* renderer, } static void fl_renderer_x11_class_init(FlRendererX11Class* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_renderer_x11_dispose; FL_RENDERER_CLASS(klass)->setup_window_attr = fl_renderer_x11_setup_window_attr; FL_RENDERER_CLASS(klass)->create_display = fl_renderer_x11_create_display; From d2ea378be45dd0de7a86b521d48240fd9d276283 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 14 Oct 2020 16:30:40 -0700 Subject: [PATCH 114/219] Revert "Explicitly make the X connection for EGL. (#21831)" (#21851) This reverts commit 516cbaee8736cb4bbc9fdaa6af4ae9a2ec55c5cc. --- shell/platform/linux/BUILD.gn | 2 -- shell/platform/linux/config/BUILD.gn | 6 +++-- shell/platform/linux/fl_renderer_x11.cc | 33 ++++--------------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 0512376aefe7c..523ea1194899c 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -129,7 +129,6 @@ source_set("flutter_linux") { "//flutter/shell/platform/linux/config:gtk", "//flutter/shell/platform/linux/config:egl", "//flutter/shell/platform/linux/config:wayland-egl", - "//flutter/shell/platform/linux/config:x11", "//third_party/khronos:khronos_headers", ] @@ -173,7 +172,6 @@ executable("flutter_linux_unittests") { configs += [ "//flutter/shell/platform/linux/config:gtk", "//flutter/shell/platform/linux/config:wayland-egl", - "//flutter/shell/platform/linux/config:x11", ] # Set flag to allow public headers to be directly included (library users should not do this) diff --git a/shell/platform/linux/config/BUILD.gn b/shell/platform/linux/config/BUILD.gn index e8fd120e25d3b..77402c5315b78 100644 --- a/shell/platform/linux/config/BUILD.gn +++ b/shell/platform/linux/config/BUILD.gn @@ -5,8 +5,10 @@ import("//build/config/linux/pkg_config.gni") import("//flutter/shell/platform/glfw/config.gni") -pkg_config("x11") { - packages = [ "x11" ] +if (build_glfw_shell) { + pkg_config("x11") { + packages = [ "x11" ] + } } pkg_config("gtk") { diff --git a/shell/platform/linux/fl_renderer_x11.cc b/shell/platform/linux/fl_renderer_x11.cc index 2e9d7c147996a..2c646b16ca223 100644 --- a/shell/platform/linux/fl_renderer_x11.cc +++ b/shell/platform/linux/fl_renderer_x11.cc @@ -5,30 +5,14 @@ #include "fl_renderer_x11.h" #ifdef GDK_WINDOWING_X11 -#include - #include "flutter/shell/platform/linux/egl_utils.h" struct _FlRendererX11 { FlRenderer parent_instance; - - // Connection to the X server. - Display* display; }; G_DEFINE_TYPE(FlRendererX11, fl_renderer_x11, fl_renderer_get_type()) -static void fl_renderer_x11_dispose(GObject* object) { - FlRendererX11* self = FL_RENDERER_X11(object); - - if (self->display != nullptr) { - XCloseDisplay(self->display); - self->display = nullptr; - } - - G_OBJECT_CLASS(fl_renderer_x11_parent_class)->dispose(object); -} - // Implements FlRenderer::setup_window_attr. static gboolean fl_renderer_x11_setup_window_attr( FlRenderer* renderer, @@ -66,17 +50,11 @@ static gboolean fl_renderer_x11_setup_window_attr( // Implements FlRenderer::create_display. static EGLDisplay fl_renderer_x11_create_display(FlRenderer* renderer) { - FlRendererX11* self = FL_RENDERER_X11(renderer); - - // Create a dedicated connection to the X server because the EGL calls are - // made from Flutter on a different thread to GTK. Re-using the existing - // GTK X11 connection would crash as Xlib is not thread safe. - if (self->display == nullptr) { - Display* display = gdk_x11_get_default_xdisplay(); - self->display = XOpenDisplay(DisplayString(display)); - } - - return eglGetDisplay(self->display); + // Note the use of EGL_DEFAULT_DISPLAY rather than sharing the existing + // display connection from GTK. This is because this EGL display is going to + // be accessed by a thread from Flutter. The GTK/X11 display connection is not + // thread safe and would cause a crash. + return eglGetDisplay(EGL_DEFAULT_DISPLAY); } // Implements FlRenderer::create_surfaces. @@ -121,7 +99,6 @@ static gboolean fl_renderer_x11_create_surfaces(FlRenderer* renderer, } static void fl_renderer_x11_class_init(FlRendererX11Class* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_renderer_x11_dispose; FL_RENDERER_CLASS(klass)->setup_window_attr = fl_renderer_x11_setup_window_attr; FL_RENDERER_CLASS(klass)->create_display = fl_renderer_x11_create_display; From 913a244157e14f7b2c6406c39e4eb124a789d017 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 14 Oct 2020 20:22:01 -0400 Subject: [PATCH 115/219] Roll Dart SDK from 4226116043f5 to 04cf6ade9fc4 (4 revisions) (#21846) --- DEPS | 4 ++-- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 3febd9e347bb8..25da1315948d1 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '4226116043f5ea5301b11474e0a97706132c317b', + 'dart_revision': '04cf6ade9fc4fb960a6f43177ecc8b9a20805250', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -49,7 +49,7 @@ vars = { 'dart_linter_tag': '0.1.121', 'dart_oauth2_tag': '1.6.0', 'dart_protobuf_rev': '3746c8fd3f2b0147623a8e3db89c3ff4330de760', - 'dart_pub_rev': '04e237f78b2302d7f20d0b362554425e8deb8add', + 'dart_pub_rev': 'f0c7771b38155d3829a60d60b5dba2784b100811', 'dart_pub_semver_tag': 'v1.4.4', 'dart_quiver-dart_tag': '246e754fe45cecb6aa5f3f13b4ed61037ff0d784', 'dart_resource_rev': 'f8e37558a1c4f54550aa463b88a6a831e3e33cd6', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 6b4459f794f3f..8cc6ff53fe039 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: cfe1613fcec7f9ec3bc156a9992a9d2d +Signature: 5dc2e0ab64cbdb1da2ee2a1ab43ec2e4 UNUSED LICENSES: From b22809b084345daecb2c2160cba5c9de9637c3f8 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 14 Oct 2020 20:27:04 -0400 Subject: [PATCH 116/219] Roll Skia from 99446001182c to f4bda743ff8d (22 revisions) (#21848) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/DEPS b/DEPS index 25da1315948d1..d0f65b3a115bf 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '99446001182ceb7a4fa3c4fbcb908bde38185766', + 'skia_revision': 'f4bda743ff8d9cd10df0a771582aa8f25749eb57', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 22668e498dd57..78d7503d618a0 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 1e59d8eb25114e6b03e47e82d70c9e95 +Signature: 819d3bc919ec6336e402246f10c994a1 UNUSED LICENSES: @@ -435,7 +435,6 @@ FILE: ../../../third_party/skia/bench/StreamBench.cpp FILE: ../../../third_party/skia/bench/SwizzleBench.cpp FILE: ../../../third_party/skia/bench/TileImageFilterBench.cpp FILE: ../../../third_party/skia/bench/VertexColorSpaceBench.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkPEG.h FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttribute.cpp FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttribute.h FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttributeParser.cpp @@ -619,7 +618,7 @@ FILE: ../../../third_party/skia/src/gpu/GrClipStackClip.cpp FILE: ../../../third_party/skia/src/gpu/GrClipStackClip.h FILE: ../../../third_party/skia/src/gpu/GrColorSpaceXform.cpp FILE: ../../../third_party/skia/src/gpu/GrColorSpaceXform.h -FILE: ../../../third_party/skia/src/gpu/GrContextPriv.h +FILE: ../../../third_party/skia/src/gpu/GrDirectContextPriv.h FILE: ../../../third_party/skia/src/gpu/GrFixedClip.h FILE: ../../../third_party/skia/src/gpu/GrImageTextureMaker.cpp FILE: ../../../third_party/skia/src/gpu/GrImageTextureMaker.h @@ -2918,7 +2917,7 @@ FILE: ../../../third_party/skia/modules/particles/src/SkParticleEffect.cpp FILE: ../../../third_party/skia/modules/particles/src/SkReflected.cpp FILE: ../../../third_party/skia/modules/skresources/include/SkResources.h FILE: ../../../third_party/skia/modules/skresources/src/SkResources.cpp -FILE: ../../../third_party/skia/samplecode/SampleBackdropBounds.cpp +FILE: ../../../third_party/skia/samplecode/SampleFilterBounds.cpp FILE: ../../../third_party/skia/samplecode/SampleImageFilterDAG.cpp FILE: ../../../third_party/skia/src/core/SkImageFilterTypes.cpp FILE: ../../../third_party/skia/src/core/SkImageFilterTypes.h @@ -3115,12 +3114,12 @@ FILE: ../../../third_party/skia/src/gpu/GrAHardwareBufferUtils.cpp FILE: ../../../third_party/skia/src/gpu/GrAHardwareBufferUtils.h FILE: ../../../third_party/skia/src/gpu/GrBaseContextPriv.h FILE: ../../../third_party/skia/src/gpu/GrBuffer.h -FILE: ../../../third_party/skia/src/gpu/GrContextPriv.cpp FILE: ../../../third_party/skia/src/gpu/GrContextThreadSafeProxy.cpp FILE: ../../../third_party/skia/src/gpu/GrContext_Base.cpp FILE: ../../../third_party/skia/src/gpu/GrCpuBuffer.h FILE: ../../../third_party/skia/src/gpu/GrDataUtils.cpp FILE: ../../../third_party/skia/src/gpu/GrDataUtils.h +FILE: ../../../third_party/skia/src/gpu/GrDirectContextPriv.cpp FILE: ../../../third_party/skia/src/gpu/GrGpuBuffer.cpp FILE: ../../../third_party/skia/src/gpu/GrGpuBuffer.h FILE: ../../../third_party/skia/src/gpu/GrImageContext.cpp From b715d3f21d66e284364d290b0bd050c76e021d85 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Oct 2020 09:54:06 -0700 Subject: [PATCH 117/219] Migrate TextInputPlugin API to TextRange (#21854) Replaces selection_base() and selection_extent() with selection() and SetSelection(int, int) with SetSelection(range). This also adds the following convenience methods to TextRange: * reversed() * Contains(size_t position) * Contains(const TextRange& range) as well as operator== for use in unit tests. When Flutter migrates to C++20, we can replace that method with a default declaration. --- shell/platform/common/cpp/text_input_model.cc | 7 +- shell/platform/common/cpp/text_input_model.h | 14 +- .../common/cpp/text_input_model_unittests.cc | 316 +++++++----------- shell/platform/common/cpp/text_range.h | 17 + .../common/cpp/text_range_unittests.cc | 119 +++++++ shell/platform/glfw/text_input_plugin.cc | 8 +- shell/platform/linux/fl_text_input_plugin.cc | 13 +- shell/platform/windows/text_input_plugin.cc | 8 +- 8 files changed, 287 insertions(+), 215 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index 1ef5b6f3883e9..da0c1fe1e5ed8 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -41,12 +41,11 @@ void TextInputModel::SetText(const std::string& text) { selection_ = TextRange(0); } -bool TextInputModel::SetSelection(size_t base, size_t extent) { - size_t max_pos = text_.length(); - if (base > max_pos || extent > max_pos) { +bool TextInputModel::SetSelection(const TextRange& range) { + if (!text_range().Contains(range)) { return false; } - selection_ = TextRange(base, extent); + selection_ = range; return true; } diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index c5753f85e1218..c49f786f0a9c0 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -27,8 +27,8 @@ class TextInputModel { // Attempts to set the text selection. // - // Returns false if the base or extent are out of bounds. - bool SetSelection(size_t base, size_t extent); + // Returns false if the selection is not within the bounds of the text. + bool SetSelection(const TextRange& range); // Adds a Unicode code point. // @@ -105,11 +105,8 @@ class TextInputModel { // GetText(). int GetCursorOffset() const; - // The position where the selection starts. - int selection_base() const { return selection_.base(); } - - // The position of the cursor. - int selection_extent() const { return selection_.extent(); } + // The current selection. + TextRange selection() const { return selection_; } private: // Deletes the current selection, if any. @@ -118,6 +115,9 @@ class TextInputModel { // reset to the start of the selected range. bool DeleteSelected(); + // Returns a range covering the entire text. + TextRange text_range() const { return TextRange(0, text_.length()); } + std::u16string text_; TextRange selection_ = TextRange(0); }; diff --git a/shell/platform/common/cpp/text_input_model_unittests.cc b/shell/platform/common/cpp/text_input_model_unittests.cc index 86c4771cd5246..730f2b9905428 100644 --- a/shell/platform/common/cpp/text_input_model_unittests.cc +++ b/shell/platform/common/cpp/text_input_model_unittests.cc @@ -41,65 +41,58 @@ TEST(TextInputModel, SetTextReplaceText) { TEST(TextInputModel, SetTextResetsSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(3, 3)); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_TRUE(model->SetSelection(TextRange(3))); + EXPECT_EQ(model->selection(), TextRange(3)); model->SetText("FGHJI"); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); } TEST(TextInputModel, SetSelectionStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_TRUE(model->SetSelection(TextRange(0))); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_TRUE(model->SetSelection(TextRange(2))); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionWthExtent) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); + EXPECT_EQ(model->selection(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionReverseExtent) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); + EXPECT_EQ(model->selection(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionOutsideString) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_FALSE(model->SetSelection(4, 6)); - EXPECT_FALSE(model->SetSelection(5, 6)); - EXPECT_FALSE(model->SetSelection(6, 6)); + EXPECT_FALSE(model->SetSelection(TextRange(4, 6))); + EXPECT_FALSE(model->SetSelection(TextRange(5, 6))); + EXPECT_FALSE(model->SetSelection(TextRange(6, 6))); } TEST(TextInputModel, AddCodePoint) { @@ -109,48 +102,43 @@ TEST(TextInputModel, AddCodePoint) { model->AddCodePoint(0x1f604); model->AddCodePoint('D'); model->AddCodePoint('E'); - EXPECT_EQ(model->selection_base(), 6); - EXPECT_EQ(model->selection_extent(), 6); + EXPECT_EQ(model->selection(), TextRange(6)); EXPECT_STREQ(model->GetText().c_str(), "AB😄DE"); } TEST(TextInputModel, AddCodePointSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint('x'); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } TEST(TextInputModel, AddCodePointReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint('x'); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } TEST(TextInputModel, AddCodePointSelectionWideCharacter) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint(0x1f604); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } TEST(TextInputModel, AddCodePointReverseSelectionWideCharacter) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint(0x1f604); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } @@ -159,498 +147,448 @@ TEST(TextInputModel, AddText) { model->AddText(u"ABCDE"); model->AddText("😄"); model->AddText("FGHIJ"); - EXPECT_EQ(model->selection_base(), 12); - EXPECT_EQ(model->selection_extent(), 12); + EXPECT_EQ(model->selection(), TextRange(12)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE😄FGHIJ"); } TEST(TextInputModel, AddTextSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText("xy"); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } TEST(TextInputModel, AddTextReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText("xy"); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } TEST(TextInputModel, AddTextSelectionWideCharacter) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText(u"😄🙃"); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } TEST(TextInputModel, AddTextReverseSelectionWideCharacter) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText(u"😄🙃"); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } TEST(TextInputModel, DeleteStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "BCDE"); } TEST(TextInputModel, DeleteMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); ASSERT_FALSE(model->Delete()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, DeleteWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(4, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_EQ(model->selection(), TextRange(4)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🧐"); } TEST(TextInputModel, DeleteSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } TEST(TextInputModel, DeleteReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } TEST(TextInputModel, DeleteSurroundingAtCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteSurroundingAtCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(0, 3)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } TEST(TextInputModel, DeleteSurroundingAtCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(0, 4)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } TEST(TextInputModel, DeleteSurroundingBeforeCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(-1, 1)); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } TEST(TextInputModel, DeleteSurroundingBeforeCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(-2, 2)); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(-3, 3)); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } TEST(TextInputModel, DeleteSurroundingAfterCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(1, 1)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } TEST(TextInputModel, DeleteSurroundingAfterCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(1, 2)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } TEST(TextInputModel, DeleteSurroundingAfterCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->DeleteSurrounding(1, 3)); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } TEST(TextInputModel, DeleteSurroundingSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 3)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } TEST(TextInputModel, DeleteSurroundingReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 3)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } TEST(TextInputModel, BackspaceStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); ASSERT_FALSE(model->Backspace()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, BackspaceMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); ASSERT_TRUE(model->Backspace()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } TEST(TextInputModel, BackspaceEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); ASSERT_TRUE(model->Backspace()); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_EQ(model->selection(), TextRange(4)); EXPECT_STREQ(model->GetText().c_str(), "ABCD"); } TEST(TextInputModel, BackspaceWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(4, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); ASSERT_TRUE(model->Backspace()); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "😄🤪🧐"); } TEST(TextInputModel, BackspaceSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } TEST(TextInputModel, BackspaceReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } TEST(TextInputModel, MoveCursorForwardStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); EXPECT_TRUE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 3); - EXPECT_EQ(model->selection_extent(), 3); + EXPECT_EQ(model->selection(), TextRange(3)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); EXPECT_FALSE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(4, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); ASSERT_TRUE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 6); - EXPECT_EQ(model->selection_extent(), 6); + EXPECT_EQ(model->selection(), TextRange(6)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } TEST(TextInputModel, MoveCursorForwardSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_EQ(model->selection(), TextRange(4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorForward()); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_EQ(model->selection(), TextRange(4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); EXPECT_FALSE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); EXPECT_TRUE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 4); - EXPECT_EQ(model->selection_extent(), 4); + EXPECT_EQ(model->selection(), TextRange(4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(4, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); ASSERT_TRUE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 2); - EXPECT_EQ(model->selection_extent(), 2); + EXPECT_EQ(model->selection(), TextRange(2)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } TEST(TextInputModel, MoveCursorBackSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorBack()); - EXPECT_EQ(model->selection_base(), 1); - EXPECT_EQ(model->selection_extent(), 1); + EXPECT_EQ(model->selection(), TextRange(1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); EXPECT_FALSE(model->MoveCursorToBeginning()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->MoveCursorToBeginning()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); EXPECT_TRUE(model->MoveCursorToBeginning()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToBeginning()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToBeginning()); - EXPECT_EQ(model->selection_base(), 0); - EXPECT_EQ(model->selection_extent(), 0); + EXPECT_EQ(model->selection(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); EXPECT_TRUE(model->MoveCursorToEnd()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(2, 2)); + EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); EXPECT_TRUE(model->MoveCursorToEnd()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(5, 5)); + EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); EXPECT_FALSE(model->MoveCursorToEnd()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToEnd()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToEnd()); - EXPECT_EQ(model->selection_base(), 5); - EXPECT_EQ(model->selection_extent(), 5); + EXPECT_EQ(model->selection(), TextRange(5)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -658,7 +596,7 @@ TEST(TextInputModel, GetCursorOffset) { auto model = std::make_unique(); // These characters take 1, 2, 3 and 4 bytes in UTF-8. model->SetText("$¢€𐍈"); - EXPECT_TRUE(model->SetSelection(0, 0)); + EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); EXPECT_EQ(model->GetCursorOffset(), 0); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->GetCursorOffset(), 1); @@ -673,14 +611,14 @@ TEST(TextInputModel, GetCursorOffset) { TEST(TextInputModel, GetCursorOffsetSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(1, 4)); + EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_EQ(model->GetCursorOffset(), 4); } TEST(TextInputModel, GetCursorOffsetReverseSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(4, 1)); + EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_EQ(model->GetCursorOffset(), 1); } diff --git a/shell/platform/common/cpp/text_range.h b/shell/platform/common/cpp/text_range.h index 6524a26e2594a..60482da5158ac 100644 --- a/shell/platform/common/cpp/text_range.h +++ b/shell/platform/common/cpp/text_range.h @@ -47,6 +47,23 @@ class TextRange { // Returns true if the range is of length 0. bool collapsed() const { return base_ == extent_; } + // Returns true if the base is greater than the extent. + bool reversed() const { return base_ > extent_; } + + // Returns true if |position| is contained within the range. + bool Contains(size_t position) const { + return position >= start() && position <= end(); + } + + // Returns true if |range| is contained within the range. + bool Contains(const TextRange& range) const { + return range.start() >= start() && range.end() <= end(); + } + + bool operator==(const TextRange& other) const { + return base_ == other.base_ && extent_ == other.extent_; + } + private: size_t base_; size_t extent_; diff --git a/shell/platform/common/cpp/text_range_unittests.cc b/shell/platform/common/cpp/text_range_unittests.cc index cdeff77a8ceea..8fcf8fed20534 100644 --- a/shell/platform/common/cpp/text_range_unittests.cc +++ b/shell/platform/common/cpp/text_range_unittests.cc @@ -50,4 +50,123 @@ TEST(TextRange, TextRangeFromReversedRange) { EXPECT_FALSE(range.collapsed()); } +TEST(TextRange, ContainsPreStartPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(1)); +} + +TEST(TextRange, ContainsStartPosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(2)); +} + +TEST(TextRange, ContainsMiddlePosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(3)); + EXPECT_TRUE(range.Contains(4)); +} + +TEST(TextRange, ContainsEndPosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(6)); +} + +TEST(TextRange, ContainsPostEndPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(7)); +} + +TEST(TextRange, ContainsPreStartPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(1)); +} + +TEST(TextRange, ContainsStartPositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(2)); +} + +TEST(TextRange, ContainsMiddlePositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(3)); + EXPECT_TRUE(range.Contains(4)); +} + +TEST(TextRange, ContainsEndPositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(6)); +} + +TEST(TextRange, ContainsPostEndPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(7)); +} + +TEST(TextRange, ContainsRangePreStartPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(TextRange(0, 1))); +} + +TEST(TextRange, ContainsRangeStartPosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(TextRange(2))); +} + +TEST(TextRange, ContainsRangeMiddlePosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(TextRange(3, 4))); + EXPECT_TRUE(range.Contains(TextRange(4, 5))); +} + +TEST(TextRange, ContainsRangeEndPosition) { + TextRange range(2, 6); + EXPECT_TRUE(range.Contains(TextRange(6))); +} + +TEST(TextRange, ContainsRangePostEndPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(TextRange(6, 7))); +} + +TEST(TextRange, ContainsRangePreStartPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(TextRange(0, 1))); +} + +TEST(TextRange, ContainsRangeStartPositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(TextRange(2))); +} + +TEST(TextRange, ContainsRangeMiddlePositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(TextRange(3, 4))); + EXPECT_TRUE(range.Contains(TextRange(4, 5))); +} + +TEST(TextRange, ContainsRangeEndPositionReversed) { + TextRange range(6, 2); + EXPECT_TRUE(range.Contains(TextRange(5))); +} + +TEST(TextRange, ContainsRangePostEndPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(TextRange(6, 7))); +} + +TEST(TextRange, ReversedForwardRange) { + TextRange range(2, 6); + EXPECT_FALSE(range.reversed()); +} + +TEST(TextRange, ReversedCollapsedRange) { + TextRange range(2, 2); + EXPECT_FALSE(range.reversed()); +} + +TEST(TextRange, ReversedReversedRange) { + TextRange range(6, 2); + EXPECT_TRUE(range.reversed()); +} + } // namespace flutter diff --git a/shell/platform/glfw/text_input_plugin.cc b/shell/platform/glfw/text_input_plugin.cc index 7a7a88f0ccd57..7ebc29ecef6be 100644 --- a/shell/platform/glfw/text_input_plugin.cc +++ b/shell/platform/glfw/text_input_plugin.cc @@ -195,7 +195,7 @@ void TextInputPlugin::HandleMethodCall( base = extent = 0; } active_model_->SetText(text->value.GetString()); - active_model_->SetSelection(base, extent); + active_model_->SetSelection(TextRange(base, extent)); } else { result->NotImplemented(); return; @@ -210,14 +210,14 @@ void TextInputPlugin::SendStateUpdate(const TextInputModel& model) { auto& allocator = args->GetAllocator(); args->PushBack(client_id_, allocator); + TextRange selection = model.selection(); rapidjson::Value editing_state(rapidjson::kObjectType); editing_state.AddMember(kComposingBaseKey, -1, allocator); editing_state.AddMember(kComposingExtentKey, -1, allocator); editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream, allocator); - editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator); - editing_state.AddMember(kSelectionExtentKey, model.selection_extent(), - allocator); + editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator); + editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator); editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator); editing_state.AddMember( kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator); diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index ae9ed10a98897..11dc8489f1a21 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -90,15 +90,14 @@ static void update_editing_state(FlTextInputPlugin* self) { fl_value_append_take(args, fl_value_new_int(self->client_id)); g_autoptr(FlValue) value = fl_value_new_map(); + TextRange selection = self->text_model->selection(); fl_value_set_string_take( value, kTextKey, fl_value_new_string(self->text_model->GetText().c_str())); - fl_value_set_string_take( - value, kSelectionBaseKey, - fl_value_new_int(self->text_model->selection_base())); - fl_value_set_string_take( - value, kSelectionExtentKey, - fl_value_new_int(self->text_model->selection_extent())); + fl_value_set_string_take(value, kSelectionBaseKey, + fl_value_new_int(selection.base())); + fl_value_set_string_take(value, kSelectionExtentKey, + fl_value_new_int(selection.extent())); // The following keys are not implemented and set to default values. fl_value_set_string_take(value, kSelectionAffinityKey, @@ -219,7 +218,7 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, } self->text_model->SetText(text); - self->text_model->SetSelection(selection_base, selection_extent); + self->text_model->SetSelection(TextRange(selection_base, selection_extent)); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 1687664b42fdd..e69caf504e364 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -196,7 +196,7 @@ void TextInputPlugin::HandleMethodCall( base = extent = 0; } active_model_->SetText(text->value.GetString()); - active_model_->SetSelection(base, extent); + active_model_->SetSelection(TextRange(base, extent)); } else { result->NotImplemented(); return; @@ -211,14 +211,14 @@ void TextInputPlugin::SendStateUpdate(const TextInputModel& model) { auto& allocator = args->GetAllocator(); args->PushBack(client_id_, allocator); + TextRange selection = model.selection(); rapidjson::Value editing_state(rapidjson::kObjectType); editing_state.AddMember(kComposingBaseKey, -1, allocator); editing_state.AddMember(kComposingExtentKey, -1, allocator); editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream, allocator); - editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator); - editing_state.AddMember(kSelectionExtentKey, model.selection_extent(), - allocator); + editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator); + editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator); editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator); editing_state.AddMember( kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator); From f398204b16d8def89eebc4b766d3e5415fec6e9f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 15 Oct 2020 10:47:01 -0700 Subject: [PATCH 118/219] Add a style note about Linux embedding style (#21819) --- CONTRIBUTING.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86c46f4b65e33..4cd3c378af349 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,11 +20,18 @@ contributing guide. The Flutter engine follows Google style for the languages it uses: - [C++](https://google.github.io/styleguide/cppguide.html) + - **Note**: The Linux embedding generally follows idiomatic GObject-based C style. + Use of C++ is discouraged in that embedding to avoid creating hybrid code that + feels unfamiliar to either developers used to working with GObject or C++ developers. + E.g., do not use STL collections or std::string. Exceptions: + - C-style casts are forbidden; use C++ casts. + - Use `nullptr` rather than `NULL`. + - Avoid `#define`; for internal constants use `static constexpr` instead. - [Objective-C](https://google.github.io/styleguide/objcguide.html) (including [Objective-C++](https://google.github.io/styleguide/objcguide.html#objective-c)) - [Java](https://google.github.io/styleguide/javaguide.html) -C++ and Objective-C/C++ files are formatted with `clang-format`, and GN files with `gn format`. +C/C++ and Objective-C/C++ files are formatted with `clang-format`, and GN files with `gn format`. [build_status]: https://cirrus-ci.com/github/flutter/engine [code_of_conduct]: https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md From dc848f154b9c5534262e5b8d3f0360a1beafdeb5 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 15 Oct 2020 10:48:50 -0700 Subject: [PATCH 119/219] Add flag to not publish the observatory port over mDNS (#21632) * Add flag to not publish the observatory port over mDNS * Review edits * Format --- common/settings.cc | 2 + common/settings.h | 5 + shell/common/switches.cc | 4 + shell/common/switches.h | 3 + .../ios/framework/Source/FlutterEngine.mm | 3 +- .../Source/FlutterObservatoryPublisher.h | 5 + .../Source/FlutterObservatoryPublisher.mm | 96 +++++++------------ 7 files changed, 56 insertions(+), 62 deletions(-) diff --git a/common/settings.cc b/common/settings.cc index 1508e213bcc6c..ec8ddf60f9c8d 100644 --- a/common/settings.cc +++ b/common/settings.cc @@ -46,6 +46,8 @@ std::string Settings::ToString() const { stream << "enable_dart_profiling: " << enable_dart_profiling << std::endl; stream << "disable_dart_asserts: " << disable_dart_asserts << std::endl; stream << "enable_observatory: " << enable_observatory << std::endl; + stream << "enable_observatory_publication: " << enable_observatory_publication + << std::endl; stream << "observatory_host: " << observatory_host << std::endl; stream << "observatory_port: " << observatory_port << std::endl; stream << "use_test_fonts: " << use_test_fonts << std::endl; diff --git a/common/settings.h b/common/settings.h index 46f88399b407e..d85506b946e25 100644 --- a/common/settings.h +++ b/common/settings.h @@ -126,6 +126,11 @@ struct Settings { // Whether the Dart VM service should be enabled. bool enable_observatory = false; + // Whether to publish the observatory URL over mDNS. + // On iOS 14 this prompts a local network permission dialog, + // which cannot be accepted or dismissed in a CI environment. + bool enable_observatory_publication = true; + // The IP address to which the Dart VM service is bound. std::string observatory_host; diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 1b2398b942ff0..3f80830136cdc 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -221,6 +221,10 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.enable_observatory = !command_line.HasOption(FlagForSwitch(Switch::DisableObservatory)); + // Enable mDNS Observatory Publication + settings.enable_observatory_publication = !command_line.HasOption( + FlagForSwitch(Switch::DisableObservatoryPublication)); + // Set Observatory Host if (command_line.HasOption(FlagForSwitch(Switch::DeviceObservatoryHost))) { command_line.GetOptionValue(FlagForSwitch(Switch::DeviceObservatoryHost), diff --git a/shell/common/switches.h b/shell/common/switches.h index 260214db4e8a0..9a9dea9401d8f 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -79,6 +79,9 @@ DEF_SWITCH(DisableObservatory, "disable-observatory", "Disable the Dart Observatory. The observatory is never available " "in release mode.") +DEF_SWITCH(DisableObservatoryPublication, + "disable-observatory-publication", + "Disable mDNS Dart Observatory publication.") DEF_SWITCH(IPv6, "ipv6", "Bind to the IPv6 localhost address for the Dart Observatory. " diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 2592537749efa..5248f59a98b34 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -547,7 +547,8 @@ - (BOOL)createShell:(NSString*)entrypoint if (!_platformViewsController) { _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); } - _publisher.reset([[FlutterObservatoryPublisher alloc] init]); + _publisher.reset([[FlutterObservatoryPublisher alloc] + initWithEnableObservatoryPublication:settings.enable_observatory_publication]); [self maybeSetupPlatformViewChannels]; _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); if (profilerEnabled) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h index 387176f76e25e..a00c17ad89245 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h @@ -9,6 +9,11 @@ @interface FlutterObservatoryPublisher : NSObject +- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + @property(nonatomic, readonly) NSURL* url; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm index 072a244a967fc..e06ac8ea702a4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm @@ -37,26 +37,23 @@ @implementation FlutterObservatoryPublisher #include #include "flutter/fml/logging.h" -#include "flutter/fml/make_copyable.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/task_runner.h" #include "flutter/runtime/dart_service_isolate.h" @protocol FlutterObservatoryPublisherDelegate -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner; -- (void)publishServiceProtocolPort:(NSString*)uri; +- (void)publishServiceProtocolPort:(NSURL*)uri; - (void)stopService; - -@property(readonly) fml::scoped_nsobject url; @end @interface FlutterObservatoryPublisher () -- (NSData*)createTxtData:(NSURL*)url; ++ (NSData*)createTxtData:(NSURL*)url; -@property(readonly) NSString* serviceName; +@property(readonly, class) NSString* serviceName; @property(readonly) fml::scoped_nsobject> delegate; +@property(nonatomic, readwrite) NSURL* url; +@property(readonly) BOOL enableObservatoryPublication; @end @@ -68,19 +65,9 @@ @interface ObservatoryDNSServiceDelegate : NSObject _owner; DNSServiceRef _dnsServiceRef; } -@synthesize url; - -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { - self = [super init]; - NSAssert(self, @"Super must not return null on init."); - _owner.reset([owner retain]); - return self; -} - - (void)stopService { if (_dnsServiceRef) { DNSServiceRefDeallocate(_dnsServiceRef); @@ -88,11 +75,7 @@ - (void)stopService { } } -- (void)publishServiceProtocolPort:(NSString*)uri { - // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port - // number. - url.reset([[NSURL alloc] initWithString:uri]); - +- (void)publishServiceProtocolPort:(NSURL*)url { DNSServiceFlags flags = kDNSServiceFlagsDefault; #if TARGET_IPHONE_SIMULATOR // Simulator needs to use local loopback explicitly to work. @@ -105,11 +88,11 @@ - (void)publishServiceProtocolPort:(NSString*)uri { const char* domain = "local."; // default domain uint16_t port = [[url port] unsignedShortValue]; - NSData* txtData = [_owner createTxtData:url.get()]; - int err = - DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, - [_owner.get().serviceName UTF8String], registrationType, domain, NULL, - htons(port), txtData.length, txtData.bytes, registrationCallback, NULL); + NSData* txtData = [FlutterObservatoryPublisher createTxtData:url]; + int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, + FlutterObservatoryPublisher.serviceName.UTF8String, registrationType, + domain, NULL, htons(port), txtData.length, txtData.bytes, + registrationCallback, NULL); if (err != 0) { FML_LOG(ERROR) << "Failed to register observatory port with mDNS with error " << err << "."; @@ -122,8 +105,8 @@ - (void)publishServiceProtocolPort:(NSString*)uri { << "to the 'NSBonjourServices' key in your Info.plist for the Debug/" << "Profile configurations. " << "For more information, see " - // Update link to a specific header as needed. - << "https://flutter.dev/docs/development/add-to-app/ios/project-setup"; + << "https://flutter.dev/docs/development/add-to-app/ios/" + "project-setup#local-network-privacy-permissions"; } } else { DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue()); @@ -162,34 +145,21 @@ static void DNSSD_API registrationCallback(DNSServiceRef sdRef, @end @implementation ObservatoryNSNetServiceDelegate { - fml::scoped_nsobject _owner; fml::scoped_nsobject _netService; } -@synthesize url; - -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { - self = [super init]; - NSAssert(self, @"Super must not return null on init."); - _owner.reset([owner retain]); - return self; -} - - (void)stopService { [_netService.get() stop]; [_netService.get() setDelegate:nil]; } -- (void)publishServiceProtocolPort:(NSString*)uri { - // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port - // number. - url.reset([[NSURL alloc] initWithString:uri]); - - NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local." - type:@"_dartobservatory._tcp." - name:_owner.get().serviceName - port:[[url port] intValue]]; - [netServiceTmp setTXTRecordData:[_owner createTxtData:url.get()]]; +- (void)publishServiceProtocolPort:(NSURL*)url { + NSNetService* netServiceTmp = + [[NSNetService alloc] initWithDomain:@"local." + type:@"_dartobservatory._tcp." + name:FlutterObservatoryPublisher.serviceName + port:[[url port] intValue]]; + [netServiceTmp setTXTRecordData:[FlutterObservatoryPublisher createTxtData:url]]; _netService.reset(netServiceTmp); [_netService.get() setDelegate:self]; [_netService.get() publish]; @@ -211,19 +181,16 @@ @implementation FlutterObservatoryPublisher { std::unique_ptr> _weakFactory; } -- (NSURL*)url { - return [_delegate.get().url autorelease]; -} - -- (instancetype)init { +- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication { self = [super init]; NSAssert(self, @"Super must not return null on init."); if (@available(iOS 9.3, *)) { - _delegate.reset([[ObservatoryDNSServiceDelegate alloc] initWithOwner:self]); + _delegate.reset([[ObservatoryDNSServiceDelegate alloc] init]); } else { - _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] initWithOwner:self]); + _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] init]); } + _enableObservatoryPublication = enableObservatoryPublication; _weakFactory = std::make_unique>(self); fml::MessageLoop::EnsureInitializedForCurrentThread(); @@ -233,9 +200,15 @@ - (instancetype)init { runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) { if (!uri.empty()) { runner->PostTask([weak, uri]() { + // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port + // number. if (weak) { - [[weak.get() delegate] - publishServiceProtocolPort:[NSString stringWithUTF8String:uri.c_str()]]; + NSURL* url = + [[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]]; + weak.get().url = url; + if (weak.get().enableObservatoryPublication) { + [[weak.get() delegate] publishServiceProtocolPort:url]; + } } }); } @@ -244,11 +217,11 @@ - (instancetype)init { return self; } -- (NSString*)serviceName { ++ (NSString*)serviceName { return NSBundle.mainBundle.bundleIdentifier; } -- (NSData*)createTxtData:(NSURL*)url { ++ (NSData*)createTxtData:(NSURL*)url { // Check to see if there's an authentication code. If there is, we'll provide // it as a txt record so flutter tools can establish a connection. NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])]; @@ -261,6 +234,7 @@ - (NSData*)createTxtData:(NSURL*)url { - (void)dealloc { [_delegate stopService]; + [_url release]; flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle)); [super dealloc]; From 57d3c6d98699aaf8b6edcb85a0742ad56803d833 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 15 Oct 2020 10:54:08 -0700 Subject: [PATCH 120/219] Fix destruction order in C++ plugin registrar (#21840) The C++ wrapper's plugin registrar can own plugins to provided lifetime management. However, plugins expect the registrar to be valid for the life of the object, including during destruction, so any owned plugins must be explicitly cleared before any registrar-specific destruction happens. --- .../include/flutter/plugin_registrar.h | 5 ++ .../cpp/client_wrapper/plugin_registrar.cc | 12 +++- .../plugin_registrar_unittests.cc | 33 ++++++++++ shell/platform/glfw/client_wrapper/BUILD.gn | 1 + .../include/flutter/plugin_registrar_glfw.h | 7 ++- .../plugin_registrar_glfw_unittests.cc | 60 +++++++++++++++++++ .../testing/stub_flutter_glfw_api.cc | 6 ++ .../flutter/plugin_registrar_windows.h | 7 ++- .../plugin_registrar_windows_unittests.cc | 34 +++++++++++ 9 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 shell/platform/glfw/client_wrapper/plugin_registrar_glfw_unittests.cc diff --git a/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h b/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h index 06a34e0bf9a29..a01b75e8c9136 100644 --- a/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h +++ b/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h @@ -51,6 +51,11 @@ class PluginRegistrar { protected: FlutterDesktopPluginRegistrarRef registrar() { return registrar_; } + // Destroys all owned plugins. Subclasses should call this at the beginning of + // their destructors to prevent the possibility of an owned plugin trying to + // access destroyed state during its own destruction. + void ClearPlugins(); + private: // Handle for interacting with the C API's registrar. FlutterDesktopPluginRegistrarRef registrar_; diff --git a/shell/platform/common/cpp/client_wrapper/plugin_registrar.cc b/shell/platform/common/cpp/client_wrapper/plugin_registrar.cc index b81411fb40e01..b5ab707c80aec 100644 --- a/shell/platform/common/cpp/client_wrapper/plugin_registrar.cc +++ b/shell/platform/common/cpp/client_wrapper/plugin_registrar.cc @@ -21,12 +21,22 @@ PluginRegistrar::PluginRegistrar(FlutterDesktopPluginRegistrarRef registrar) messenger_ = std::make_unique(core_messenger); } -PluginRegistrar::~PluginRegistrar() {} +PluginRegistrar::~PluginRegistrar() { + // This must always be the first call. + ClearPlugins(); + + // Explicitly cleared to facilitate testing of destruction order. + messenger_.reset(); +} void PluginRegistrar::AddPlugin(std::unique_ptr plugin) { plugins_.insert(std::move(plugin)); } +void PluginRegistrar::ClearPlugins() { + plugins_.clear(); +} + // ===== PluginRegistrarManager ===== // static diff --git a/shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc b/shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc index 739b959a8814b..b3a92aa67d714 100644 --- a/shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc @@ -80,8 +80,41 @@ class TestPluginRegistrar : public PluginRegistrar { std::function destruction_callback_; }; +// A test plugin that tries to access registrar state during destruction and +// reports it out via a flag provided at construction. +class TestPlugin : public Plugin { + public: + // registrar_valid_at_destruction will be set at destruction to indicate + // whether or not |registrar->messenger()| was non-null. + TestPlugin(PluginRegistrar* registrar, bool* registrar_valid_at_destruction) + : registrar_(registrar), + registrar_valid_at_destruction_(registrar_valid_at_destruction) {} + virtual ~TestPlugin() { + *registrar_valid_at_destruction_ = registrar_->messenger() != nullptr; + } + + private: + PluginRegistrar* registrar_; + bool* registrar_valid_at_destruction_; +}; + } // namespace +// Tests that the registrar runs plugin destructors before its own teardown. +TEST(PluginRegistrarTest, PluginDestroyedBeforeRegistrar) { + auto dummy_registrar_handle = + reinterpret_cast(1); + bool registrar_valid_at_destruction = false; + { + PluginRegistrar registrar(dummy_registrar_handle); + + auto plugin = std::make_unique(®istrar, + ®istrar_valid_at_destruction); + registrar.AddPlugin(std::move(plugin)); + } + EXPECT_TRUE(registrar_valid_at_destruction); +} + // Tests that the registrar returns a messenger that passes Send through to the // C API. TEST(PluginRegistrarTest, MessengerSend) { diff --git a/shell/platform/glfw/client_wrapper/BUILD.gn b/shell/platform/glfw/client_wrapper/BUILD.gn index 3a4f57263ef6f..c0d6ca643896f 100644 --- a/shell/platform/glfw/client_wrapper/BUILD.gn +++ b/shell/platform/glfw/client_wrapper/BUILD.gn @@ -76,6 +76,7 @@ executable("client_wrapper_glfw_unittests") { "flutter_engine_unittests.cc", "flutter_window_controller_unittests.cc", "flutter_window_unittests.cc", + "plugin_registrar_glfw_unittests.cc", ] defines = [ "FLUTTER_DESKTOP_LIBRARY" ] diff --git a/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h b/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h index 1d0a83e20b2f9..2459dde60bf29 100644 --- a/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h +++ b/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h @@ -26,7 +26,12 @@ class PluginRegistrarGlfw : public PluginRegistrar { FlutterDesktopPluginRegistrarGetWindow(core_registrar)); } - virtual ~PluginRegistrarGlfw() = default; + virtual ~PluginRegistrarGlfw() { + // Must be the first call. + ClearPlugins(); + // Explicitly cleared to facilitate destruction order testing. + window_.reset(); + } // Prevent copying. PluginRegistrarGlfw(PluginRegistrarGlfw const&) = delete; diff --git a/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_unittests.cc b/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_unittests.cc new file mode 100644 index 0000000000000..ff95921fb35e3 --- /dev/null +++ b/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_unittests.cc @@ -0,0 +1,60 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "flutter/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h" +#include "flutter/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.h" +#include "gtest/gtest.h" + +namespace flutter { + +namespace { + +// A test plugin that tries to access registrar state during destruction and +// reports it out via a flag provided at construction. +class TestPlugin : public Plugin { + public: + // registrar_valid_at_destruction will be set at destruction to indicate + // whether or not |registrar->window()| was non-null. + TestPlugin(PluginRegistrarGlfw* registrar, + bool* registrar_valid_at_destruction) + : registrar_(registrar), + registrar_valid_at_destruction_(registrar_valid_at_destruction) {} + virtual ~TestPlugin() { + *registrar_valid_at_destruction_ = registrar_->window() != nullptr; + } + + private: + PluginRegistrarGlfw* registrar_; + bool* registrar_valid_at_destruction_; +}; + +} // namespace + +TEST(PluginRegistrarGlfwTest, GetView) { + testing::ScopedStubFlutterGlfwApi scoped_api_stub( + std::make_unique()); + PluginRegistrarGlfw registrar( + reinterpret_cast(1)); + EXPECT_NE(registrar.window(), nullptr); +} + +// Tests that the registrar runs plugin destructors before its own teardown. +TEST(PluginRegistrarGlfwTest, PluginDestroyedBeforeRegistrar) { + auto dummy_registrar_handle = + reinterpret_cast(1); + bool registrar_valid_at_destruction = false; + { + PluginRegistrarGlfw registrar(dummy_registrar_handle); + + auto plugin = std::make_unique(®istrar, + ®istrar_valid_at_destruction); + registrar.AddPlugin(std::move(plugin)); + } + EXPECT_TRUE(registrar_valid_at_destruction); +} + +} // namespace flutter diff --git a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc index c12e54fd4dad7..df61b34550715 100644 --- a/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc +++ b/shell/platform/glfw/client_wrapper/testing/stub_flutter_glfw_api.cc @@ -183,6 +183,12 @@ FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( return reinterpret_cast(2); } +FlutterDesktopWindowRef FlutterDesktopPluginRegistrarGetWindow( + FlutterDesktopPluginRegistrarRef registrar) { + // The stub ignores this, so just return an arbitrary non-zero value. + return reinterpret_cast(3); +} + void FlutterDesktopPluginRegistrarEnableInputBlocking( FlutterDesktopPluginRegistrarRef registrar, const char* channel) { diff --git a/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h b/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h index 20b00aeb605eb..98093e1b396e7 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h +++ b/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h @@ -36,7 +36,12 @@ class PluginRegistrarWindows : public PluginRegistrar { FlutterDesktopPluginRegistrarGetView(core_registrar)); } - virtual ~PluginRegistrarWindows() = default; + virtual ~PluginRegistrarWindows() { + // Must be the first call. + ClearPlugins(); + // Explicitly cleared to facilitate destruction order testing. + view_.reset(); + } // Prevent copying. PluginRegistrarWindows(PluginRegistrarWindows const&) = delete; diff --git a/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc b/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc index 63049a5eb82b9..0c9bdb9618907 100644 --- a/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc +++ b/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc @@ -43,6 +43,25 @@ class TestWindowsApi : public testing::StubFlutterWindowsApi { void* last_registered_user_data_ = nullptr; }; +// A test plugin that tries to access registrar state during destruction and +// reports it out via a flag provided at construction. +class TestPlugin : public Plugin { + public: + // registrar_valid_at_destruction will be set at destruction to indicate + // whether or not |registrar->GetView()| was non-null. + TestPlugin(PluginRegistrarWindows* registrar, + bool* registrar_valid_at_destruction) + : registrar_(registrar), + registrar_valid_at_destruction_(registrar_valid_at_destruction) {} + virtual ~TestPlugin() { + *registrar_valid_at_destruction_ = registrar_->GetView() != nullptr; + } + + private: + PluginRegistrarWindows* registrar_; + bool* registrar_valid_at_destruction_; +}; + } // namespace TEST(PluginRegistrarWindowsTest, GetView) { @@ -54,6 +73,21 @@ TEST(PluginRegistrarWindowsTest, GetView) { EXPECT_NE(registrar.GetView(), nullptr); } +// Tests that the registrar runs plugin destructors before its own teardown. +TEST(PluginRegistrarWindowsTest, PluginDestroyedBeforeRegistrar) { + auto dummy_registrar_handle = + reinterpret_cast(1); + bool registrar_valid_at_destruction = false; + { + PluginRegistrarWindows registrar(dummy_registrar_handle); + + auto plugin = std::make_unique(®istrar, + ®istrar_valid_at_destruction); + registrar.AddPlugin(std::move(plugin)); + } + EXPECT_TRUE(registrar_valid_at_destruction); +} + TEST(PluginRegistrarWindowsTest, RegisterUnregister) { testing::ScopedStubFlutterWindowsApi scoped_api_stub( std::make_unique()); From 40c226e253101b79a39f77fbf4dcfe9c2e9c738e Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 15 Oct 2020 12:32:24 -0700 Subject: [PATCH 121/219] Revert "Add flag to not publish the observatory port over mDNS (#21632)" (#21882) This reverts commit dc848f154b9c5534262e5b8d3f0360a1beafdeb5. --- common/settings.cc | 2 - common/settings.h | 5 - shell/common/switches.cc | 4 - shell/common/switches.h | 3 - .../ios/framework/Source/FlutterEngine.mm | 3 +- .../Source/FlutterObservatoryPublisher.h | 5 - .../Source/FlutterObservatoryPublisher.mm | 96 ++++++++++++------- 7 files changed, 62 insertions(+), 56 deletions(-) diff --git a/common/settings.cc b/common/settings.cc index ec8ddf60f9c8d..1508e213bcc6c 100644 --- a/common/settings.cc +++ b/common/settings.cc @@ -46,8 +46,6 @@ std::string Settings::ToString() const { stream << "enable_dart_profiling: " << enable_dart_profiling << std::endl; stream << "disable_dart_asserts: " << disable_dart_asserts << std::endl; stream << "enable_observatory: " << enable_observatory << std::endl; - stream << "enable_observatory_publication: " << enable_observatory_publication - << std::endl; stream << "observatory_host: " << observatory_host << std::endl; stream << "observatory_port: " << observatory_port << std::endl; stream << "use_test_fonts: " << use_test_fonts << std::endl; diff --git a/common/settings.h b/common/settings.h index d85506b946e25..46f88399b407e 100644 --- a/common/settings.h +++ b/common/settings.h @@ -126,11 +126,6 @@ struct Settings { // Whether the Dart VM service should be enabled. bool enable_observatory = false; - // Whether to publish the observatory URL over mDNS. - // On iOS 14 this prompts a local network permission dialog, - // which cannot be accepted or dismissed in a CI environment. - bool enable_observatory_publication = true; - // The IP address to which the Dart VM service is bound. std::string observatory_host; diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 3f80830136cdc..1b2398b942ff0 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -221,10 +221,6 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.enable_observatory = !command_line.HasOption(FlagForSwitch(Switch::DisableObservatory)); - // Enable mDNS Observatory Publication - settings.enable_observatory_publication = !command_line.HasOption( - FlagForSwitch(Switch::DisableObservatoryPublication)); - // Set Observatory Host if (command_line.HasOption(FlagForSwitch(Switch::DeviceObservatoryHost))) { command_line.GetOptionValue(FlagForSwitch(Switch::DeviceObservatoryHost), diff --git a/shell/common/switches.h b/shell/common/switches.h index 9a9dea9401d8f..260214db4e8a0 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -79,9 +79,6 @@ DEF_SWITCH(DisableObservatory, "disable-observatory", "Disable the Dart Observatory. The observatory is never available " "in release mode.") -DEF_SWITCH(DisableObservatoryPublication, - "disable-observatory-publication", - "Disable mDNS Dart Observatory publication.") DEF_SWITCH(IPv6, "ipv6", "Bind to the IPv6 localhost address for the Dart Observatory. " diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 5248f59a98b34..2592537749efa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -547,8 +547,7 @@ - (BOOL)createShell:(NSString*)entrypoint if (!_platformViewsController) { _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); } - _publisher.reset([[FlutterObservatoryPublisher alloc] - initWithEnableObservatoryPublication:settings.enable_observatory_publication]); + _publisher.reset([[FlutterObservatoryPublisher alloc] init]); [self maybeSetupPlatformViewChannels]; _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); if (profilerEnabled) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h index a00c17ad89245..387176f76e25e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h @@ -9,11 +9,6 @@ @interface FlutterObservatoryPublisher : NSObject -- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication - NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - @property(nonatomic, readonly) NSURL* url; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm index e06ac8ea702a4..072a244a967fc 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm @@ -37,23 +37,26 @@ @implementation FlutterObservatoryPublisher #include #include "flutter/fml/logging.h" +#include "flutter/fml/make_copyable.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/fml/task_runner.h" #include "flutter/runtime/dart_service_isolate.h" @protocol FlutterObservatoryPublisherDelegate -- (void)publishServiceProtocolPort:(NSURL*)uri; +- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner; +- (void)publishServiceProtocolPort:(NSString*)uri; - (void)stopService; + +@property(readonly) fml::scoped_nsobject url; @end @interface FlutterObservatoryPublisher () -+ (NSData*)createTxtData:(NSURL*)url; +- (NSData*)createTxtData:(NSURL*)url; -@property(readonly, class) NSString* serviceName; +@property(readonly) NSString* serviceName; @property(readonly) fml::scoped_nsobject> delegate; -@property(nonatomic, readwrite) NSURL* url; -@property(readonly) BOOL enableObservatoryPublication; @end @@ -65,9 +68,19 @@ @interface ObservatoryDNSServiceDelegate : NSObject _owner; DNSServiceRef _dnsServiceRef; } +@synthesize url; + +- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { + self = [super init]; + NSAssert(self, @"Super must not return null on init."); + _owner.reset([owner retain]); + return self; +} + - (void)stopService { if (_dnsServiceRef) { DNSServiceRefDeallocate(_dnsServiceRef); @@ -75,7 +88,11 @@ - (void)stopService { } } -- (void)publishServiceProtocolPort:(NSURL*)url { +- (void)publishServiceProtocolPort:(NSString*)uri { + // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port + // number. + url.reset([[NSURL alloc] initWithString:uri]); + DNSServiceFlags flags = kDNSServiceFlagsDefault; #if TARGET_IPHONE_SIMULATOR // Simulator needs to use local loopback explicitly to work. @@ -88,11 +105,11 @@ - (void)publishServiceProtocolPort:(NSURL*)url { const char* domain = "local."; // default domain uint16_t port = [[url port] unsignedShortValue]; - NSData* txtData = [FlutterObservatoryPublisher createTxtData:url]; - int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, - FlutterObservatoryPublisher.serviceName.UTF8String, registrationType, - domain, NULL, htons(port), txtData.length, txtData.bytes, - registrationCallback, NULL); + NSData* txtData = [_owner createTxtData:url.get()]; + int err = + DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, + [_owner.get().serviceName UTF8String], registrationType, domain, NULL, + htons(port), txtData.length, txtData.bytes, registrationCallback, NULL); if (err != 0) { FML_LOG(ERROR) << "Failed to register observatory port with mDNS with error " << err << "."; @@ -105,8 +122,8 @@ - (void)publishServiceProtocolPort:(NSURL*)url { << "to the 'NSBonjourServices' key in your Info.plist for the Debug/" << "Profile configurations. " << "For more information, see " - << "https://flutter.dev/docs/development/add-to-app/ios/" - "project-setup#local-network-privacy-permissions"; + // Update link to a specific header as needed. + << "https://flutter.dev/docs/development/add-to-app/ios/project-setup"; } } else { DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue()); @@ -145,21 +162,34 @@ static void DNSSD_API registrationCallback(DNSServiceRef sdRef, @end @implementation ObservatoryNSNetServiceDelegate { + fml::scoped_nsobject _owner; fml::scoped_nsobject _netService; } +@synthesize url; + +- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { + self = [super init]; + NSAssert(self, @"Super must not return null on init."); + _owner.reset([owner retain]); + return self; +} + - (void)stopService { [_netService.get() stop]; [_netService.get() setDelegate:nil]; } -- (void)publishServiceProtocolPort:(NSURL*)url { - NSNetService* netServiceTmp = - [[NSNetService alloc] initWithDomain:@"local." - type:@"_dartobservatory._tcp." - name:FlutterObservatoryPublisher.serviceName - port:[[url port] intValue]]; - [netServiceTmp setTXTRecordData:[FlutterObservatoryPublisher createTxtData:url]]; +- (void)publishServiceProtocolPort:(NSString*)uri { + // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port + // number. + url.reset([[NSURL alloc] initWithString:uri]); + + NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local." + type:@"_dartobservatory._tcp." + name:_owner.get().serviceName + port:[[url port] intValue]]; + [netServiceTmp setTXTRecordData:[_owner createTxtData:url.get()]]; _netService.reset(netServiceTmp); [_netService.get() setDelegate:self]; [_netService.get() publish]; @@ -181,16 +211,19 @@ @implementation FlutterObservatoryPublisher { std::unique_ptr> _weakFactory; } -- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication { +- (NSURL*)url { + return [_delegate.get().url autorelease]; +} + +- (instancetype)init { self = [super init]; NSAssert(self, @"Super must not return null on init."); if (@available(iOS 9.3, *)) { - _delegate.reset([[ObservatoryDNSServiceDelegate alloc] init]); + _delegate.reset([[ObservatoryDNSServiceDelegate alloc] initWithOwner:self]); } else { - _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] init]); + _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] initWithOwner:self]); } - _enableObservatoryPublication = enableObservatoryPublication; _weakFactory = std::make_unique>(self); fml::MessageLoop::EnsureInitializedForCurrentThread(); @@ -200,15 +233,9 @@ - (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPubl runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) { if (!uri.empty()) { runner->PostTask([weak, uri]() { - // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port - // number. if (weak) { - NSURL* url = - [[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]]; - weak.get().url = url; - if (weak.get().enableObservatoryPublication) { - [[weak.get() delegate] publishServiceProtocolPort:url]; - } + [[weak.get() delegate] + publishServiceProtocolPort:[NSString stringWithUTF8String:uri.c_str()]]; } }); } @@ -217,11 +244,11 @@ - (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPubl return self; } -+ (NSString*)serviceName { +- (NSString*)serviceName { return NSBundle.mainBundle.bundleIdentifier; } -+ (NSData*)createTxtData:(NSURL*)url { +- (NSData*)createTxtData:(NSURL*)url { // Check to see if there's an authentication code. If there is, we'll provide // it as a txt record so flutter tools can establish a connection. NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])]; @@ -234,7 +261,6 @@ + (NSData*)createTxtData:(NSURL*)url { - (void)dealloc { [_delegate stopService]; - [_url release]; flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle)); [super dealloc]; From 4494a8374249113af6b9f16ad4be4f3520f56062 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 15 Oct 2020 14:53:20 -0700 Subject: [PATCH 122/219] Add flag to not publish the observatory port over mDNS (#21883) --- common/settings.cc | 2 + common/settings.h | 5 + shell/common/switches.cc | 4 + shell/common/switches.h | 3 + .../ios/framework/Source/FlutterEngine.mm | 3 +- .../Source/FlutterObservatoryPublisher.h | 5 + .../Source/FlutterObservatoryPublisher.mm | 99 +++++++------------ 7 files changed, 59 insertions(+), 62 deletions(-) diff --git a/common/settings.cc b/common/settings.cc index 1508e213bcc6c..ec8ddf60f9c8d 100644 --- a/common/settings.cc +++ b/common/settings.cc @@ -46,6 +46,8 @@ std::string Settings::ToString() const { stream << "enable_dart_profiling: " << enable_dart_profiling << std::endl; stream << "disable_dart_asserts: " << disable_dart_asserts << std::endl; stream << "enable_observatory: " << enable_observatory << std::endl; + stream << "enable_observatory_publication: " << enable_observatory_publication + << std::endl; stream << "observatory_host: " << observatory_host << std::endl; stream << "observatory_port: " << observatory_port << std::endl; stream << "use_test_fonts: " << use_test_fonts << std::endl; diff --git a/common/settings.h b/common/settings.h index 46f88399b407e..d85506b946e25 100644 --- a/common/settings.h +++ b/common/settings.h @@ -126,6 +126,11 @@ struct Settings { // Whether the Dart VM service should be enabled. bool enable_observatory = false; + // Whether to publish the observatory URL over mDNS. + // On iOS 14 this prompts a local network permission dialog, + // which cannot be accepted or dismissed in a CI environment. + bool enable_observatory_publication = true; + // The IP address to which the Dart VM service is bound. std::string observatory_host; diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 1b2398b942ff0..3f80830136cdc 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -221,6 +221,10 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.enable_observatory = !command_line.HasOption(FlagForSwitch(Switch::DisableObservatory)); + // Enable mDNS Observatory Publication + settings.enable_observatory_publication = !command_line.HasOption( + FlagForSwitch(Switch::DisableObservatoryPublication)); + // Set Observatory Host if (command_line.HasOption(FlagForSwitch(Switch::DeviceObservatoryHost))) { command_line.GetOptionValue(FlagForSwitch(Switch::DeviceObservatoryHost), diff --git a/shell/common/switches.h b/shell/common/switches.h index 260214db4e8a0..9a9dea9401d8f 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -79,6 +79,9 @@ DEF_SWITCH(DisableObservatory, "disable-observatory", "Disable the Dart Observatory. The observatory is never available " "in release mode.") +DEF_SWITCH(DisableObservatoryPublication, + "disable-observatory-publication", + "Disable mDNS Dart Observatory publication.") DEF_SWITCH(IPv6, "ipv6", "Bind to the IPv6 localhost address for the Dart Observatory. " diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 2592537749efa..5248f59a98b34 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -547,7 +547,8 @@ - (BOOL)createShell:(NSString*)entrypoint if (!_platformViewsController) { _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); } - _publisher.reset([[FlutterObservatoryPublisher alloc] init]); + _publisher.reset([[FlutterObservatoryPublisher alloc] + initWithEnableObservatoryPublication:settings.enable_observatory_publication]); [self maybeSetupPlatformViewChannels]; _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); if (profilerEnabled) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h index 387176f76e25e..a00c17ad89245 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h @@ -9,6 +9,11 @@ @interface FlutterObservatoryPublisher : NSObject +- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + @property(nonatomic, readonly) NSURL* url; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm index 072a244a967fc..c9b124ba4e264 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.mm @@ -9,6 +9,9 @@ #if FLUTTER_RELEASE @implementation FlutterObservatoryPublisher +- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication { + return [super init]; +} @end #else // FLUTTER_RELEASE @@ -37,26 +40,23 @@ @implementation FlutterObservatoryPublisher #include #include "flutter/fml/logging.h" -#include "flutter/fml/make_copyable.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" -#include "flutter/fml/task_runner.h" #include "flutter/runtime/dart_service_isolate.h" @protocol FlutterObservatoryPublisherDelegate -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner; -- (void)publishServiceProtocolPort:(NSString*)uri; +- (void)publishServiceProtocolPort:(NSURL*)uri; - (void)stopService; - -@property(readonly) fml::scoped_nsobject url; @end @interface FlutterObservatoryPublisher () -- (NSData*)createTxtData:(NSURL*)url; ++ (NSData*)createTxtData:(NSURL*)url; -@property(readonly) NSString* serviceName; +@property(readonly, class) NSString* serviceName; @property(readonly) fml::scoped_nsobject> delegate; +@property(nonatomic, readwrite) NSURL* url; +@property(readonly) BOOL enableObservatoryPublication; @end @@ -68,19 +68,9 @@ @interface ObservatoryDNSServiceDelegate : NSObject _owner; DNSServiceRef _dnsServiceRef; } -@synthesize url; - -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { - self = [super init]; - NSAssert(self, @"Super must not return null on init."); - _owner.reset([owner retain]); - return self; -} - - (void)stopService { if (_dnsServiceRef) { DNSServiceRefDeallocate(_dnsServiceRef); @@ -88,11 +78,7 @@ - (void)stopService { } } -- (void)publishServiceProtocolPort:(NSString*)uri { - // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port - // number. - url.reset([[NSURL alloc] initWithString:uri]); - +- (void)publishServiceProtocolPort:(NSURL*)url { DNSServiceFlags flags = kDNSServiceFlagsDefault; #if TARGET_IPHONE_SIMULATOR // Simulator needs to use local loopback explicitly to work. @@ -105,11 +91,11 @@ - (void)publishServiceProtocolPort:(NSString*)uri { const char* domain = "local."; // default domain uint16_t port = [[url port] unsignedShortValue]; - NSData* txtData = [_owner createTxtData:url.get()]; - int err = - DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, - [_owner.get().serviceName UTF8String], registrationType, domain, NULL, - htons(port), txtData.length, txtData.bytes, registrationCallback, NULL); + NSData* txtData = [FlutterObservatoryPublisher createTxtData:url]; + int err = DNSServiceRegister(&_dnsServiceRef, flags, interfaceIndex, + FlutterObservatoryPublisher.serviceName.UTF8String, registrationType, + domain, NULL, htons(port), txtData.length, txtData.bytes, + registrationCallback, NULL); if (err != 0) { FML_LOG(ERROR) << "Failed to register observatory port with mDNS with error " << err << "."; @@ -122,8 +108,8 @@ - (void)publishServiceProtocolPort:(NSString*)uri { << "to the 'NSBonjourServices' key in your Info.plist for the Debug/" << "Profile configurations. " << "For more information, see " - // Update link to a specific header as needed. - << "https://flutter.dev/docs/development/add-to-app/ios/project-setup"; + << "https://flutter.dev/docs/development/add-to-app/ios/" + "project-setup#local-network-privacy-permissions"; } } else { DNSServiceSetDispatchQueue(_dnsServiceRef, dispatch_get_main_queue()); @@ -162,34 +148,21 @@ static void DNSSD_API registrationCallback(DNSServiceRef sdRef, @end @implementation ObservatoryNSNetServiceDelegate { - fml::scoped_nsobject _owner; fml::scoped_nsobject _netService; } -@synthesize url; - -- (instancetype)initWithOwner:(FlutterObservatoryPublisher*)owner { - self = [super init]; - NSAssert(self, @"Super must not return null on init."); - _owner.reset([owner retain]); - return self; -} - - (void)stopService { [_netService.get() stop]; [_netService.get() setDelegate:nil]; } -- (void)publishServiceProtocolPort:(NSString*)uri { - // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port - // number. - url.reset([[NSURL alloc] initWithString:uri]); - - NSNetService* netServiceTmp = [[NSNetService alloc] initWithDomain:@"local." - type:@"_dartobservatory._tcp." - name:_owner.get().serviceName - port:[[url port] intValue]]; - [netServiceTmp setTXTRecordData:[_owner createTxtData:url.get()]]; +- (void)publishServiceProtocolPort:(NSURL*)url { + NSNetService* netServiceTmp = + [[NSNetService alloc] initWithDomain:@"local." + type:@"_dartobservatory._tcp." + name:FlutterObservatoryPublisher.serviceName + port:[[url port] intValue]]; + [netServiceTmp setTXTRecordData:[FlutterObservatoryPublisher createTxtData:url]]; _netService.reset(netServiceTmp); [_netService.get() setDelegate:self]; [_netService.get() publish]; @@ -211,19 +184,16 @@ @implementation FlutterObservatoryPublisher { std::unique_ptr> _weakFactory; } -- (NSURL*)url { - return [_delegate.get().url autorelease]; -} - -- (instancetype)init { +- (instancetype)initWithEnableObservatoryPublication:(BOOL)enableObservatoryPublication { self = [super init]; NSAssert(self, @"Super must not return null on init."); if (@available(iOS 9.3, *)) { - _delegate.reset([[ObservatoryDNSServiceDelegate alloc] initWithOwner:self]); + _delegate.reset([[ObservatoryDNSServiceDelegate alloc] init]); } else { - _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] initWithOwner:self]); + _delegate.reset([[ObservatoryNSNetServiceDelegate alloc] init]); } + _enableObservatoryPublication = enableObservatoryPublication; _weakFactory = std::make_unique>(self); fml::MessageLoop::EnsureInitializedForCurrentThread(); @@ -233,9 +203,15 @@ - (instancetype)init { runner = fml::MessageLoop::GetCurrent().GetTaskRunner()](const std::string& uri) { if (!uri.empty()) { runner->PostTask([weak, uri]() { + // uri comes in as something like 'http://127.0.0.1:XXXXX/' where XXXXX is the port + // number. if (weak) { - [[weak.get() delegate] - publishServiceProtocolPort:[NSString stringWithUTF8String:uri.c_str()]]; + NSURL* url = + [[NSURL alloc] initWithString:[NSString stringWithUTF8String:uri.c_str()]]; + weak.get().url = url; + if (weak.get().enableObservatoryPublication) { + [[weak.get() delegate] publishServiceProtocolPort:url]; + } } }); } @@ -244,11 +220,11 @@ - (instancetype)init { return self; } -- (NSString*)serviceName { ++ (NSString*)serviceName { return NSBundle.mainBundle.bundleIdentifier; } -- (NSData*)createTxtData:(NSURL*)url { ++ (NSData*)createTxtData:(NSURL*)url { // Check to see if there's an authentication code. If there is, we'll provide // it as a txt record so flutter tools can establish a connection. NSString* path = [[url path] substringFromIndex:MIN(1, [[url path] length])]; @@ -261,6 +237,7 @@ - (NSData*)createTxtData:(NSURL*)url { - (void)dealloc { [_delegate stopService]; + [_url release]; flutter::DartServiceIsolate::RemoveServerStatusCallback(std::move(_callbackHandle)); [super dealloc]; From 2eff20345de6d6d14eea5a93adeb63a69f34a59d Mon Sep 17 00:00:00 2001 From: Anthony Robledo Date: Thu, 15 Oct 2020 18:00:59 -0400 Subject: [PATCH 123/219] begin to add uniformData --- lib/ui/painting.dart | 3 +++ lib/ui/painting/fragment_shader.cc | 16 +++++++++++++++- lib/ui/painting/fragment_shader.h | 6 ++++++ lib/ui/spirv/transpiler.cc | 1 + 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index d6b9ea1295e66..16c4723f1bb4b 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3504,6 +3504,9 @@ class FragmentShader extends Shader { /// TODO(clocksmith): Public Docs. void setImage(Image image, TileMode tmx, TileMode tmy, Float64List matrix4) native 'FragmentShader_setImage'; + + /// TODO(clocksmith): Public Docs. + void refresh() native 'FragmentShader_refresh'; } /// A shader (as used by [Paint.shader]) that tiles an image. diff --git a/lib/ui/painting/fragment_shader.cc b/lib/ui/painting/fragment_shader.cc index a53f83d06e024..a4985517d8e7b 100644 --- a/lib/ui/painting/fragment_shader.cc +++ b/lib/ui/painting/fragment_shader.cc @@ -28,7 +28,8 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, FragmentShader); V(FragmentShader, initWithSource) \ V(FragmentShader, initWithSPIRV) \ V(FragmentShader, setTime) \ - V(FragmentShader, setImage) + V(FragmentShader, setImage) \ + V(FragmentShader, refresh) FOR_EACH_BINDING(DART_NATIVE_CALLBACK) @@ -47,6 +48,7 @@ void FragmentShader::initWithSPIRV(const tonic::Uint8List& data) { return; } auto sksl = transpiler->GetSkSL(); + uniformData_ = std::make_unique(Dart_NewTypedData(Dart_TypedData_kFloat32, transpiler->GetUniformBufferSize())); initWithSource(sksl); } @@ -64,11 +66,23 @@ void FragmentShader::setImage(CanvasImage* image, setShader(); } +void FragmentShader::refresh() { + sk_sp uniforms = SkData::MakeWithoutCopy(uniformData_->data(), uniformData_->num_elements() * 4); + set_shader(UIDartState::CreateGPUObject(runtime_effect_->makeShader(uniforms, nullptr, 0, nullptr, false))); + // set_shader(UIDartState::CreateGPUObject(builder_->makeShader(nullptr, false))); +} + +// FragmentShader::uniformData() { +// return _uniformData; +// } + void FragmentShader::initEffect(SkString sksl) { SkString err; std::tie(runtime_effect_, err) = SkRuntimeEffect::Make(sksl); if (!runtime_effect_) { FML_DLOG(ERROR) << "Invalid SKSL:\n" << sksl.c_str() << "\nSKSL Error:\n" << err.c_str(); + } else { + FML_DLOG(ERROR) << "Valid SKSL:\n" << sksl.c_str(); } } diff --git a/lib/ui/painting/fragment_shader.h b/lib/ui/painting/fragment_shader.h index 24439b68c513e..fcc575b141957 100644 --- a/lib/ui/painting/fragment_shader.h +++ b/lib/ui/painting/fragment_shader.h @@ -41,6 +41,10 @@ class FragmentShader : public Shader { SkTileMode tmy, const tonic::Float64List& matrix4); + void refresh(); + + // uniformData(); + static void RegisterNatives(tonic::DartLibraryNatives* natives); private: @@ -53,6 +57,8 @@ class FragmentShader : public Shader { float t_; sk_sp input_; + std::unique_ptr uniformData_; + // Since the sksl cannot be updated, the effect can be // created once and re-used. sk_sp runtime_effect_; diff --git a/lib/ui/spirv/transpiler.cc b/lib/ui/spirv/transpiler.cc index 01fc31329a0ab..3b1d8cf8e2746 100644 --- a/lib/ui/spirv/transpiler.cc +++ b/lib/ui/spirv/transpiler.cc @@ -58,6 +58,7 @@ class TranspilerImpl : public Transpiler { private: std::string ResolveName(uint32_t id); std::string ResolveType(uint32_t id); + size_t ResolveTypeFloatCount(uint32_t id); std::string ResolveGLSLName(uint32_t id); const spv_context spv_context_; From 8ec9b3a16217abf4314db12a1435d3ea13f0e377 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 15 Oct 2020 19:14:23 -0400 Subject: [PATCH 124/219] Update more class names from GrContext to GrDirectContext (#21864) This name change has to do with SkDeferredDisplayList, which Flutter does not use. As far as Flutter is concerned, this is a no-op. --- shell/platform/darwin/ios/ios_external_texture_gl.h | 4 ++-- shell/platform/darwin/ios/ios_external_texture_gl.mm | 4 ++-- shell/platform/fuchsia/flutter/compositor_context.cc | 2 +- shell/platform/fuchsia/flutter/vulkan_surface_producer.cc | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/ios/ios_external_texture_gl.h b/shell/platform/darwin/ios/ios_external_texture_gl.h index 09220a3d3564b..b7ad8b4daefd5 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl.h +++ b/shell/platform/darwin/ios/ios_external_texture_gl.h @@ -60,9 +60,9 @@ class IOSExternalTextureGL final : public Texture { void CreateRGBATextureFromPixelBuffer(); - sk_sp CreateImageFromYUVTextures(GrContext* context, const SkRect& bounds); + sk_sp CreateImageFromYUVTextures(GrDirectContext* context, const SkRect& bounds); - sk_sp CreateImageFromRGBATexture(GrContext* context, const SkRect& bounds); + sk_sp CreateImageFromRGBATexture(GrDirectContext* context, const SkRect& bounds); FML_DISALLOW_COPY_AND_ASSIGN(IOSExternalTextureGL); }; diff --git a/shell/platform/darwin/ios/ios_external_texture_gl.mm b/shell/platform/darwin/ios/ios_external_texture_gl.mm index 688e3072ed130..1429add5f283e 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl.mm +++ b/shell/platform/darwin/ios/ios_external_texture_gl.mm @@ -95,7 +95,7 @@ } } -sk_sp IOSExternalTextureGL::CreateImageFromRGBATexture(GrContext* context, +sk_sp IOSExternalTextureGL::CreateImageFromRGBATexture(GrDirectContext* context, const SkRect& bounds) { GrGLTextureInfo textureInfo = {CVOpenGLESTextureGetTarget(texture_ref_), CVOpenGLESTextureGetName(texture_ref_), GL_RGBA8_OES}; @@ -106,7 +106,7 @@ return image; } -sk_sp IOSExternalTextureGL::CreateImageFromYUVTextures(GrContext* context, +sk_sp IOSExternalTextureGL::CreateImageFromYUVTextures(GrDirectContext* context, const SkRect& bounds) { GrGLTextureInfo yTextureInfo = {CVOpenGLESTextureGetTarget(y_texture_ref_), CVOpenGLESTextureGetName(y_texture_ref_), GR_GL_LUMINANCE8}; diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc index 599ebf77b0dd2..005a9cca0705b 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.cc +++ b/shell/platform/fuchsia/flutter/compositor_context.cc @@ -14,7 +14,7 @@ namespace flutter_runner { class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { public: ScopedFrame(CompositorContext& context, - GrContext* gr_context, + GrDirectContext* gr_context, SkCanvas* canvas, flutter::ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc index 36eeadd85afa3..ed69ce441eac5 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc +++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc @@ -160,7 +160,7 @@ void VulkanSurfaceProducer::OnSurfacesPresented( // Do a single flush for all canvases derived from the context. { - TRACE_EVENT0("flutter", "GrContext::flushAndSignalSemaphores"); + TRACE_EVENT0("flutter", "GrDirectContext::flushAndSignalSemaphores"); context_->flushAndSubmit(); } From ddf978b8b9d1dae0ac8c49f35a65ce9299fd1612 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Thu, 15 Oct 2020 16:33:51 -0700 Subject: [PATCH 125/219] Add more TextStyle support to Paragraph in CanvasKit mode (#21629) * WIP on Paragraph * WIP skparagraph * Add more Paragraph features in CanvasKit mode * Fix addRoundRect test * Respond to review comments * Remove unused (and potentially harmful) getters from Sk classes --- .../lib/src/engine/canvaskit/canvas.dart | 15 +- .../src/engine/canvaskit/canvaskit_api.dart | 390 ++++++++------- .../src/engine/canvaskit/initialization.dart | 2 +- lib/web_ui/lib/src/engine/canvaskit/path.dart | 26 +- .../engine/canvaskit/picture_recorder.dart | 2 +- lib/web_ui/lib/src/engine/canvaskit/text.dart | 329 +++++++++++-- .../test/canvaskit/canvaskit_api_test.dart | 449 +++++++++--------- 7 files changed, 744 insertions(+), 469 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart index 3426253473469..06d325e3c1dfa 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart @@ -176,8 +176,7 @@ class CkCanvas { skCanvas.drawPicture(picture.skiaObject.skiaObject); } - void drawPoints(CkPaint paint, ui.PointMode pointMode, - Float32List points) { + void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) { skCanvas.drawPoints( toSkPointMode(pointMode), points, @@ -230,24 +229,24 @@ class CkCanvas { void saveLayer(ui.Rect bounds, CkPaint paint) { skCanvas.saveLayer( - toSkRect(bounds), paint.skiaObject, + toSkRect(bounds), + null, + null, ); } void saveLayerWithoutBounds(CkPaint paint) { - final SkCanvasSaveLayerWithoutBoundsOverload override = skCanvas as SkCanvasSaveLayerWithoutBoundsOverload; - override.saveLayer(paint.skiaObject); + skCanvas.saveLayer(paint.skiaObject, null, null, null); } void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { - final SkCanvasSaveLayerWithFilterOverload override = skCanvas as SkCanvasSaveLayerWithFilterOverload; final CkImageFilter skImageFilter = filter as CkImageFilter; - return override.saveLayer( + return skCanvas.saveLayer( null, + toSkRect(bounds), skImageFilter.skiaObject, 0, - toSkRect(bounds), ); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 1252cba16e436..09d67dbe25cbe 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -79,6 +79,10 @@ class CanvasKit { external int get LineThroughDecoration; // End of text decoration enum. + external SkTextDecorationStyleEnum get DecorationStyle; + external SkTextBaselineEnum get TextBaseline; + external SkPlaceholderAlignmentEnum get PlaceholderAlignment; + external SkFontMgrNamespace get SkFontMgr; external TypefaceFontProviderNamespace get TypefaceFontProvider; external int GetWebGLContext( @@ -1015,12 +1019,12 @@ class SkPath { external SkPath([SkPath? other]); external void setFillType(SkFillType fillType); external void addArc( - SkRect oval, + Float32List oval, double startAngleDegrees, double sweepAngleDegrees, ); external void addOval( - SkRect oval, + Float32List oval, bool counterClockWise, int startIndex, ); @@ -1041,16 +1045,15 @@ class SkPath { Float32List points, bool close, ); - external void addRoundRect( - SkRect outerRect, - Float32List radii, + external void addRRect( + Float32List rrect, bool counterClockWise, ); external void addRect( - SkRect rect, + Float32List rect, ); external void arcToOval( - SkRect oval, + Float32List oval, double startAngleDegrees, double sweepAngleDegrees, bool forceMoveTo, @@ -1084,7 +1087,7 @@ class SkPath { double x3, double y3, ); - external SkRect getBounds(); + external Float32List getBounds(); external void lineTo(double x, double y); external void moveTo(double x, double y); external void quadTo( @@ -1156,88 +1159,46 @@ class SkContourMeasure { external double length(); } -@JS() -@anonymous -class SkRect { - external factory SkRect({ - required double fLeft, - required double fTop, - required double fRight, - required double fBottom, - }); - external double get fLeft; - external double get fTop; - external double get fRight; - external double get fBottom; -} - -extension SkRectExtensions on SkRect { - ui.Rect toRect() { - return ui.Rect.fromLTRB( - this.fLeft, - this.fTop, - this.fRight, - this.fBottom, - ); - } -} - -SkRect toSkRect(ui.Rect rect) { - return SkRect( - fLeft: rect.left, - fTop: rect.top, - fRight: rect.right, - fBottom: rect.bottom, - ); -} - -@JS() -@anonymous -class SkRRect { - external factory SkRRect({ - required SkRect rect, - required double rx1, - required double ry1, - required double rx2, - required double ry2, - required double rx3, - required double ry3, - required double rx4, - required double ry4, - }); - - external SkRect get rect; - external double get rx1; - external double get ry1; - external double get rx2; - external double get ry2; - external double get rx3; - external double get ry3; - external double get rx4; - external double get ry4; -} - -SkRRect toSkRRect(ui.RRect rrect) { - return SkRRect( - rect: toOuterSkRect(rrect), - rx1: rrect.tlRadiusX, - ry1: rrect.tlRadiusY, - rx2: rrect.trRadiusX, - ry2: rrect.trRadiusY, - rx3: rrect.brRadiusX, - ry3: rrect.brRadiusY, - rx4: rrect.blRadiusX, - ry4: rrect.blRadiusY, - ); -} - -SkRect toOuterSkRect(ui.RRect rrect) { - return SkRect( - fLeft: rrect.left, - fTop: rrect.top, - fRight: rrect.right, - fBottom: rrect.bottom, - ); +// TODO(hterkelsen): Use a shared malloc'ed array for performance. +Float32List toSkRect(ui.Rect rect) { + final Float32List skRect = Float32List(4); + skRect[0] = rect.left; + skRect[1] = rect.top; + skRect[2] = rect.right; + skRect[3] = rect.bottom; + return skRect; +} + +ui.Rect fromSkRect(Float32List skRect) { + return ui.Rect.fromLTRB(skRect[0], skRect[1], skRect[2], skRect[3]); +} + +// TODO(hterkelsen): Use a shared malloc'ed array for performance. +Float32List toSkRRect(ui.RRect rrect) { + final Float32List skRRect = Float32List(12); + skRRect[0] = rrect.left; + skRRect[1] = rrect.top; + skRRect[2] = rrect.right; + skRRect[3] = rrect.bottom; + skRRect[4] = rrect.tlRadiusX; + skRRect[5] = rrect.tlRadiusY; + skRRect[6] = rrect.trRadiusX; + skRRect[7] = rrect.trRadiusY; + skRRect[8] = rrect.brRadiusX; + skRRect[9] = rrect.brRadiusY; + skRRect[10] = rrect.blRadiusX; + skRRect[11] = rrect.blRadiusY; + return skRRect; +} + +// TODO(hterkelsen): Use a shared malloc'ed array for performance. +Float32List toOuterSkRect(ui.RRect rrect) { + final Float32List skRect = Float32List(4); + skRect[0] = rrect.left; + skRect[1] = rrect.top; + skRect[2] = rrect.right; + skRect[3] = rrect.bottom; + return skRect; } /// Encodes a list of offsets to CanvasKit-compatible point array. @@ -1263,9 +1224,9 @@ List rawPointsToSkPoints2d(Float32List points) { assert(points.length % 2 == 0); final int pointLength = points.length ~/ 2; final List result = []; - for (var i = 0; i < pointLength; i++) { - var x = i * 2; - var y = x + 1; + for (int i = 0; i < pointLength; i++) { + int x = i * 2; + int y = x + 1; final Float32List skPoint = Float32List(2); skPoint[0] = points[x]; skPoint[1] = points[y]; @@ -1277,7 +1238,7 @@ List rawPointsToSkPoints2d(Float32List points) { List toSkPoints2d(List offsets) { final int len = offsets.length; final List result = []; - for (var i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { final ui.Offset offset = offsets[i]; final Float32List skPoint = Float32List(2); skPoint[0] = offset.dx; @@ -1299,7 +1260,7 @@ Uint16List toUint16List(List ints) { @JS('window.flutterCanvasKit.SkPictureRecorder') class SkPictureRecorder { external SkPictureRecorder(); - external SkCanvas beginRecording(SkRect bounds); + external SkCanvas beginRecording(Float32List bounds); external SkPicture finishRecordingAsPicture(); external void delete(); } @@ -1319,17 +1280,17 @@ class SkCanvas { bool doAntiAlias, ); external void clipRRect( - SkRRect rrect, + Float32List rrect, SkClipOp clipOp, bool doAntiAlias, ); external void clipRect( - SkRect rrect, + Float32List rrect, SkClipOp clipOp, bool doAntiAlias, ); external void drawArc( - SkRect oval, + Float32List oval, double startAngleDegrees, double sweepAngleDegrees, bool useCenter, @@ -1354,8 +1315,8 @@ class SkCanvas { SkBlendMode blendMode, ); external void drawDRRect( - SkRRect outer, - SkRRect inner, + Float32List outer, + Float32List inner, SkPaint paint, ); external void drawImage( @@ -1366,15 +1327,15 @@ class SkCanvas { ); external void drawImageRect( SkImage image, - SkRect src, - SkRect dst, + Float32List src, + Float32List dst, SkPaint paint, bool fastSample, ); external void drawImageNine( SkImage image, - SkRect center, - SkRect dst, + Float32List center, + Float32List dst, SkPaint paint, ); external void drawLine( @@ -1385,7 +1346,7 @@ class SkCanvas { SkPaint paint, ); external void drawOval( - SkRect rect, + Float32List rect, SkPaint paint, ); external void drawPaint( @@ -1401,11 +1362,11 @@ class SkCanvas { SkPaint paint, ); external void drawRRect( - SkRRect rrect, + Float32List rrect, SkPaint paint, ); external void drawRect( - SkRect rrect, + Float32List rrect, SkPaint paint, ); external void drawShadow( @@ -1425,8 +1386,10 @@ class SkCanvas { external int save(); external int getSaveCount(); external void saveLayer( - SkRect bounds, - SkPaint paint, + SkPaint? paint, + Float32List? bounds, + SkImageFilter? backdrop, + int? flags, ); external void restore(); external void restoreToCount(int count); @@ -1448,23 +1411,6 @@ class SkCanvas { ); } -@JS() -@anonymous -class SkCanvasSaveLayerWithoutBoundsOverload { - external void saveLayer(SkPaint paint); -} - -@JS() -@anonymous -class SkCanvasSaveLayerWithFilterOverload { - external void saveLayer( - SkPaint? paint, - SkImageFilter? imageFilter, - int flags, - SkRect rect, - ); -} - @JS() @anonymous class SkPicture { @@ -1493,6 +1439,7 @@ class SkParagraphBuilder { external void pushPaintStyle( SkTextStyle textStyle, SkPaint foreground, SkPaint background); external void pop(); + external void addPlaceholder(SkPlaceholderStyleProperties placeholderStyle); external SkParagraph build(); external void delete(); } @@ -1504,69 +1451,162 @@ class SkParagraphStyle {} @JS() @anonymous class SkParagraphStyleProperties { - external SkTextAlign? get textAlign; external set textAlign(SkTextAlign? value); - - external SkTextDirection? get textDirection; external set textDirection(SkTextDirection? value); - - external double? get heightMultiplier; external set heightMultiplier(double? value); - - external int? get textHeightBehavior; external set textHeightBehavior(int? value); - - external int? get maxLines; external set maxLines(int? value); - - external String? get ellipsis; external set ellipsis(String? value); - - external SkTextStyleProperties? get textStyle; external set textStyle(SkTextStyleProperties? value); + external set strutStyle(SkStrutStyleProperties? strutStyle); } @JS() class SkTextStyle {} +@JS() +class SkTextDecorationStyleEnum { + external SkTextDecorationStyle get Solid; + external SkTextDecorationStyle get Double; + external SkTextDecorationStyle get Dotted; + external SkTextDecorationStyle get Dashed; + external SkTextDecorationStyle get Wavy; +} + +@JS() +class SkTextDecorationStyle { + external int get value; +} + +final List _skTextDecorationStyles = + [ + canvasKit.DecorationStyle.Solid, + canvasKit.DecorationStyle.Double, + canvasKit.DecorationStyle.Dotted, + canvasKit.DecorationStyle.Dashed, + canvasKit.DecorationStyle.Wavy, +]; + +SkTextDecorationStyle toSkTextDecorationStyle(ui.TextDecorationStyle style) { + return _skTextDecorationStyles[style.index]; +} + +@JS() +class SkTextBaselineEnum { + external SkTextBaseline get Alphabetic; + external SkTextBaseline get Ideographic; +} + +@JS() +class SkTextBaseline { + external int get value; +} + +final List _skTextBaselines = [ + canvasKit.TextBaseline.Alphabetic, + canvasKit.TextBaseline.Ideographic, +]; + +SkTextBaseline toSkTextBaseline(ui.TextBaseline baseline) { + return _skTextBaselines[baseline.index]; +} + +@JS() +class SkPlaceholderAlignmentEnum { + external SkPlaceholderAlignment get Baseline; + external SkPlaceholderAlignment get AboveBaseline; + external SkPlaceholderAlignment get BelowBaseline; + external SkPlaceholderAlignment get Top; + external SkPlaceholderAlignment get Bottom; + external SkPlaceholderAlignment get Middle; +} + +@JS() +class SkPlaceholderAlignment { + external int get value; +} + +final List _skPlaceholderAlignments = + [ + canvasKit.PlaceholderAlignment.Baseline, + canvasKit.PlaceholderAlignment.AboveBaseline, + canvasKit.PlaceholderAlignment.BelowBaseline, + canvasKit.PlaceholderAlignment.Top, + canvasKit.PlaceholderAlignment.Bottom, + canvasKit.PlaceholderAlignment.Middle, +]; + +SkPlaceholderAlignment toSkPlaceholderAlignment( + ui.PlaceholderAlignment alignment) { + return _skPlaceholderAlignments[alignment.index]; +} + @JS() @anonymous class SkTextStyleProperties { - external Float32List? get backgroundColor; external set backgroundColor(Float32List? value); - - external Float32List? get color; external set color(Float32List? value); - - external Float32List? get foregroundColor; external set foregroundColor(Float32List? value); - - external int? get decoration; external set decoration(int? value); - - external double? get decorationThickness; external set decorationThickness(double? value); - - external double? get fontSize; + external set decorationColor(Float32List? value); + external set decorationStyle(SkTextDecorationStyle? value); + external set textBaseline(SkTextBaseline? value); external set fontSize(double? value); - - external List? get fontFamilies; + external set letterSpacing(double? value); + external set wordSpacing(double? value); + external set heightMultiplier(double? value); + external set locale(String? value); external set fontFamilies(List? value); + external set fontStyle(SkFontStyle? value); + external set shadows(List? value); + external set fontFeatures(List? value); +} - external SkFontStyle? get fontStyle; +@JS() +@anonymous +class SkStrutStyleProperties { + external set fontFamilies(List? value); external set fontStyle(SkFontStyle? value); + external set fontSize(double? value); + external set heightMultiplier(double? value); + external set leading(double? value); + external set strutEnabled(bool? value); + external set forceStrutHeight(bool? value); +} + +@JS() +@anonymous +class SkPlaceholderStyleProperties { + external set width(double? value); + external set height(double? value); + external set alignment(SkPlaceholderAlignment? value); + external set offset(double? value); + external set baseline(SkTextBaseline? value); } @JS() @anonymous class SkFontStyle { - external SkFontWeight? get weight; external set weight(SkFontWeight? value); - - external SkFontSlant? get slant; external set slant(SkFontSlant? value); } +@JS() +@anonymous +class SkTextShadow { + external set color(Float32List? value); + external set offset(Float32List? value); + external set blurRadius(double? value); +} + +@JS() +@anonymous +class SkFontFeature { + external set name(String? value); + external set value(int? value); +} + @JS() @anonymous class SkFontMgr { @@ -1591,12 +1631,13 @@ class SkParagraph { external double getMaxIntrinsicWidth(); external double getMinIntrinsicWidth(); external double getMaxWidth(); - external List getRectsForRange( + external List getRectsForRange( int start, int end, SkRectHeightStyle heightStyle, SkRectWidthStyle widthStyle, ); + external List getRectsForPlaceholders(); external SkTextPosition getGlyphPositionAtCoordinate( double x, double y, @@ -1649,7 +1690,8 @@ class TypefaceFontProviderNamespace { Timer? _skObjectCollector; List _skObjectDeleteQueue = []; -final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) { +final SkObjectFinalizationRegistry skObjectFinalizationRegistry = + SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) { _skObjectDeleteQueue.add(deletable); _skObjectCollector ??= _scheduleSkObjectCollection(); })); @@ -1668,19 +1710,20 @@ final SkObjectFinalizationRegistry skObjectFinalizationRegistry = SkObjectFinali /// yielding to the graphics system to render the frame on the screen if there /// is a large number of objects to delete, causing jank. Timer _scheduleSkObjectCollection() => Timer(Duration.zero, () { - html.window.performance.mark('SkObject collection-start'); - final int length = _skObjectDeleteQueue.length; - for (int i = 0; i < length; i++) { - _skObjectDeleteQueue[i].delete(); - } - _skObjectDeleteQueue = []; - - // Null out the timer so we can schedule a new one next time objects are - // scheduled for deletion. - _skObjectCollector = null; - html.window.performance.mark('SkObject collection-end'); - html.window.performance.measure('SkObject collection', 'SkObject collection-start', 'SkObject collection-end'); -}); + html.window.performance.mark('SkObject collection-start'); + final int length = _skObjectDeleteQueue.length; + for (int i = 0; i < length; i++) { + _skObjectDeleteQueue[i].delete(); + } + _skObjectDeleteQueue = []; + + // Null out the timer so we can schedule a new one next time objects are + // scheduled for deletion. + _skObjectCollector = null; + html.window.performance.mark('SkObject collection-end'); + html.window.performance.measure('SkObject collection', + 'SkObject collection-start', 'SkObject collection-end'); + }); /// Any Skia object that has a `delete` method. @JS() @@ -1716,7 +1759,8 @@ class SkObjectFinalizationRegistry { external Object? get _finalizationRegistryConstructor; /// Whether the current browser supports `FinalizationRegistry`. -bool browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null; +bool browserSupportsFinalizationRegistry = + _finalizationRegistryConstructor != null; @JS() class SkData { @@ -1741,7 +1785,7 @@ class SkImageInfo { external int get height; external bool get isEmpty; external bool get isOpaque; - external SkRect get bounds; + external Float32List get bounds; external int get width; external SkImageInfo makeAlphaType(SkAlphaType alphaType); external SkImageInfo makeColorSpace(SkColorSpace colorSpace); diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index c1d91ad525925..1bfe7cf97aa69 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -20,7 +20,7 @@ const bool canvasKitForceCpuOnly = /// NPM, update this URL to `https://unpkg.com/canvaskit-wasm@0.34.0/bin/`. const String canvasKitBaseUrl = String.fromEnvironment( 'FLUTTER_WEB_CANVASKIT_URL', - defaultValue: 'https://unpkg.com/canvaskit-wasm@0.17.3/bin/', + defaultValue: 'https://unpkg.com/canvaskit-wasm@0.18.1/bin/', ); /// Initialize CanvasKit. diff --git a/lib/web_ui/lib/src/engine/canvaskit/path.dart b/lib/web_ui/lib/src/engine/canvaskit/path.dart index 86a758baf15fb..810c76e386a49 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/path.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/path.dart @@ -11,11 +11,15 @@ part of engine; class CkPath implements ui.Path { final SkPath _skPath; - CkPath() : _skPath = SkPath(), _fillType = ui.PathFillType.nonZero { + CkPath() + : _skPath = SkPath(), + _fillType = ui.PathFillType.nonZero { _skPath.setFillType(toSkFillType(_fillType)); } - CkPath.from(CkPath other) : _skPath = SkPath(other._skPath), _fillType = other.fillType { + CkPath.from(CkPath other) + : _skPath = SkPath(other._skPath), + _fillType = other.fillType { _skPath.setFillType(toSkFillType(_fillType)); } @@ -89,22 +93,10 @@ class CkPath implements ui.Path { @override void addRRect(ui.RRect rrect) { - final SkFloat32List skRadii = mallocFloat32List(8); - final Float32List radii = skRadii.toTypedArray(); - radii[0] = rrect.tlRadiusX; - radii[1] = rrect.tlRadiusY; - radii[2] = rrect.trRadiusX; - radii[3] = rrect.trRadiusY; - radii[4] = rrect.brRadiusX; - radii[5] = rrect.brRadiusY; - radii[6] = rrect.blRadiusX; - radii[7] = rrect.blRadiusY; - _skPath.addRoundRect( - toOuterSkRect(rrect), - radii, + _skPath.addRRect( + toSkRRect(rrect), false, ); - freeFloat32List(skRadii); } @override @@ -195,7 +187,7 @@ class CkPath implements ui.Path { } @override - ui.Rect getBounds() => _skPath.getBounds().toRect(); + ui.Rect getBounds() => fromSkRect(_skPath.getBounds()); @override void lineTo(double x, double y) { diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart b/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart index fa6793d0f2297..126ffd97648de 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart @@ -13,7 +13,7 @@ class CkPictureRecorder implements ui.PictureRecorder { CkCanvas beginRecording(ui.Rect bounds) { _cullRect = bounds; final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder(); - final SkRect skRect = toSkRect(bounds); + final Float32List skRect = toSkRect(bounds); final SkCanvas skCanvas = recorder.beginRecording(skRect); return _recordingCanvas = CkCanvas(skCanvas); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 146a9f0baf4e4..a033f78f2e521 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -29,15 +29,23 @@ class CkParagraphStyle implements ui.ParagraphStyle { textHeightBehavior, fontWeight, fontStyle, + strutStyle, ellipsis, + locale, ) { _textDirection = textDirection ?? ui.TextDirection.ltr; _fontFamily = fontFamily; + _fontSize = fontSize; + _fontWeight = fontWeight; + _fontStyle = fontStyle; } SkParagraphStyle skParagraphStyle; ui.TextDirection? _textDirection; String? _fontFamily; + double? _fontSize; + ui.FontWeight? _fontWeight; + ui.FontStyle? _fontStyle; static SkTextStyleProperties toSkTextStyleProperties( String? fontFamily, @@ -63,6 +71,46 @@ class CkParagraphStyle implements ui.ParagraphStyle { return skTextStyle; } + static SkStrutStyleProperties toSkStrutStyleProperties(ui.StrutStyle value) { + EngineStrutStyle style = value as EngineStrutStyle; + final SkStrutStyleProperties skStrutStyle = SkStrutStyleProperties(); + if (style._fontFamily != null) { + final List fontFamilies = [style._fontFamily!]; + if (style._fontFamilyFallback != null) { + fontFamilies.addAll(style._fontFamilyFallback!); + } + skStrutStyle.fontFamilies = fontFamilies; + } else { + // If no strut font family is given, default to Roboto. + skStrutStyle.fontFamilies = ['Roboto']; + } + + if (style._fontSize != null) { + skStrutStyle.fontSize = style._fontSize; + } + + if (style._height != null) { + skStrutStyle.heightMultiplier = style._height; + } + + if (style._leading != null) { + skStrutStyle.leading = style._leading; + } + + if (style._fontWeight != null || style._fontStyle != null) { + skStrutStyle.fontStyle = + toSkFontStyle(style._fontWeight, style._fontStyle); + } + + if (style._forceStrutHeight != null) { + skStrutStyle.forceStrutHeight = style._forceStrutHeight; + } + + skStrutStyle.strutEnabled = true; + + return skStrutStyle; + } + static SkParagraphStyle toSkParagraphStyle( ui.TextAlign? textAlign, ui.TextDirection? textDirection, @@ -73,7 +121,9 @@ class CkParagraphStyle implements ui.ParagraphStyle { ui.TextHeightBehavior? textHeightBehavior, ui.FontWeight? fontWeight, ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, String? ellipsis, + ui.Locale? locale, ) { final SkParagraphStyleProperties properties = SkParagraphStyleProperties(); @@ -85,6 +135,10 @@ class CkParagraphStyle implements ui.ParagraphStyle { properties.textDirection = toSkTextDirection(textDirection); } + if (maxLines != null) { + properties.maxLines = maxLines; + } + if (height != null) { properties.heightMultiplier = height; } @@ -93,25 +147,52 @@ class CkParagraphStyle implements ui.ParagraphStyle { properties.textHeightBehavior = textHeightBehavior.encode(); } - if (maxLines != null) { - properties.maxLines = maxLines; - } - if (ellipsis != null) { properties.ellipsis = ellipsis; } + if (strutStyle != null) { + properties.strutStyle = toSkStrutStyleProperties(strutStyle); + } + properties.textStyle = toSkTextStyleProperties(fontFamily, fontSize, fontWeight, fontStyle); return canvasKit.ParagraphStyle(properties); } + + CkTextStyle getTextStyle() { + return CkTextStyle( + fontFamily: _fontFamily, + fontSize: _fontSize, + fontWeight: _fontWeight, + fontStyle: _fontStyle, + ); + } } class CkTextStyle implements ui.TextStyle { SkTextStyle skTextStyle; + + ui.Color? color; + ui.TextDecoration? decoration; + ui.Color? decorationColor; + ui.TextDecorationStyle? decorationStyle; + double? decorationThickness; + ui.FontWeight? fontWeight; + ui.FontStyle? fontStyle; + ui.TextBaseline? textBaseline; + String? fontFamily; + List? fontFamilyFallback; + double? fontSize; + double? letterSpacing; + double? wordSpacing; + double? height; + ui.Locale? locale; CkPaint? background; CkPaint? foreground; + List? shadows; + List? fontFeatures; factory CkTextStyle({ ui.Color? color, @@ -162,10 +243,38 @@ class CkTextStyle implements ui.TextStyle { properties.decorationThickness = decorationThickness; } + if (decorationColor != null) { + properties.decorationColor = makeFreshSkColor(decorationColor); + } + + if (decorationStyle != null) { + properties.decorationStyle = toSkTextDecorationStyle(decorationStyle); + } + + if (textBaseline != null) { + properties.textBaseline = toSkTextBaseline(textBaseline); + } + if (fontSize != null) { properties.fontSize = fontSize; } + if (letterSpacing != null) { + properties.letterSpacing = letterSpacing; + } + + if (wordSpacing != null) { + properties.wordSpacing = wordSpacing; + } + + if (height != null) { + properties.heightMultiplier = height; + } + + if (locale != null) { + properties.locale = locale.toLanguageTag(); + } + if (fontFamily == null || !skiaFontCollection.registeredFamilies.contains(fontFamily)) { fontFamily = 'Roboto'; @@ -187,21 +296,103 @@ class CkTextStyle implements ui.TextStyle { properties.foregroundColor = makeFreshSkColor(foreground.color); } - // TODO(hterkelsen): Add support for - // - decorationColor - // - decorationStyle - // - textBaseline - // - letterSpacing - // - wordSpacing - // - height - // - locale - // - shadows - // - fontFeatures + if (shadows != null) { + List ckShadows = []; + for (ui.Shadow shadow in shadows) { + final ckShadow = SkTextShadow(); + ckShadow.color = makeFreshSkColor(shadow.color); + ckShadow.offset = toSkPoint(shadow.offset); + ckShadow.blurRadius = shadow.blurRadius; + ckShadows.add(ckShadow); + } + properties.shadows = ckShadows; + } + + if (fontFeatures != null) { + List ckFontFeatures = []; + for (ui.FontFeature fontFeature in fontFeatures) { + SkFontFeature ckFontFeature = SkFontFeature(); + ckFontFeature.name = fontFeature.feature; + ckFontFeature.value = fontFeature.value; + ckFontFeatures.add(ckFontFeature); + } + properties.fontFeatures = ckFontFeatures; + } + return CkTextStyle._( - canvasKit.TextStyle(properties), foreground, background); + canvasKit.TextStyle(properties), + color, + decoration, + decorationColor, + decorationStyle, + decorationThickness, + fontWeight, + fontStyle, + textBaseline, + fontFamily, + fontFamilyFallback, + fontSize, + letterSpacing, + wordSpacing, + height, + locale, + background, + foreground, + shadows, + fontFeatures, + ); + } + + /// Merges this text style with [other] and returns the new text style. + /// + /// The values in this text style are used unless [other] specifically + /// overrides it. + CkTextStyle mergeWith(CkTextStyle other) { + return CkTextStyle( + color: other.color ?? color, + decoration: other.decoration ?? decoration, + decorationColor: other.decorationColor ?? decorationColor, + decorationStyle: other.decorationStyle ?? decorationStyle, + decorationThickness: other.decorationThickness ?? decorationThickness, + fontWeight: other.fontWeight ?? fontWeight, + fontStyle: other.fontStyle ?? fontStyle, + textBaseline: other.textBaseline ?? textBaseline, + fontFamily: other.fontFamily ?? fontFamily, + fontFamilyFallback: other.fontFamilyFallback ?? fontFamilyFallback, + fontSize: other.fontSize ?? fontSize, + letterSpacing: other.letterSpacing ?? letterSpacing, + wordSpacing: other.wordSpacing ?? wordSpacing, + height: other.height ?? height, + locale: other.locale ?? locale, + background: other.background ?? background, + foreground: other.foreground ?? foreground, + shadows: other.shadows ?? shadows, + fontFeatures: other.fontFeatures ?? fontFeatures, + ); } - CkTextStyle._(this.skTextStyle, this.foreground, this.background); + CkTextStyle._( + this.skTextStyle, + this.color, + this.decoration, + this.decorationColor, + this.decorationStyle, + this.decorationThickness, + this.fontWeight, + this.fontStyle, + this.textBaseline, + this.fontFamily, + this.fontFamilyFallback, + this.fontSize, + this.letterSpacing, + this.wordSpacing, + this.height, + this.locale, + this.background, + this.foreground, + this.shadows, + this.fontFeatures, + ); } SkFontStyle toSkFontStyle(ui.FontWeight? fontWeight, ui.FontStyle? fontStyle) { @@ -260,6 +451,9 @@ class CkParagraph extends ManagedSkiaObject case _ParagraphCommandType.pushStyle: builder.pushStyle(command.style!); break; + case _ParagraphCommandType.addPlaceholder: + builder._addPlaceholder(command.placeholderStyle!); + break; } } @@ -304,10 +498,10 @@ class CkParagraph extends ManagedSkiaObject @override double get width => skiaObject.getMaxWidth(); - // TODO(hterkelsen): Implement placeholders once it's in CanvasKit @override List getBoxesForPlaceholders() { - return const []; + List> skRects = skiaObject.getRectsForPlaceholders(); + return skRectsToTextBoxes(skRects); } @override @@ -321,22 +515,26 @@ class CkParagraph extends ManagedSkiaObject return const []; } - List skRects = skiaObject.getRectsForRange( + List> skRects = skiaObject.getRectsForRange( start, end, toSkRectHeightStyle(boxHeightStyle), toSkRectWidthStyle(boxWidthStyle), ); + return skRectsToTextBoxes(skRects); + } + + List skRectsToTextBoxes(List> skRects) { List result = []; for (int i = 0; i < skRects.length; i++) { - final SkRect rect = skRects[i]; + final List rect = skRects[i]; result.add(ui.TextBox.fromLTRBD( - rect.fLeft, - rect.fTop, - rect.fRight, - rect.fBottom, + rect[0], + rect[1], + rect[2], + rect[3], _paragraphStyle._textDirection!, )); } @@ -404,16 +602,21 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { final SkParagraphBuilder _paragraphBuilder; final CkParagraphStyle _style; final List<_ParagraphCommand> _commands; + int _placeholderCount; + final List _placeholderScales; + final List _styleStack; CkParagraphBuilder(ui.ParagraphStyle style) : _commands = <_ParagraphCommand>[], _style = style as CkParagraphStyle, + _placeholderCount = 0, + _placeholderScales = [], + _styleStack = [], _paragraphBuilder = canvasKit.ParagraphBuilder.MakeFromFontProvider( style.skParagraphStyle, skiaFontCollection.fontProvider, ); - // TODO(hterkelsen): Implement placeholders. @override void addPlaceholder( double width, @@ -423,7 +626,44 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { double? baselineOffset, ui.TextBaseline? baseline, }) { - throw UnimplementedError('addPlaceholder'); + // Require a baseline to be specified if using a baseline-based alignment. + assert((alignment == ui.PlaceholderAlignment.aboveBaseline || + alignment == ui.PlaceholderAlignment.belowBaseline || + alignment == ui.PlaceholderAlignment.baseline) + ? baseline != null + : true); + + _placeholderCount++; + _placeholderScales.add(scale); + SkPlaceholderStyleProperties placeholderStyle = toSkPlaceholderStyle( + width * scale, + height * scale, + alignment, + (baselineOffset ?? height) * scale, + baseline ?? ui.TextBaseline.alphabetic, + ); + _addPlaceholder(placeholderStyle); + } + + void _addPlaceholder(SkPlaceholderStyleProperties placeholderStyle) { + _commands.add(_ParagraphCommand.addPlaceholder(placeholderStyle)); + _paragraphBuilder.addPlaceholder(placeholderStyle); + } + + static SkPlaceholderStyleProperties toSkPlaceholderStyle( + double width, + double height, + ui.PlaceholderAlignment alignment, + double baselineOffset, + ui.TextBaseline baseline, + ) { + final properties = SkPlaceholderStyleProperties(); + properties.width = width; + properties.height = height; + properties.alignment = toSkPlaceholderAlignment(alignment); + properties.offset = baselineOffset; + properties.baseline = toSkTextBaseline(baseline); + return properties; } @override @@ -446,22 +686,28 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { } @override - int get placeholderCount => throw UnimplementedError('placeholderCount'); + int get placeholderCount => _placeholderCount; - // TODO(hterkelsen): Implement this once CanvasKit exposes placeholders. @override - List get placeholderScales => const []; + List get placeholderScales => _placeholderScales; @override void pop() { _commands.add(const _ParagraphCommand.pop()); + _styleStack.removeLast(); _paragraphBuilder.pop(); } + CkTextStyle _peekStyle() => + _styleStack.isEmpty ? _style.getTextStyle() : _styleStack.last; + @override void pushStyle(ui.TextStyle style) { - final CkTextStyle skStyle = style as CkTextStyle; - _commands.add(_ParagraphCommand.pushStyle(skStyle)); + final CkTextStyle baseStyle = _peekStyle(); + final CkTextStyle ckStyle = style as CkTextStyle; + final CkTextStyle skStyle = baseStyle.mergeWith(ckStyle); + _styleStack.add(skStyle); + _commands.add(_ParagraphCommand.pushStyle(ckStyle)); if (skStyle.foreground != null || skStyle.background != null) { final SkPaint foreground = skStyle.foreground?.skiaObject ?? SkPaint(); final SkPaint background = skStyle.background?.skiaObject ?? SkPaint(); @@ -477,20 +723,33 @@ class _ParagraphCommand { final _ParagraphCommandType type; final String? text; final CkTextStyle? style; + final SkPlaceholderStyleProperties? placeholderStyle; - const _ParagraphCommand._(this.type, this.text, this.style); + const _ParagraphCommand._( + this.type, + this.text, + this.style, + this.placeholderStyle, + ); const _ParagraphCommand.addText(String text) - : this._(_ParagraphCommandType.addText, text, null); + : this._(_ParagraphCommandType.addText, text, null, null); - const _ParagraphCommand.pop() : this._(_ParagraphCommandType.pop, null, null); + const _ParagraphCommand.pop() + : this._(_ParagraphCommandType.pop, null, null, null); const _ParagraphCommand.pushStyle(CkTextStyle style) - : this._(_ParagraphCommandType.pushStyle, null, style); + : this._(_ParagraphCommandType.pushStyle, null, style, null); + + const _ParagraphCommand.addPlaceholder( + SkPlaceholderStyleProperties placeholderStyle) + : this._( + _ParagraphCommandType.addPlaceholder, null, null, placeholderStyle); } enum _ParagraphCommandType { addText, pop, pushStyle, + addPlaceholder, } diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index b03f3526db149..28e1c11198468 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -51,7 +51,7 @@ void testMain() { _toSkPointTests(); _toSkColorStopsTests(); _toSkMatrixFromFloat32Tests(); - _skSkRectTests(); + _toSkRectTests(); _skVerticesTests(); group('SkPath', () { _pathTests(); @@ -59,7 +59,10 @@ void testMain() { group('SkCanvas', () { _canvasTests(); }); - // TODO: https://github.com/flutter/flutter/issues/60040 + group('SkParagraph', () { + _textStyleTests(); + }); + // TODO: https://github.com/flutter/flutter/issues/60040 }, skip: isIosSafari); } @@ -203,11 +206,13 @@ void _fillTypeTests() { void _pathOpTests() { test('path op mapping is correct', () { - expect(canvasKit.PathOp.Difference.value, ui.PathOperation.difference.index); + expect( + canvasKit.PathOp.Difference.value, ui.PathOperation.difference.index); expect(canvasKit.PathOp.Intersect.value, ui.PathOperation.intersect.index); expect(canvasKit.PathOp.Union.value, ui.PathOperation.union.index); expect(canvasKit.PathOp.XOR.value, ui.PathOperation.xor.index); - expect(canvasKit.PathOp.ReverseDifference.value, ui.PathOperation.reverseDifference.index); + expect(canvasKit.PathOp.ReverseDifference.value, + ui.PathOperation.reverseDifference.index); }); test('ui.PathOperation converts to SkPathOp', () { @@ -269,8 +274,10 @@ void _pointModeTests() { void _vertexModeTests() { test('vertex mode mapping is correct', () { expect(canvasKit.VertexMode.Triangles.value, ui.VertexMode.triangles.index); - expect(canvasKit.VertexMode.TrianglesStrip.value, ui.VertexMode.triangleStrip.index); - expect(canvasKit.VertexMode.TriangleFan.value, ui.VertexMode.triangleFan.index); + expect(canvasKit.VertexMode.TrianglesStrip.value, + ui.VertexMode.triangleStrip.index); + expect(canvasKit.VertexMode.TriangleFan.value, + ui.VertexMode.triangleFan.index); }); test('ui.VertexMode converts to SkVertexMode', () { @@ -282,7 +289,8 @@ void _vertexModeTests() { void _imageTests() { test('MakeAnimatedImageFromEncoded makes a non-animated image', () { - final SkAnimatedImage nonAnimated = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final SkAnimatedImage nonAnimated = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); expect(nonAnimated.getFrameCount(), 1); expect(nonAnimated.getRepetitionCount(), 0); expect(nonAnimated.width(), 1); @@ -304,9 +312,10 @@ void _imageTests() { }); test('MakeAnimatedImageFromEncoded makes an animated image', () { - final SkAnimatedImage animated = canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif); + final SkAnimatedImage animated = + canvasKit.MakeAnimatedImageFromEncoded(kAnimatedGif); expect(animated.getFrameCount(), 3); - expect(animated.getRepetitionCount(), -1); // animates forever + expect(animated.getRepetitionCount(), -1); // animates forever expect(animated.width(), 1); expect(animated.height(), 1); for (int i = 0; i < 100; i++) { @@ -324,35 +333,39 @@ void _shaderTests() { }); test('MakeRadialGradient', () { - expect(canvasKit.SkShader.MakeRadialGradient( - Float32List.fromList([1, 1]), - 10.0, - [ - Float32List.fromList([0, 0, 0, 1]), - Float32List.fromList([1, 1, 1, 1]), - ], - Float32List.fromList([0, 1]), - canvasKit.TileMode.Repeat, - toSkMatrixFromFloat32(Matrix4.identity().storage), - 0, - ), isNotNull); + expect( + canvasKit.SkShader.MakeRadialGradient( + Float32List.fromList([1, 1]), + 10.0, + [ + Float32List.fromList([0, 0, 0, 1]), + Float32List.fromList([1, 1, 1, 1]), + ], + Float32List.fromList([0, 1]), + canvasKit.TileMode.Repeat, + toSkMatrixFromFloat32(Matrix4.identity().storage), + 0, + ), + isNotNull); }); test('MakeTwoPointConicalGradient', () { - expect(canvasKit.SkShader.MakeTwoPointConicalGradient( - Float32List.fromList([1, 1]), - 10.0, - Float32List.fromList([1, 1]), - 10.0, - [ - Float32List.fromList([0, 0, 0, 1]), - Float32List.fromList([1, 1, 1, 1]), - ], - Float32List.fromList([0, 1]), - canvasKit.TileMode.Repeat, - toSkMatrixFromFloat32(Matrix4.identity().storage), - 0, - ), isNotNull); + expect( + canvasKit.SkShader.MakeTwoPointConicalGradient( + Float32List.fromList([1, 1]), + 10.0, + Float32List.fromList([1, 1]), + 10.0, + [ + Float32List.fromList([0, 0, 0, 1]), + Float32List.fromList([1, 1, 1, 1]), + ], + Float32List.fromList([0, 1]), + canvasKit.TileMode.Repeat, + toSkMatrixFromFloat32(Matrix4.identity().storage), + 0, + ), + isNotNull); }); } @@ -398,11 +411,13 @@ void _paintTests() { void _maskFilterTests() { test('MakeBlurMaskFilter', () { - expect(canvasKit.MakeBlurMaskFilter( - canvasKit.BlurStyle.Outer, - 5.0, - false, - ), isNotNull); + expect( + canvasKit.MakeBlurMaskFilter( + canvasKit.BlurStyle.Outer, + 5.0, + false, + ), + isNotNull); }); } @@ -543,18 +558,44 @@ void _toSkMatrixFromFloat32Tests() { ..translate(1, 2, 3) ..rotateZ(4); expect( - toSkMatrixFromFloat32(matrix.storage), - Float32List.fromList([ - -0.6536436080932617, - 0.756802499294281, + toSkMatrixFromFloat32(matrix.storage), + Float32List.fromList([ + -0.6536436080932617, + 0.756802499294281, + 1, + -0.756802499294281, + -0.6536436080932617, + 2, + -0.0, + 0, + 1, + ])); + }); +} + +void _toSkRectTests() { + test('toSkRect', () { + expect(toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4)), [1, 2, 3, 4]); + }); + + test('fromSkRect', () { + expect(fromSkRect(Float32List.fromList([1, 2, 3, 4])), + ui.Rect.fromLTRB(1, 2, 3, 4)); + }); + + test('toSkRRect', () { + expect( + toSkRRect(ui.RRect.fromLTRBAndCorners( 1, - -0.756802499294281, - -0.6536436080932617, 2, - -0.0, - 0, - 1, - ]) + 3, + 4, + topLeft: ui.Radius.elliptical(5, 6), + topRight: ui.Radius.elliptical(7, 8), + bottomRight: ui.Radius.elliptical(9, 10), + bottomLeft: ui.Radius.elliptical(11, 12), + )), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], ); }); } @@ -581,7 +622,7 @@ void _pathTests() { test('addArc', () { path.addArc( - SkRect(fLeft: 10, fTop: 20, fRight: 30, fBottom: 40), + toSkRect(ui.Rect.fromLTRB(10, 20, 30, 40)), 1, 5, ); @@ -589,7 +630,7 @@ void _pathTests() { test('addOval', () { path.addOval( - SkRect(fLeft: 10, fTop: 20, fRight: 30, fBottom: 40), + toSkRect(ui.Rect.fromLTRB(10, 20, 30, 40)), false, 1, ); @@ -608,36 +649,24 @@ void _pathTests() { freeFloat32List(encodedPoints); }); - test('addRoundRect', () { + test('addRRect', () { final ui.RRect rrect = ui.RRect.fromRectAndRadius( ui.Rect.fromLTRB(10, 10, 20, 20), ui.Radius.circular(3), ); - final SkFloat32List skRadii = mallocFloat32List(8); - final Float32List radii = skRadii.toTypedArray(); - radii[0] = rrect.tlRadiusX; - radii[1] = rrect.tlRadiusY; - radii[2] = rrect.trRadiusX; - radii[3] = rrect.trRadiusY; - radii[4] = rrect.brRadiusX; - radii[5] = rrect.brRadiusY; - radii[6] = rrect.blRadiusX; - radii[7] = rrect.blRadiusY; - path.addRoundRect( - toOuterSkRect(rrect), - radii, + path.addRRect( + toSkRRect(rrect), false, ); - freeFloat32List(skRadii); }); test('addRect', () { - path.addRect(SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4)); + path.addRect(toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4))); }); test('arcTo', () { path.arcToOval( - SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4), + toSkRect(ui.Rect.fromLTRB(1, 2, 3, 4)), 5, 40, false, @@ -676,7 +705,7 @@ void _pathTests() { test('getBounds', () { final SkPath testPath = _testClosedSkPath(); - final ui.Rect bounds = testPath.getBounds().toRect(); + final ui.Rect bounds = fromSkRect(testPath.getBounds()); expect(bounds, const ui.Rect.fromLTRB(10, 10, 20, 20)); }); @@ -726,13 +755,15 @@ void _pathTests() { test('reset', () { final SkPath testPath = _testClosedSkPath(); - expect(testPath.getBounds().toRect(), const ui.Rect.fromLTRB(10, 10, 20, 20)); + expect(fromSkRect(testPath.getBounds()), + const ui.Rect.fromLTRB(10, 10, 20, 20)); testPath.reset(); - expect(testPath.getBounds().toRect(), ui.Rect.zero); + expect(fromSkRect(testPath.getBounds()), ui.Rect.zero); }); test('toSVGString', () { - expect(_testClosedSkPath().toSVGString(), 'M10 10L20 10L20 20L10 20L10 10Z'); + expect( + _testClosedSkPath().toSVGString(), 'M10 10L20 10L20 20L10 20L10 10Z'); }); test('isEmpty', () { @@ -743,22 +774,24 @@ void _pathTests() { test('copy', () { final SkPath original = _testClosedSkPath(); final SkPath copy = original.copy(); - expect(original.getBounds().toRect(), copy.getBounds().toRect()); + expect(fromSkRect(original.getBounds()), fromSkRect(copy.getBounds())); }); test('transform', () { path = _testClosedSkPath(); path.transform(2, 0, 10, 0, 2, 10, 0, 0, 0); - final ui.Rect transformedBounds = path.getBounds().toRect(); + final ui.Rect transformedBounds = fromSkRect(path.getBounds()); expect(transformedBounds, ui.Rect.fromLTRB(30, 30, 50, 50)); }); test('SkContourMeasureIter/SkContourMeasure', () { - final SkContourMeasureIter iter = SkContourMeasureIter(_testClosedSkPath(), false, 0); + final SkContourMeasureIter iter = + SkContourMeasureIter(_testClosedSkPath(), false, 0); final SkContourMeasure measure1 = iter.next(); expect(measure1.length(), 40); expect(measure1.getPosTan(5), Float32List.fromList([15, 10, 1, 0])); - expect(measure1.getPosTan(15), Float32List.fromList([20, 15, 0, 1])); + expect( + measure1.getPosTan(15), Float32List.fromList([20, 15, 0, 1])); expect(measure1.isClosed(), true); // Starting with a box path: @@ -783,29 +816,13 @@ void _pathTests() { // | | // 20 +-----------+ final SkPath segment = measure1.getSegment(5, 15, true); - expect(segment.getBounds().toRect(), ui.Rect.fromLTRB(15, 10, 20, 15)); + expect(fromSkRect(segment.getBounds()), ui.Rect.fromLTRB(15, 10, 20, 15)); final SkContourMeasure measure2 = iter.next(); expect(measure2, isNull); }); } -void _skSkRectTests() { - test('SkRect', () { - final SkRect rect = SkRect(fLeft: 1, fTop: 2, fRight: 3, fBottom: 4); - expect(rect.fLeft, 1); - expect(rect.fTop, 2); - expect(rect.fRight, 3); - expect(rect.fBottom, 4); - - final ui.Rect uiRect = rect.toRect(); - expect(uiRect.left, 1); - expect(uiRect.top, 2); - expect(uiRect.right, 3); - expect(uiRect.bottom, 4); - }); -} - SkVertices _testVertices() { return canvasKit.MakeSkVertices( canvasKit.VertexMode.Triangles, @@ -840,12 +857,8 @@ void _canvasTests() { setUp(() { recorder = SkPictureRecorder(); - canvas = recorder.beginRecording(SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - )); + canvas = + recorder.beginRecording(toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100))); }); tearDown(() { @@ -866,33 +879,23 @@ void _canvasTests() { test('saveLayer', () { canvas.saveLayer( - SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), SkPaint(), + toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100)), + null, + null, ); }); - test('SkCanvasSaveLayerWithoutBoundsOverload.saveLayer', () { - final SkCanvasSaveLayerWithoutBoundsOverload override = canvas as SkCanvasSaveLayerWithoutBoundsOverload; - override.saveLayer(SkPaint()); + test('saveLayer without bounds', () { + canvas.saveLayer(SkPaint(), null, null, null); }); - test('SkCanvasSaveLayerWithFilterOverload.saveLayer', () { - final SkCanvasSaveLayerWithFilterOverload override = canvas as SkCanvasSaveLayerWithFilterOverload; - override.saveLayer( + test('saveLayer with filter', () { + canvas.saveLayer( SkPaint(), + toSkRect(ui.Rect.fromLTRB(0, 0, 100, 100)), canvasKit.SkImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null), 0, - SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), ); }); @@ -910,22 +913,7 @@ void _canvasTests() { test('clipRRect', () { canvas.clipRRect( - SkRRect( - rect: SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), - rx1: 1, - ry1: 2, - rx2: 3, - ry2: 4, - rx3: 5, - ry3: 6, - rx4: 7, - ry4: 8, - ), + Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]), canvasKit.ClipOp.Intersect, true, ); @@ -933,12 +921,7 @@ void _canvasTests() { test('clipRect', () { canvas.clipRect( - SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), + Float32List.fromList([0, 0, 100, 100]), canvasKit.ClipOp.Intersect, true, ); @@ -946,12 +929,7 @@ void _canvasTests() { test('drawArc', () { canvas.drawArc( - SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 50, - ), + Float32List.fromList([0, 0, 100, 50]), 0, 100, true, @@ -960,7 +938,8 @@ void _canvasTests() { }); test('drawAtlas', () { - final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); canvas.drawAtlas( image.getCurrentFrame(), Float32List.fromList([0, 0, 1, 1]), @@ -984,44 +963,15 @@ void _canvasTests() { test('drawDRRect', () { canvas.drawDRRect( - SkRRect( - rect: SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), - rx1: 1, - ry1: 2, - rx2: 3, - ry2: 4, - rx3: 5, - ry3: 6, - rx4: 7, - ry4: 8, - ), - SkRRect( - rect: SkRect( - fLeft: 20, - fTop: 20, - fRight: 80, - fBottom: 80, - ), - rx1: 1, - ry1: 2, - rx2: 3, - ry2: 4, - rx3: 5, - ry3: 6, - rx4: 7, - ry4: 8, - ), + Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]), + Float32List.fromList([20, 20, 80, 80, 1, 2, 3, 4, 5, 6, 7, 8]), SkPaint(), ); }); test('drawImage', () { - final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); canvas.drawImage( image.getCurrentFrame(), 10, @@ -1031,22 +981,24 @@ void _canvasTests() { }); test('drawImageRect', () { - final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); canvas.drawImageRect( image.getCurrentFrame(), - SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), - SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([0, 0, 1, 1]), SkPaint(), false, ); }); test('drawImageNine', () { - final SkAnimatedImage image = canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); + final SkAnimatedImage image = + canvasKit.MakeAnimatedImageFromEncoded(kTransparentImage); canvas.drawImageNine( image.getCurrentFrame(), - SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), - SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), + Float32List.fromList([0, 0, 1, 1]), + Float32List.fromList([0, 0, 1, 1]), SkPaint(), ); }); @@ -1056,7 +1008,7 @@ void _canvasTests() { }); test('drawOval', () { - canvas.drawOval(SkRect(fLeft: 0, fTop: 0, fRight: 1, fBottom: 1), SkPaint()); + canvas.drawOval(Float32List.fromList([0, 0, 1, 1]), SkPaint()); }); test('drawPaint', () { @@ -1080,34 +1032,14 @@ void _canvasTests() { test('drawRRect', () { canvas.drawRRect( - SkRRect( - rect: SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), - rx1: 1, - ry1: 2, - rx2: 3, - ry2: 4, - rx3: 5, - ry3: 6, - rx4: 7, - ry4: 8, - ), + Float32List.fromList([0, 0, 100, 100, 1, 2, 3, 4, 5, 6, 7, 8]), SkPaint(), ); }); test('drawRect', () { canvas.drawRect( - SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - ), + Float32List.fromList([0, 0, 100, 100]), SkPaint(), ); }); @@ -1120,12 +1052,13 @@ void _canvasTests() { const double spotAlpha = 0.25; final SkPath path = _testClosedSkPath(); - final ui.Rect bounds = path.getBounds().toRect(); + final ui.Rect bounds = fromSkRect(path.getBounds()); final double shadowX = (bounds.left + bounds.right) / 2.0; final double shadowY = bounds.top - 600.0; const ui.Color color = ui.Color(0xAABBCCDD); - ui.Color inAmbient = color.withAlpha((color.alpha * ambientAlpha).round()); + ui.Color inAmbient = + color.withAlpha((color.alpha * ambientAlpha).round()); ui.Color inSpot = color.withAlpha((color.alpha * spotAlpha).round()); final SkTonalColors inTonalColors = SkTonalColors( @@ -1138,8 +1071,7 @@ void _canvasTests() { canvas.drawShadow( path, - Float32List(3) - ..[2] = devicePixelRatio * elevation, + Float32List(3)..[2] = devicePixelRatio * elevation, Float32List(3) ..[0] = shadowX ..[1] = shadowY @@ -1186,12 +1118,8 @@ void _canvasTests() { test('drawPicture', () { final SkPictureRecorder otherRecorder = SkPictureRecorder(); - final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect( - fLeft: 0, - fTop: 0, - fRight: 100, - fBottom: 100, - )); + final SkCanvas otherCanvas = + otherRecorder.beginRecording(Float32List.fromList([0, 0, 100, 100])); otherCanvas.drawLine(0, 0, 10, 10, SkPaint()); canvas.drawPicture(otherRecorder.finishRecordingAsPicture()); }); @@ -1212,26 +1140,79 @@ void _canvasTests() { test('toImage.toByteData', () async { final SkPictureRecorder otherRecorder = SkPictureRecorder(); - final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect( - fLeft: 0, - fTop: 0, - fRight: 1, - fBottom: 1, - )); + final SkCanvas otherCanvas = + otherRecorder.beginRecording(Float32List.fromList([0, 0, 1, 1])); otherCanvas.drawRect( - SkRect( - fLeft: 0, - fTop: 0, - fRight: 1, - fBottom: 1, - ), + Float32List.fromList([0, 0, 1, 1]), SkPaint(), ); - final CkPicture picture = CkPicture(otherRecorder.finishRecordingAsPicture(), null); + final CkPicture picture = + CkPicture(otherRecorder.finishRecordingAsPicture(), null); final CkImage image = await picture.toImage(1, 1); - final ByteData rawData = await image.toByteData(format: ui.ImageByteFormat.rawRgba); + final ByteData rawData = + await image.toByteData(format: ui.ImageByteFormat.rawRgba); expect(rawData, isNotNull); - final ByteData pngData = await image.toByteData(format: ui.ImageByteFormat.png); + final ByteData pngData = + await image.toByteData(format: ui.ImageByteFormat.png); expect(pngData, isNotNull); }); } + +void _textStyleTests() { + test('SkTextDecorationStyle mapping is correct', () { + expect(canvasKit.DecorationStyle.Solid.value, + ui.TextDecorationStyle.solid.index); + expect(canvasKit.DecorationStyle.Double.value, + ui.TextDecorationStyle.double.index); + expect(canvasKit.DecorationStyle.Dotted.value, + ui.TextDecorationStyle.dotted.index); + expect(canvasKit.DecorationStyle.Dashed.value, + ui.TextDecorationStyle.dashed.index); + expect(canvasKit.DecorationStyle.Wavy.value, + ui.TextDecorationStyle.wavy.index); + }); + + test('ui.TextDecorationStyle converts to SkTextDecorationStyle', () { + for (ui.TextDecorationStyle decorationStyle + in ui.TextDecorationStyle.values) { + expect(toSkTextDecorationStyle(decorationStyle).value, + decorationStyle.index); + } + }); + + test('SkTextBaseline mapping is correct', () { + expect(canvasKit.TextBaseline.Alphabetic.value, + ui.TextBaseline.alphabetic.index); + expect(canvasKit.TextBaseline.Ideographic.value, + ui.TextBaseline.ideographic.index); + }); + + test('ui.TextBaseline converts to SkTextBaseline', () { + for (ui.TextBaseline textBaseline in ui.TextBaseline.values) { + expect(toSkTextBaseline(textBaseline).value, textBaseline.index); + } + }); + + test('SkPlaceholderAlignment mapping is correct', () { + expect(canvasKit.PlaceholderAlignment.Baseline.value, + ui.PlaceholderAlignment.baseline.index); + expect(canvasKit.PlaceholderAlignment.AboveBaseline.value, + ui.PlaceholderAlignment.aboveBaseline.index); + expect(canvasKit.PlaceholderAlignment.BelowBaseline.value, + ui.PlaceholderAlignment.belowBaseline.index); + expect(canvasKit.PlaceholderAlignment.Top.value, + ui.PlaceholderAlignment.top.index); + expect(canvasKit.PlaceholderAlignment.Bottom.value, + ui.PlaceholderAlignment.bottom.index); + expect(canvasKit.PlaceholderAlignment.Middle.value, + ui.PlaceholderAlignment.middle.index); + }); + + test('ui.PlaceholderAlignment converts to SkPlaceholderAlignment', () { + for (ui.PlaceholderAlignment placeholderAlignment + in ui.PlaceholderAlignment.values) { + expect(toSkPlaceholderAlignment(placeholderAlignment).value, + placeholderAlignment.index); + } + }); +} From 21caa92309f458ae639d206ab126edcd22ab535d Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Thu, 15 Oct 2020 16:40:23 -0700 Subject: [PATCH 126/219] disabled the auto assign bot (#21341) --- .github/auto_assign.yml | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/auto_assign.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml deleted file mode 100644 index 8a18797fd9d6e..0000000000000 --- a/.github/auto_assign.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This is the config file for `auto-assign` bot. -# https://github.com/kentaro-m/auto-assign/ - -# Set to true to add reviewers to pull requests -addReviewers: true - -# Set to true to add assignees to pull requests -addAssignees: false - -# A list of reviewers to be added to pull requests (GitHub user name) -# Note: Add new engine contributors here when joining the team. -reviewers: - - gaaclarke - - liyuqian - - gw280 - - chinmaygarde - - GaryQian - - jason-simmons - - iskakaushik - - flar - -# A number of reviewers added to the pull request -# Set 0 to add all the reviewers (default: 0) -numberOfReviewers: 1 - -# A list of assignees, overrides reviewers if set -# assignees: -# - assigneeA - -# A number of assignees to add to the pull request -# Set to 0 to add all of the assignees. -# Uses numberOfReviewers if unset. -# numberOfAssignees: 2 - -# A list of keywords to be skipped the process that add reviewers if pull requests include it -skipKeywords: - - Roll - - web From ab8b2073ed238db2ab8e860357a4addda34f2f8f Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 15 Oct 2020 16:51:17 -0700 Subject: [PATCH 127/219] Fix incldues to be flutter/shell rather than shell/ (#21889) --- .../external_view_embedder_unittests.cc | 2 +- shell/platform/android/platform_view_android.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index dff6e685bb1fd..ba16586b723aa 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -10,10 +10,10 @@ #include "flutter/fml/raster_thread_merger.h" #include "flutter/fml/thread.h" #include "flutter/shell/platform/android/jni/jni_mock.h" +#include "flutter/shell/platform/android/surface/android_surface.h" #include "flutter/shell/platform/android/surface/android_surface_mock.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "shell/platform/android/surface/android_surface.h" #include "third_party/skia/include/gpu/GrDirectContext.h" namespace flutter { diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 7bde2a45144a3..8ecba33be57f4 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -14,8 +14,8 @@ #include "flutter/shell/platform/android/android_external_texture_gl.h" #include "flutter/shell/platform/android/android_surface_gl.h" #include "flutter/shell/platform/android/android_surface_software.h" -#include "shell/platform/android/external_view_embedder/external_view_embedder.h" -#include "shell/platform/android/surface/android_surface.h" +#include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" +#include "flutter/shell/platform/android/surface/android_surface.h" #if SHELL_ENABLE_VULKAN #include "flutter/shell/platform/android/android_surface_vulkan.h" From c2080ec7253621982a3b3ad35fb701bf04a18029 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 15 Oct 2020 16:57:02 -0700 Subject: [PATCH 128/219] Check for null images in ImageFromCompressedData (#21891) --- lib/ui/painting/image_decoder.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/painting/image_decoder.cc b/lib/ui/painting/image_decoder.cc index a2de0185bb285..d6c4a668fb5fc 100644 --- a/lib/ui/painting/image_decoder.cc +++ b/lib/ui/painting/image_decoder.cc @@ -105,7 +105,8 @@ sk_sp ImageFromCompressedData(fml::RefPtr descriptor, if (!descriptor->should_resize(target_width, target_height)) { // No resizing requested. Just decode & rasterize the image. - return descriptor->image()->makeRasterImage(); + sk_sp image = descriptor->image(); + return image ? image->makeRasterImage() : nullptr; } const SkISize source_dimensions = descriptor->image_info().dimensions(); From 335531c8153dea5aae0efbc3387a74db748d5b6f Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Oct 2020 16:58:05 -0700 Subject: [PATCH 129/219] Roll buildroot to 9184ff0695be1b3e4bb20cf64efcfa56daa0a3c0 (#21884) This fixes Windows build on goma. Rolls in buildroot change https://github.com/flutter/buildroot/pull/406 https://github.com/flutter/engine/pull/21884 by stuartmorgan which removes the /FC flag in Windows builds. That flag is incompatible with upstream goma changes. See: https://source.chromium.org/chromium/chromium/src/+/2e6d17c6948b2ca1e4dbd6bf15fcb52d32fa338d --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index d0f65b3a115bf..b4bb5db4e4d5a 100644 --- a/DEPS +++ b/DEPS @@ -105,7 +105,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'e5b7a41ce43f8a00d5fcb9c55cb16b9c7697e8aa', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + '9184ff0695be1b3e4bb20cf64efcfa56daa0a3c0', # Fuchsia compatibility # From fe734bdb8baa7852ff72d3f1bd919887f98533fa Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Oct 2020 16:59:15 -0700 Subject: [PATCH 130/219] Add TextRange::Contains tests spanning base/extent (#21874) Adds tests for TextRange::Contains(const TextRange&) where the range being tested spans the base/extent of the testing range. This was originally intended to land in #21854, but it seems I didn't push the additional tests before landing. --- .../common/cpp/text_range_unittests.cc | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/shell/platform/common/cpp/text_range_unittests.cc b/shell/platform/common/cpp/text_range_unittests.cc index 8fcf8fed20534..eaf30d935a515 100644 --- a/shell/platform/common/cpp/text_range_unittests.cc +++ b/shell/platform/common/cpp/text_range_unittests.cc @@ -107,6 +107,11 @@ TEST(TextRange, ContainsRangePreStartPosition) { EXPECT_FALSE(range.Contains(TextRange(0, 1))); } +TEST(TextRange, ContainsRangeSpanningStartPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(TextRange(1, 3))); +} + TEST(TextRange, ContainsRangeStartPosition) { TextRange range(2, 6); EXPECT_TRUE(range.Contains(TextRange(2))); @@ -123,6 +128,11 @@ TEST(TextRange, ContainsRangeEndPosition) { EXPECT_TRUE(range.Contains(TextRange(6))); } +TEST(TextRange, ContainsRangeSpanningEndPosition) { + TextRange range(2, 6); + EXPECT_FALSE(range.Contains(TextRange(5, 7))); +} + TEST(TextRange, ContainsRangePostEndPosition) { TextRange range(2, 6); EXPECT_FALSE(range.Contains(TextRange(6, 7))); @@ -133,6 +143,11 @@ TEST(TextRange, ContainsRangePreStartPositionReversed) { EXPECT_FALSE(range.Contains(TextRange(0, 1))); } +TEST(TextRange, ContainsRangeSpanningStartPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(TextRange(1, 3))); +} + TEST(TextRange, ContainsRangeStartPositionReversed) { TextRange range(6, 2); EXPECT_TRUE(range.Contains(TextRange(2))); @@ -144,6 +159,11 @@ TEST(TextRange, ContainsRangeMiddlePositionReversed) { EXPECT_TRUE(range.Contains(TextRange(4, 5))); } +TEST(TextRange, ContainsRangeSpanningEndPositionReversed) { + TextRange range(6, 2); + EXPECT_FALSE(range.Contains(TextRange(5, 7))); +} + TEST(TextRange, ContainsRangeEndPositionReversed) { TextRange range(6, 2); EXPECT_TRUE(range.Contains(TextRange(5))); From 29602e139a9518d3e812dbdd3e1ea617dc9e7cac Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Thu, 15 Oct 2020 20:17:03 -0400 Subject: [PATCH 131/219] Roll Skia from f4bda743ff8d to f1b53836b705 (21 revisions) (#21892) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index b4bb5db4e4d5a..1e2dec048aad2 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'f4bda743ff8d9cd10df0a771582aa8f25749eb57', + 'skia_revision': 'f1b53836b70549fb9e0a0f0358b6dbf676d35f41', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 78d7503d618a0..9283ff1fe0aec 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 819d3bc919ec6336e402246f10c994a1 +Signature: a21a1a69f9d735a406af72e69b6ae7e7 UNUSED LICENSES: From 1bc025d6cbf1136c2e96c6fb041d24202ea47ff0 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 15 Oct 2020 17:22:02 -0700 Subject: [PATCH 132/219] [fuchsia] External view embedder will be shared with platform view (#21850) --- flow/layers/child_scene_layer.cc | 4 +- flow/layers/child_scene_layer.h | 2 +- flow/layers/clip_path_layer.cc | 2 +- flow/layers/clip_path_layer.h | 2 +- flow/layers/clip_rect_layer.cc | 2 +- flow/layers/clip_rect_layer.h | 2 +- flow/layers/clip_rrect_layer.cc | 2 +- flow/layers/clip_rrect_layer.h | 2 +- flow/layers/container_layer.cc | 7 +- flow/layers/container_layer.h | 4 +- flow/layers/fuchsia_layer_unittests.cc | 12 ++-- flow/layers/layer.cc | 4 +- flow/layers/layer.h | 2 +- flow/layers/layer_tree.cc | 6 +- flow/layers/layer_tree.h | 2 +- flow/layers/opacity_layer.cc | 8 +-- flow/layers/opacity_layer.h | 2 +- flow/layers/platform_view_layer.cc | 5 +- flow/layers/platform_view_layer.h | 2 +- flow/layers/transform_layer.cc | 2 +- flow/layers/transform_layer.h | 2 +- flow/scene_update_context.cc | 70 ++++++++++--------- flow/scene_update_context.h | 16 +++-- .../fuchsia/flutter/compositor_context.cc | 8 +-- .../fuchsia/flutter/compositor_context.h | 9 +-- shell/platform/fuchsia/flutter/engine.cc | 36 ++++++---- shell/platform/fuchsia/flutter/engine.h | 6 +- .../platform/fuchsia/flutter/platform_view.cc | 3 + .../platform/fuchsia/flutter/platform_view.h | 4 ++ .../fuchsia/flutter/platform_view_unittest.cc | 17 ++++- shell/platform/fuchsia/flutter/surface.cc | 4 +- shell/platform/fuchsia/flutter/surface.h | 4 +- 32 files changed, 146 insertions(+), 107 deletions(-) diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 9ba5eff3c5021..f15173c064d51 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -26,10 +26,10 @@ void ChildSceneLayer::Paint(PaintContext& context) const { FML_NOTREACHED(); } -void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { +void ChildSceneLayer::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "ChildSceneLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); - context.UpdateView(layer_id_, offset_, size_, hit_testable_); + context->UpdateView(layer_id_, offset_, size_, hit_testable_); } } // namespace flutter diff --git a/flow/layers/child_scene_layer.h b/flow/layers/child_scene_layer.h index cad47db00462a..0ec7f3b16efb1 100644 --- a/flow/layers/child_scene_layer.h +++ b/flow/layers/child_scene_layer.h @@ -26,7 +26,7 @@ class ChildSceneLayer : public Layer { void Paint(PaintContext& context) const override; - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; private: zx_koid_t layer_id_ = ZX_KOID_INVALID; diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 038ead12a6805..0b714392b60f5 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -41,7 +41,7 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipPathLayer::UpdateScene(SceneUpdateContext& context) { +void ClipPathLayer::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "ClipPathLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index ce2e4e54d66e3..eac015db9e5d1 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -22,7 +22,7 @@ class ClipPathLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index fbd36dab65d05..cd4443b5c0198 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -36,7 +36,7 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipRectLayer::UpdateScene(SceneUpdateContext& context) { +void ClipRectLayer::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "ClipRectLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index b52a9512262d3..9c57861d87fe2 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -21,7 +21,7 @@ class ClipRectLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index b3ff833c4a4f3..32ed1a39096ab 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -37,7 +37,7 @@ void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipRRectLayer::UpdateScene(SceneUpdateContext& context) { +void ClipRRectLayer::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "ClipRRectLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 314886a1fed30..1422615c5c6b3 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -22,7 +22,7 @@ class ClipRRectLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index 825826b70835f..a6ad98ba1aae4 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -98,18 +98,19 @@ void ContainerLayer::CheckForChildLayerBelow(PrerollContext* context) { // All ContainerLayers make the check in PrerollChildren. } -void ContainerLayer::UpdateScene(SceneUpdateContext& context) { +void ContainerLayer::UpdateScene(std::shared_ptr context) { UpdateSceneChildren(context); } -void ContainerLayer::UpdateSceneChildren(SceneUpdateContext& context) { +void ContainerLayer::UpdateSceneChildren( + std::shared_ptr context) { FML_DCHECK(needs_system_composite()); std::optional frame; if (child_layer_exists_below_) { frame.emplace( context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT, - SkScalarRoundToInt(context.alphaf() * 255), "flutter::ContainerLayer"); + SkScalarRoundToInt(context->alphaf() * 255), "flutter::ContainerLayer"); frame->AddPaintLayer(this); } diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index 0e5b27d7083fd..11eae5f3fe123 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -21,7 +21,7 @@ class ContainerLayer : public Layer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) void CheckForChildLayerBelow(PrerollContext* context) override; - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif const std::vector>& layers() const { return layers_; } @@ -33,7 +33,7 @@ class ContainerLayer : public Layer { void PaintChildren(PaintContext& context) const; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateSceneChildren(SceneUpdateContext& context); + void UpdateSceneChildren(std::shared_ptr context); #endif // Try to prepare the raster cache for a given layer. diff --git a/flow/layers/fuchsia_layer_unittests.cc b/flow/layers/fuchsia_layer_unittests.cc index df8f8e746ae00..85278b8c60af5 100644 --- a/flow/layers/fuchsia_layer_unittests.cc +++ b/flow/layers/fuchsia_layer_unittests.cc @@ -261,7 +261,7 @@ struct TestContext { std::unique_ptr mock_session_wrapper; // SceneUpdateContext. - std::unique_ptr scene_update_context; + std::shared_ptr scene_update_context; // PrerollContext. MutatorsStack unused_stack; @@ -286,7 +286,7 @@ std::unique_ptr InitTest() { std::make_unique(std::move(session_ptr)); // Init SceneUpdateContext. - context->scene_update_context = std::make_unique( + context->scene_update_context = std::make_shared( "fuchsia_layer_unittest", fuchsia::ui::views::ViewToken(), scenic::ViewRefPair::New(), *(context->mock_session_wrapper)); @@ -410,7 +410,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_PhysicalShapeLayersAndChildSceneLayers) { // Create another frame to be the "real" root. Required because // UpdateScene() traversal expects there to already be a top node. - SceneUpdateContext::Frame frame(*(test_context->scene_update_context), + SceneUpdateContext::Frame frame(test_context->scene_update_context, SkRRect::MakeRect(SkRect::MakeWH(100, 100)), SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "fuchsia test root"); @@ -555,7 +555,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_PhysicalShapeLayersAndChildSceneLayers) { // Finally, UpdateScene(). The MockSession will check the emitted commands // against the list above. - root->UpdateScene(*(test_context->scene_update_context)); + root->UpdateScene(test_context->scene_update_context); test_context->mock_session_wrapper->Present(); @@ -626,7 +626,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_OpacityAndTransformLayer) { // Create another frame to be the "real" root. Required because // UpdateScene() traversal expects there to already be a top node. - SceneUpdateContext::Frame frame(*(test_context->scene_update_context), + SceneUpdateContext::Frame frame(test_context->scene_update_context, SkRRect::MakeRect(SkRect::MakeWH(100, 100)), SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "fuchsia test root"); @@ -737,7 +737,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_OpacityAndTransformLayer) { // Finally, UpdateScene(). The MockSession will check the emitted // commands against the list above. - root->UpdateScene(*(test_context->scene_update_context)); + root->UpdateScene(test_context->scene_update_context); test_context->mock_session_wrapper->Present(); diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index a242f977cde29..006f5cb84e916 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -67,13 +67,13 @@ void Layer::CheckForChildLayerBelow(PrerollContext* context) { } } -void Layer::UpdateScene(SceneUpdateContext& context) { +void Layer::UpdateScene(std::shared_ptr context) { FML_DCHECK(needs_system_composite()); FML_DCHECK(child_layer_exists_below_); SceneUpdateContext::Frame frame( context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT, - SkScalarRoundToInt(context.alphaf() * 255), "flutter::Layer"); + SkScalarRoundToInt(context->alphaf() * 255), "flutter::Layer"); frame.AddPaintLayer(this); } diff --git a/flow/layers/layer.h b/flow/layers/layer.h index eeffabacbb325..0ce351db63b2f 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -154,7 +154,7 @@ class Layer { #if defined(LEGACY_FUCHSIA_EMBEDDER) // Updates the system composited scene. - virtual void UpdateScene(SceneUpdateContext& context); + virtual void UpdateScene(std::shared_ptr context); virtual void CheckForChildLayerBelow(PrerollContext* context); #endif diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index e0ac5157fc746..d669591da1654 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -62,11 +62,11 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, } #if defined(LEGACY_FUCHSIA_EMBEDDER) -void LayerTree::UpdateScene(SceneUpdateContext& context) { +void LayerTree::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "LayerTree::UpdateScene"); // Reset for a new Scene. - context.Reset(); + context->Reset(); const float inv_dpr = 1.0f / device_pixel_ratio_; SceneUpdateContext::Transform transform(context, inv_dpr, inv_dpr, 1.0f); @@ -82,7 +82,7 @@ void LayerTree::UpdateScene(SceneUpdateContext& context) { if (root_layer_->needs_painting()) { frame.AddPaintLayer(root_layer_.get()); } - context.root_node().AddChild(transform.entity_node()); + context->root_node().AddChild(transform.entity_node()); } #endif diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index b9cd8d37de7c0..b59f278296381 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -32,7 +32,7 @@ class LayerTree { bool ignore_raster_cache = false); #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context); + void UpdateScene(std::shared_ptr context); #endif void Paint(CompositorContext::ScopedFrame& frame, diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 131b1d9f253b7..9e8001d6e3283 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -85,11 +85,11 @@ void OpacityLayer::Paint(PaintContext& context) const { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void OpacityLayer::UpdateScene(SceneUpdateContext& context) { - float saved_alpha = context.alphaf(); - context.set_alphaf(context.alphaf() * (alpha_ / 255.f)); +void OpacityLayer::UpdateScene(std::shared_ptr context) { + float saved_alpha = context->alphaf(); + context->set_alphaf(context->alphaf() * (alpha_ / 255.f)); ContainerLayer::UpdateScene(context); - context.set_alphaf(saved_alpha); + context->set_alphaf(saved_alpha); } #endif diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index ed5f0283ad356..25f1c7944a829 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -32,7 +32,7 @@ class OpacityLayer : public MergedContainerLayer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 80514b5213e18..0971f93db777e 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -47,10 +47,11 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } #if defined(LEGACY_FUCHSIA_EMBEDDER) -void PlatformViewLayer::UpdateScene(SceneUpdateContext& context) { +void PlatformViewLayer::UpdateScene( + std::shared_ptr context) { TRACE_EVENT0("flutter", "PlatformViewLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); - context.UpdateView(view_id_, offset_, size_); + context->UpdateView(view_id_, offset_, size_); } #endif diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index a75c69689dabf..e9d6fac42eb23 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -19,7 +19,7 @@ class PlatformViewLayer : public Layer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) // Updates the system composited scene. - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 8fe5dd32e1e85..3e26e525ac637 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -54,7 +54,7 @@ void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void TransformLayer::UpdateScene(SceneUpdateContext& context) { +void TransformLayer::UpdateScene(std::shared_ptr context) { TRACE_EVENT0("flutter", "TransformLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index 63a212f9236de..7956e3906a680 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -20,7 +20,7 @@ class TransformLayer : public ContainerLayer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(SceneUpdateContext& context) override; + void UpdateScene(std::shared_ptr context) override; #endif private: diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 3edd9c4d9fe26..141e2caafeb35 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -215,29 +215,30 @@ void SceneUpdateContext::DestroyView(int64_t view_id) { ViewHolder::Destroy(view_id); } -SceneUpdateContext::Entity::Entity(SceneUpdateContext& context) +SceneUpdateContext::Entity::Entity(std::shared_ptr context) : context_(context), - previous_entity_(context.top_entity_), - entity_node_(context.session_.get()) { - context.top_entity_ = this; + previous_entity_(context->top_entity_), + entity_node_(context->session_.get()) { + context->top_entity_ = this; } SceneUpdateContext::Entity::~Entity() { if (previous_entity_) { previous_entity_->embedder_node().AddChild(entity_node_); } else { - context_.root_node_.AddChild(entity_node_); + context_->root_node_.AddChild(entity_node_); } - FML_DCHECK(context_.top_entity_ == this); - context_.top_entity_ = previous_entity_; + FML_DCHECK(context_->top_entity_ == this); + context_->top_entity_ = previous_entity_; } -SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, - const SkMatrix& transform) +SceneUpdateContext::Transform::Transform( + std::shared_ptr context, + const SkMatrix& transform) : Entity(context), - previous_scale_x_(context.top_scale_x_), - previous_scale_y_(context.top_scale_y_) { + previous_scale_x_(context->top_scale_x_), + previous_scale_y_(context->top_scale_y_) { entity_node().SetLabel("flutter::Transform"); if (!transform.isIdentity()) { // TODO(SCN-192): The perspective and shear components in the matrix @@ -255,8 +256,8 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, decomposition.scale().y, // 1.f // ); - context.top_scale_x_ *= decomposition.scale().x; - context.top_scale_y_ *= decomposition.scale().y; + context->top_scale_x_ *= decomposition.scale().x; + context->top_scale_y_ *= decomposition.scale().y; entity_node().SetRotation(decomposition.rotation().x, // decomposition.rotation().y, // @@ -267,45 +268,46 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, } } -SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, - float scale_x, - float scale_y, - float scale_z) +SceneUpdateContext::Transform::Transform( + std::shared_ptr context, + float scale_x, + float scale_y, + float scale_z) : Entity(context), - previous_scale_x_(context.top_scale_x_), - previous_scale_y_(context.top_scale_y_) { + previous_scale_x_(context->top_scale_x_), + previous_scale_y_(context->top_scale_y_) { entity_node().SetLabel("flutter::Transform"); if (scale_x != 1.f || scale_y != 1.f || scale_z != 1.f) { entity_node().SetScale(scale_x, scale_y, scale_z); - context.top_scale_x_ *= scale_x; - context.top_scale_y_ *= scale_y; + context->top_scale_x_ *= scale_x; + context->top_scale_y_ *= scale_y; } } SceneUpdateContext::Transform::~Transform() { - context().top_scale_x_ = previous_scale_x_; - context().top_scale_y_ = previous_scale_y_; + context()->top_scale_x_ = previous_scale_x_; + context()->top_scale_y_ = previous_scale_y_; } -SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, +SceneUpdateContext::Frame::Frame(std::shared_ptr context, const SkRRect& rrect, SkColor color, SkAlpha opacity, std::string label) : Entity(context), - previous_elevation_(context.top_elevation_), + previous_elevation_(context->top_elevation_), rrect_(rrect), color_(color), opacity_(opacity), - opacity_node_(context.session_.get()), + opacity_node_(context->session_.get()), paint_bounds_(SkRect::MakeEmpty()) { // Increment elevation trackers before calculating any local elevation. - // |UpdateView| can modify context.next_elevation_, which is why it is + // |UpdateView| can modify context->next_elevation_, which is why it is // neccesary to track this addtional state. - context.top_elevation_ += kScenicZElevationBetweenLayers; - context.next_elevation_ += kScenicZElevationBetweenLayers; + context->top_elevation_ += kScenicZElevationBetweenLayers; + context->next_elevation_ += kScenicZElevationBetweenLayers; - float local_elevation = context.next_elevation_ - previous_elevation_; + float local_elevation = context->next_elevation_ - previous_elevation_; entity_node().SetTranslation(0.f, 0.f, -local_elevation); entity_node().SetLabel(label); entity_node().AddChild(opacity_node_); @@ -318,11 +320,11 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, } SceneUpdateContext::Frame::~Frame() { - context().top_elevation_ = previous_elevation_; + context()->top_elevation_ = previous_elevation_; // Add a part which represents the frame's geometry for clipping purposes - context().CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_, - std::move(paint_layers_)); + context()->CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_, + std::move(paint_layers_)); } void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { @@ -331,7 +333,7 @@ void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { paint_bounds_.join(layer->paint_bounds()); } -SceneUpdateContext::Clip::Clip(SceneUpdateContext& context, +SceneUpdateContext::Clip::Clip(std::shared_ptr context, const SkRect& shape_bounds) : Entity(context) { entity_node().SetLabel("flutter::Clip"); diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index f15c59fa7f6ea..d95513bc259b5 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -46,15 +46,15 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { public: class Entity { public: - Entity(SceneUpdateContext& context); + Entity(std::shared_ptr context); virtual ~Entity(); - SceneUpdateContext& context() { return context_; } + std::shared_ptr context() { return context_; } scenic::EntityNode& entity_node() { return entity_node_; } virtual scenic::ContainerNode& embedder_node() { return entity_node_; } private: - SceneUpdateContext& context_; + std::shared_ptr context_; Entity* const previous_entity_; scenic::EntityNode entity_node_; @@ -62,8 +62,9 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { class Transform : public Entity { public: - Transform(SceneUpdateContext& context, const SkMatrix& transform); - Transform(SceneUpdateContext& context, + Transform(std::shared_ptr context, + const SkMatrix& transform); + Transform(std::shared_ptr context, float scale_x, float scale_y, float scale_z); @@ -79,7 +80,7 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { // When layer is not nullptr, the frame is associated with a layer subtree // rooted with that layer. The frame may then create a surface that will be // retained for that layer. - Frame(SceneUpdateContext& context, + Frame(std::shared_ptr context, const SkRRect& rrect, SkColor color, SkAlpha opacity, @@ -104,7 +105,8 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { class Clip : public Entity { public: - Clip(SceneUpdateContext& context, const SkRect& shape_bounds); + Clip(std::shared_ptr context, + const SkRect& shape_bounds); ~Clip() = default; }; diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc index 005a9cca0705b..2f2a83eba345a 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.cc +++ b/shell/platform/fuchsia/flutter/compositor_context.cc @@ -23,7 +23,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { fml::RefPtr raster_thread_merger, SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, - flutter::SceneUpdateContext& scene_update_context) + std::shared_ptr scene_update_context) : flutter::CompositorContext::ScopedFrame(context, surface_producer.gr_context(), canvas, @@ -39,7 +39,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { private: SessionConnection& session_connection_; VulkanSurfaceProducer& surface_producer_; - flutter::SceneUpdateContext& scene_update_context_; + std::shared_ptr scene_update_context_; flutter::RasterStatus Raster(flutter::LayerTree& layer_tree, bool ignore_raster_cache) override { @@ -64,7 +64,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { // Flush all pending session ops: create surfaces and enqueue session // Image ops for the frame's paint tasks, then Present. TRACE_EVENT0("flutter", "SessionPresent"); - frame_paint_tasks = scene_update_context_.GetPaintTasks(); + frame_paint_tasks = scene_update_context_->GetPaintTasks(); for (auto& task : frame_paint_tasks) { SkISize physical_size = SkISize::Make(layer_tree.device_pixel_ratio() * task.scale_x * @@ -142,7 +142,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { CompositorContext::CompositorContext( SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, - flutter::SceneUpdateContext& scene_update_context) + std::shared_ptr scene_update_context) : session_connection_(session_connection), surface_producer_(surface_producer), scene_update_context_(scene_update_context) {} diff --git a/shell/platform/fuchsia/flutter/compositor_context.h b/shell/platform/fuchsia/flutter/compositor_context.h index 542e5d314fa71..4a943a499ad63 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.h +++ b/shell/platform/fuchsia/flutter/compositor_context.h @@ -21,16 +21,17 @@ namespace flutter_runner { // Fuchsia. class CompositorContext final : public flutter::CompositorContext { public: - CompositorContext(SessionConnection& session_connection, - VulkanSurfaceProducer& surface_producer, - flutter::SceneUpdateContext& scene_update_context); + CompositorContext( + SessionConnection& session_connection, + VulkanSurfaceProducer& surface_producer, + std::shared_ptr scene_update_context); ~CompositorContext() override; private: SessionConnection& session_connection_; VulkanSurfaceProducer& surface_producer_; - flutter::SceneUpdateContext& scene_update_context_; + std::shared_ptr scene_update_context_; // |flutter::CompositorContext| std::unique_ptr AcquireFrame( diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index 3380726840ad6..c0f52142c934a 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -8,6 +8,7 @@ #include #include "../runtime/dart/utils/files.h" +#include "flow/embedded_views.h" #include "flutter/common/task_runners.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/synchronization/waitable_event.h" @@ -137,15 +138,18 @@ Engine::Engine(Delegate& delegate, surface_producer_.emplace(session_connection_->get()); #if defined(LEGACY_FUCHSIA_EMBEDDER) if (use_legacy_renderer_) { - legacy_external_view_embedder_.emplace( - thread_label_, std::move(view_token), std::move(view_ref_pair), - session_connection_.value()); + legacy_external_view_embedder_ = + std::make_shared( + thread_label_, std::move(view_token), + std::move(view_ref_pair), session_connection_.value()); } else #endif { - external_view_embedder_.emplace( - thread_label_, std::move(view_token), std::move(view_ref_pair), - session_connection_.value(), surface_producer_.value()); + external_view_embedder_ = + std::make_shared( + thread_label_, std::move(view_token), + std::move(view_ref_pair), session_connection_.value(), + surface_producer_.value()); } })); @@ -205,6 +209,7 @@ Engine::Engine(Delegate& delegate, on_update_view_callback = std::move(on_update_view_callback), on_destroy_view_callback = std::move(on_destroy_view_callback), on_create_surface_callback = std::move(on_create_surface_callback), + external_view_embedder = GetExternalViewEmbedder(), vsync_offset = product_config.get_vsync_offset(), vsync_handle = vsync_event_.get()](flutter::Shell& shell) mutable { return std::make_unique( @@ -222,6 +227,7 @@ Engine::Engine(Delegate& delegate, std::move(on_update_view_callback), std::move(on_destroy_view_callback), std::move(on_create_surface_callback), + external_view_embedder, // external view embedder std::move(vsync_offset), // vsync offset vsync_handle); }); @@ -238,7 +244,7 @@ Engine::Engine(Delegate& delegate, auto compositor_context = std::make_unique( session_connection_.value(), surface_producer_.value(), - legacy_external_view_embedder_.value()); + legacy_external_view_embedder_); return std::make_unique( shell, std::move(compositor_context)); } else { @@ -577,22 +583,28 @@ void Engine::DestroyView(int64_t view_id) { } std::unique_ptr Engine::CreateSurface() { - flutter::ExternalViewEmbedder* external_view_embedder = nullptr; + return std::make_unique(thread_label_, GetExternalViewEmbedder(), + surface_producer_->gr_context()); +} + +std::shared_ptr +Engine::GetExternalViewEmbedder() { + std::shared_ptr external_view_embedder = + nullptr; #if defined(LEGACY_FUCHSIA_EMBEDDER) if (use_legacy_renderer_) { FML_CHECK(legacy_external_view_embedder_); - external_view_embedder = &legacy_external_view_embedder_.value(); + external_view_embedder = legacy_external_view_embedder_; } else #endif { FML_CHECK(external_view_embedder_); - external_view_embedder = &external_view_embedder_.value(); + external_view_embedder = external_view_embedder_; } FML_CHECK(external_view_embedder); - return std::make_unique(thread_label_, external_view_embedder, - surface_producer_->gr_context()); + return external_view_embedder; } #if !defined(DART_PRODUCT) diff --git a/shell/platform/fuchsia/flutter/engine.h b/shell/platform/fuchsia/flutter/engine.h index 20e14e849fc17..29868c731c45b 100644 --- a/shell/platform/fuchsia/flutter/engine.h +++ b/shell/platform/fuchsia/flutter/engine.h @@ -14,6 +14,7 @@ #include #include +#include "flow/embedded_views.h" #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" #include "flutter/shell/common/shell.h" @@ -69,9 +70,9 @@ class Engine final { std::optional session_connection_; std::optional surface_producer_; - std::optional external_view_embedder_; + std::shared_ptr external_view_embedder_; #if defined(LEGACY_FUCHSIA_EMBEDDER) - std::optional legacy_external_view_embedder_; + std::shared_ptr legacy_external_view_embedder_; #endif std::unique_ptr isolate_configurator_; @@ -97,6 +98,7 @@ class Engine final { void CreateView(int64_t view_id, bool hit_testable, bool focusable); void UpdateView(int64_t view_id, bool hit_testable, bool focusable); void DestroyView(int64_t view_id); + std::shared_ptr GetExternalViewEmbedder(); std::unique_ptr CreateSurface(); diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index db227ffd336de..fa7e9a77c9357 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flow/embedded_views.h" #define RAPIDJSON_HAS_STDSTRING 1 #include "platform_view.h" @@ -66,6 +67,7 @@ PlatformView::PlatformView( OnUpdateView on_update_view_callback, OnDestroyView on_destroy_view_callback, OnCreateSurface on_create_surface_callback, + std::shared_ptr external_view_embedder, fml::TimeDelta vsync_offset, zx_handle_t vsync_event_handle) : flutter::PlatformView(delegate, std::move(task_runners)), @@ -80,6 +82,7 @@ PlatformView::PlatformView( on_update_view_callback_(std::move(on_update_view_callback)), on_destroy_view_callback_(std::move(on_destroy_view_callback)), on_create_surface_callback_(std::move(on_create_surface_callback)), + external_view_embedder_(external_view_embedder), ime_client_(this), vsync_offset_(std::move(vsync_offset)), vsync_event_handle_(vsync_event_handle) { diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index a888298f9ef38..9f29b3397bf09 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -14,11 +14,13 @@ #include #include +#include "flow/embedded_views.h" #include "flutter/fml/macros.h" #include "flutter/fml/time/time_delta.h" #include "flutter/shell/common/platform_view.h" #include "accessibility_bridge.h" +#include "shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h" namespace flutter_runner { @@ -61,6 +63,7 @@ class PlatformView final : public flutter::PlatformView, OnUpdateView on_update_view_callback, OnDestroyView on_destroy_view_callback, OnCreateSurface on_create_surface_callback, + std::shared_ptr view_embedder, fml::TimeDelta vsync_offset, zx_handle_t vsync_event_handle); @@ -169,6 +172,7 @@ class PlatformView final : public flutter::PlatformView, OnUpdateView on_update_view_callback_; OnDestroyView on_destroy_view_callback_; OnCreateSurface on_create_surface_callback_; + std::shared_ptr external_view_embedder_; int current_text_input_client_ = 0; fidl::Binding ime_client_; diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 63f010ed663c6..38f771ce257b9 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -184,10 +184,11 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { // Test create surface callback function. sk_sp gr_context = GrDirectContext::MakeMock(nullptr, GrContextOptions()); - MockExternalViewEmbedder view_embedder; + std::shared_ptr view_embedder = + std::make_shared(); auto CreateSurfaceCallback = [&view_embedder, gr_context]() { return std::make_unique( - "PlatformViewTest", &view_embedder, gr_context.get()); + "PlatformViewTest", view_embedder, gr_context.get()); }; auto platform_view = flutter_runner::PlatformView( @@ -205,6 +206,7 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, CreateSurfaceCallback, // on_create_surface_callback, + view_embedder, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -213,7 +215,7 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { RunLoopUntilIdle(); EXPECT_EQ(gr_context.get(), delegate.surface()->GetContext()); - EXPECT_EQ(&view_embedder, delegate.surface()->GetExternalViewEmbedder()); + EXPECT_EQ(view_embedder.get(), delegate.surface()->GetExternalViewEmbedder()); } // This test makes sure that the PlatformView correctly registers Scenic @@ -248,6 +250,7 @@ TEST_F(PlatformViewTests, SetViewportMetrics) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -380,6 +383,7 @@ TEST_F(PlatformViewTests, ChangesAccessibilitySettings) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -430,6 +434,7 @@ TEST_F(PlatformViewTests, EnableWireframeTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -491,6 +496,7 @@ TEST_F(PlatformViewTests, CreateViewTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -554,6 +560,7 @@ TEST_F(PlatformViewTests, UpdateViewTest) { UpdateViewCallback, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -617,6 +624,7 @@ TEST_F(PlatformViewTests, DestroyViewTest) { nullptr, // on_update_view_callback, DestroyViewCallback, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -677,6 +685,7 @@ TEST_F(PlatformViewTests, ViewEventsTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -759,6 +768,7 @@ TEST_F(PlatformViewTests, RequestFocusTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -835,6 +845,7 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, + nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); diff --git a/shell/platform/fuchsia/flutter/surface.cc b/shell/platform/fuchsia/flutter/surface.cc index 61f2d25878598..27753721bcc1c 100644 --- a/shell/platform/fuchsia/flutter/surface.cc +++ b/shell/platform/fuchsia/flutter/surface.cc @@ -14,7 +14,7 @@ namespace flutter_runner { Surface::Surface(std::string debug_label, - flutter::ExternalViewEmbedder* view_embedder, + std::shared_ptr view_embedder, GrDirectContext* gr_context) : debug_label_(std::move(debug_label)), view_embedder_(view_embedder), @@ -53,7 +53,7 @@ SkMatrix Surface::GetRootTransformation() const { // |flutter::GetViewEmbedder| flutter::ExternalViewEmbedder* Surface::GetExternalViewEmbedder() { - return view_embedder_; + return view_embedder_.get(); } } // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/surface.h b/shell/platform/fuchsia/flutter/surface.h index 96222c0f2d326..bc95d88cc10d4 100644 --- a/shell/platform/fuchsia/flutter/surface.h +++ b/shell/platform/fuchsia/flutter/surface.h @@ -16,14 +16,14 @@ namespace flutter_runner { class Surface final : public flutter::Surface { public: Surface(std::string debug_label, - flutter::ExternalViewEmbedder* view_embedder, + std::shared_ptr view_embedder, GrDirectContext* gr_context); ~Surface() override; private: const std::string debug_label_; - flutter::ExternalViewEmbedder* view_embedder_; + std::shared_ptr view_embedder_; GrDirectContext* gr_context_; // |flutter::Surface| From 44ea9677ad46d646df3e1c8f2478736bb3c2d5d7 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 15 Oct 2020 17:43:16 -0700 Subject: [PATCH 133/219] Add multi-step IME support to TextInputModel (#21682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add multi-step IME support to TextInputModel This updates the platform-independent TextInputModel to add support for input method (abbreviated IM or IME) composing regions. In contrast to languages such as English, where keyboard input is managed keystroke-by-keystroke, languages such as Japanese require a multi-step input process wherein the user begins a composing sequence, during which point their keystrokes are captured by a system input method and converted into a text sequence. During composing, the user is able to edit the composing range and manage the conversion from keyboard input to text before eventually committing the text to the underlying text input field. To illustrate this, in Japanese, this sequence might look something like the following: 1. User types 'k'. The character 'k' is added to the composing region. Typically, the text 'k' will be inserted inline into the underlying text field but the composing range will be highlighted in some manner, frequently with a highlight or underline. 2. User types 'a'. The composing range is replaced with the phonetic kana character 'か' (ka). The composing range continues to be highlighted. 3. User types 'k'. The character 'k' is appended to the composing range such that the highlighted text is now 'かk' 4. User types 'u'. The trailing 'k' is replaced with the phonetic kana character 'く' (ku) such that the composing range now reads 'かく' The composing range continues to be highlighted. 5. The user presses the space bar to convert the kana characters to kanji. The composing range is replaced with '書く' (kaku: to write). 6. The user presses the space bar again to show other conversions. The user's configured input method (for example, ibus) pops up a completions menu populated with alternatives such as 各 (kaku: every), 描く (kaku: to draw), 核 (kaku: pit of a fruit, nucleus), 角 (kaku: angle), etc. 7. The user uses the arrow keys to navigate the completions menu and select the alternative to input. As they do, the inline composing region in the text field is updated. It continues to be highlighted or underlined. 8. The user hits enter to commit the composing region. The text is committed to the underlying text field and the visual highlighting is removed. 9. If the user presses another key, a new composing sequence begins. If a selection is present when composing begins, it is preserved until the first keypress of input is received, at which point the selection is deleted. If a composing sequence is aborted before the first keypress, the selection is preserved. Creating a new selection (with the mouse, for example) aborts composing and the composing region is automatically committed. A composing range and selection, both with an extent, are not permitted to co-exist. During composing, keyboard navigation via the arrow keys, or home and end (or equivalent shortcuts) is restricted to the composing range, as are deletions via backspace and the delete key. This patch adds two new private convenience methods, `editing_range` and `text_range`. The former returns the range for which editing is currently active -- the composing range, if composing, otherwise the full range of the text. The latter, returns a range from position 0 (inclusive) to `text_.length()` exclusive. * Move SetComposingLength to TextRange::set_* Adds set_base, set_extent, set_start, set_end methods to TextRange. --- shell/platform/common/cpp/text_input_model.cc | 109 +- shell/platform/common/cpp/text_input_model.h | 87 +- .../common/cpp/text_input_model_unittests.cc | 932 +++++++++++++++++- shell/platform/common/cpp/text_range.h | 36 +- .../common/cpp/text_range_unittests.cc | 56 ++ 5 files changed, 1152 insertions(+), 68 deletions(-) diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index da0c1fe1e5ed8..649baede20f51 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/text_input_model.h" @@ -39,22 +38,75 @@ void TextInputModel::SetText(const std::string& text) { utf16_converter; text_ = utf16_converter.from_bytes(text); selection_ = TextRange(0); + composing_range_ = TextRange(0); } bool TextInputModel::SetSelection(const TextRange& range) { - if (!text_range().Contains(range)) { + if (composing_ && !range.collapsed()) { + return false; + } + if (!editable_range().Contains(range)) { return false; } selection_ = range; return true; } +bool TextInputModel::SetComposingRange(const TextRange& range, + size_t cursor_offset) { + if (!composing_ || !text_range().Contains(range)) { + return false; + } + composing_range_ = range; + selection_ = TextRange(range.start() + cursor_offset); + return true; +} + +void TextInputModel::BeginComposing() { + composing_ = true; + composing_range_ = TextRange(selection_.start()); +} + +void TextInputModel::UpdateComposingText(const std::string& composing_text) { + std::wstring_convert, char16_t> + utf16_converter; + std::u16string text = utf16_converter.from_bytes(composing_text); + + // Preserve selection if we get a no-op update to the composing region. + if (text.length() == 0 && composing_range_.collapsed()) { + return; + } + DeleteSelected(); + text_.replace(composing_range_.start(), composing_range_.length(), text); + composing_range_.set_end(composing_range_.start() + text.length()); + selection_ = TextRange(composing_range_.end()); +} + +void TextInputModel::CommitComposing() { + // Preserve selection if no composing text was entered. + if (composing_range_.collapsed()) { + return; + } + composing_range_ = TextRange(composing_range_.end()); + selection_ = composing_range_; +} + +void TextInputModel::EndComposing() { + composing_ = false; + composing_range_ = TextRange(0); +} + bool TextInputModel::DeleteSelected() { if (selection_.collapsed()) { return false; } - text_.erase(selection_.start(), selection_.length()); - selection_ = TextRange(selection_.start()); + size_t start = selection_.start(); + text_.erase(start, selection_.length()); + selection_ = TextRange(start); + if (composing_) { + // This occurs only immediately after composing has begun with a selection. + composing_range_ = selection_; + } return true; } @@ -74,6 +126,12 @@ void TextInputModel::AddCodePoint(char32_t c) { void TextInputModel::AddText(const std::u16string& text) { DeleteSelected(); + if (composing_) { + // Delete the current composing text, set the cursor to composing start. + text_.erase(composing_range_.start(), composing_range_.length()); + selection_ = TextRange(composing_range_.start()); + composing_range_.set_end(composing_range_.start() + text.length()); + } size_t position = selection_.position(); text_.insert(position, text); selection_ = TextRange(position + text.length()); @@ -89,12 +147,15 @@ bool TextInputModel::Backspace() { if (DeleteSelected()) { return true; } - // If there's no selection, delete the preceding codepoint. + // There is no selection. Delete the preceding codepoint. size_t position = selection_.position(); - if (position != 0) { + if (position != editable_range().start()) { int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; text_.erase(position - count, count); selection_ = TextRange(position - count); + if (composing_) { + composing_range_.set_end(composing_range_.end() - count); + } return true; } return false; @@ -104,36 +165,40 @@ bool TextInputModel::Delete() { if (DeleteSelected()) { return true; } - // If there's no selection, delete the preceding codepoint. + // There is no selection. Delete the preceding codepoint. size_t position = selection_.position(); - if (position != text_.length()) { + if (position < editable_range().end()) { int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; text_.erase(position, count); + if (composing_) { + composing_range_.set_end(composing_range_.end() - count); + } return true; } return false; } bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { + size_t max_pos = editable_range().end(); size_t start = selection_.extent(); if (offset_from_cursor < 0) { for (int i = 0; i < -offset_from_cursor; i++) { // If requested start is before the available text then reduce the // number of characters to delete. - if (start == 0) { + if (start == editable_range().start()) { count = i; break; } start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1; } } else { - for (int i = 0; i < offset_from_cursor && start != text_.length(); i++) { + for (int i = 0; i < offset_from_cursor && start != max_pos; i++) { start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } } auto end = start; - for (int i = 0; i < count && end != text_.length(); i++) { + for (int i = 0; i < count && end != max_pos; i++) { end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1; } @@ -141,25 +206,33 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { return false; } - text_.erase(start, end - start); + auto deleted_length = end - start; + text_.erase(start, deleted_length); // Cursor moves only if deleted area is before it. selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start()); + // Adjust composing range. + if (composing_) { + composing_range_.set_end(composing_range_.end() - deleted_length); + } return true; } bool TextInputModel::MoveCursorToBeginning() { - if (selection_.collapsed() && selection_.position() == 0) + size_t min_pos = editable_range().start(); + if (selection_.collapsed() && selection_.position() == min_pos) { return false; - selection_ = TextRange(0); + } + selection_ = TextRange(min_pos); return true; } bool TextInputModel::MoveCursorToEnd() { - size_t max_pos = text_.length(); - if (selection_.collapsed() && selection_.position() == max_pos) + size_t max_pos = editable_range().end(); + if (selection_.collapsed() && selection_.position() == max_pos) { return false; + } selection_ = TextRange(max_pos); return true; } @@ -172,7 +245,7 @@ bool TextInputModel::MoveCursorForward() { } // Otherwise, move the cursor forward. size_t position = selection_.position(); - if (position != text_.length()) { + if (position != editable_range().end()) { int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; selection_ = TextRange(position + count); return true; @@ -188,7 +261,7 @@ bool TextInputModel::MoveCursorBack() { } // Otherwise, move the cursor backward. size_t position = selection_.position(); - if (position != 0) { + if (position != editable_range().start()) { int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; selection_ = TextRange(position - count); return true; diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index c49f786f0a9c0..a9560b7b1483d 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -28,8 +28,43 @@ class TextInputModel { // Attempts to set the text selection. // // Returns false if the selection is not within the bounds of the text. + // While in composing mode, the selection is restricted to the composing + // range; otherwise, it is restricted to the length of the text. bool SetSelection(const TextRange& range); + // Attempts to set the composing range. + // + // Returns false if the range or offset are out of range for the text, or if + // the offset is outside the composing range. + bool SetComposingRange(const TextRange& range, size_t cursor_offset); + + // Begins IME composing mode. + // + // Resets the composing base and extent to the selection start. The existing + // selection is preserved in case composing is aborted with no changes. Until + // |EndComposing| is called, any further changes to selection base and extent + // are restricted to the composing range. + void BeginComposing(); + + // Replaces the composing range with new text. + // + // If a selection of non-zero length exists, it is deleted if the composing + // text is non-empty. The composing range is adjusted to the length of + // |composing_text| and the selection base and offset are set to the end of + // the composing range. + void UpdateComposingText(const std::string& composing_text); + + // Commits composing range to the string. + // + // Causes the composing base and extent to be collapsed to the end of the + // range. + void CommitComposing(); + + // Ends IME composing mode. + // + // Collapses the composing base and offset to 0. + void EndComposing(); + // Adds a Unicode code point. // // Either appends after the cursor (when selection base and extent are the @@ -52,18 +87,21 @@ class TextInputModel { // Deletes either the selection, or one character ahead of the cursor. // // Deleting one character ahead of the cursor occurs when the selection base - // and extent are the same. + // and extent are the same. When composing is active, deletions are + // restricted to text between the composing base and extent. // // Returns true if any deletion actually occurred. bool Delete(); // Deletes text near the cursor. // - // A section is made starting at @offset code points past the cursor (negative - // values go before the cursor). @count code points are removed. The selection - // may go outside the bounds of the text and will result in only the part - // selection that covers the available text being deleted. The existing - // selection is ignored and removed after this operation. + // A section is made starting at |offset_from_cursor| code points past the + // cursor (negative values go before the cursor). |count| code points are + // removed. The selection may go outside the bounds of the available text and + // will result in only the part selection that covers the available text + // being deleted. The existing selection is ignored and removed after this + // operation. When composing is active, deletions are restricted to the + // composing range. // // Returns true if any deletion actually occurred. bool DeleteSurrounding(int offset_from_cursor, int count); @@ -71,7 +109,8 @@ class TextInputModel { // Deletes either the selection, or one character behind the cursor. // // Deleting one character behind the cursor occurs when the selection base - // and extent are the same. + // and extent are the same. When composing is active, deletions are + // restricted to the text between the composing base and extent. // // Returns true if any deletion actually occurred. bool Backspace(); @@ -79,21 +118,31 @@ class TextInputModel { // Attempts to move the cursor backward. // // Returns true if the cursor could be moved. If a selection is active, moves - // to the start of the selection. + // to the start of the selection. If composing is active, motion is + // restricted to the composing range. bool MoveCursorBack(); // Attempts to move the cursor forward. // // Returns true if the cursor could be moved. If a selection is active, moves - // to the end of the selection. + // to the end of the selection. If composing is active, motion is restricted + // to the composing range. bool MoveCursorForward(); // Attempts to move the cursor to the beginning. // + // If composing is active, the cursor is moved to the beginning of the + // composing range; otherwise, it is moved to the beginning of the text. If + // composing is active, motion is restricted to the composing range. + // // Returns true if the cursor could be moved. bool MoveCursorToBeginning(); - // Attempts to move the cursor to the back. + // Attempts to move the cursor to the end. + // + // If composing is active, the cursor is moved to the end of the composing + // range; otherwise, it is moved to the end of the text. If composing is + // active, motion is restricted to the composing range. // // Returns true if the cursor could be moved. bool MoveCursorToEnd(); @@ -108,6 +157,14 @@ class TextInputModel { // The current selection. TextRange selection() const { return selection_; } + // The composing range. + // + // If not in composing mode, returns a collapsed range at position 0. + TextRange composing_range() const { return composing_range_; } + + // Whether multi-step input composing mode is active. + bool composing() const { return composing_; } + private: // Deletes the current selection, if any. // @@ -115,11 +172,21 @@ class TextInputModel { // reset to the start of the selected range. bool DeleteSelected(); + // Returns the currently editable text range. + // + // In composing mode, returns the composing range; otherwise, returns a range + // covering the entire text. + TextRange editable_range() const { + return composing_ ? composing_range_ : text_range(); + } + // Returns a range covering the entire text. TextRange text_range() const { return TextRange(0, text_.length()); } std::u16string text_; TextRange selection_ = TextRange(0); + TextRange composing_range_ = TextRange(0); + bool composing_ = false; }; } // namespace flutter diff --git a/shell/platform/common/cpp/text_input_model_unittests.cc b/shell/platform/common/cpp/text_input_model_unittests.cc index 730f2b9905428..6ee80206295eb 100644 --- a/shell/platform/common/cpp/text_input_model_unittests.cc +++ b/shell/platform/common/cpp/text_input_model_unittests.cc @@ -52,6 +52,18 @@ TEST(TextInputModel, SetSelectionStart) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingStart) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -60,14 +72,38 @@ TEST(TextInputModel, SetSelectionMiddle) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingMiddle) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(2))); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, SetSelectionEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionComposingEnd) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(4))); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -76,6 +112,18 @@ TEST(TextInputModel, SetSelectionWthExtent) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_EQ(model->selection(), TextRange(1, 4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionWthExtentComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(1, 4))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -84,6 +132,18 @@ TEST(TextInputModel, SetSelectionReverseExtent) { model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_EQ(model->selection(), TextRange(4, 1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetSelectionReverseExtentComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(4, 1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -92,7 +152,255 @@ TEST(TextInputModel, SetSelectionOutsideString) { model->SetText("ABCDE"); EXPECT_FALSE(model->SetSelection(TextRange(4, 6))); EXPECT_FALSE(model->SetSelection(TextRange(5, 6))); - EXPECT_FALSE(model->SetSelection(TextRange(6, 6))); + EXPECT_FALSE(model->SetSelection(TextRange(6))); +} + +TEST(TextInputModel, SetSelectionOutsideComposingRange) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->SetSelection(TextRange(0))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_FALSE(model->SetSelection(TextRange(5))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); +} + +TEST(TextInputModel, SetComposingRangeStart) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(0, 0), 0)); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeMiddle) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(2, 2), 0)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeEnd) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(5, 5), 0)); + EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(5)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeWithExtent) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeReverseExtent) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, SetComposingRangeOutsideString) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_FALSE(model->SetComposingRange(TextRange(4, 6), 0)); + EXPECT_FALSE(model->SetComposingRange(TextRange(5, 6), 0)); + EXPECT_FALSE(model->SetComposingRange(TextRange(6, 6), 0)); +} + +// Composing sequence with no initial selection and no text input. +TEST(TextInputModel, CommitComposingNoTextWithNoSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(0)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +// Composing sequence with an initial selection and no text input. +TEST(TextInputModel, CommitComposingNoTextWithSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1, 3)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify no changes on CommitComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +// Composing sequence with no initial selection. +TEST(TextInputModel, CommitComposingTextWithNoSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify selection base, extent and composing extent increment as text is + // entered. Verify composing base does not change. + model->UpdateComposingText("つ"); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "AつBCDE"); + model->UpdateComposingText("つる"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "AつるBCDE"); + + // Verify that cursor position is set to correct offset from composing base. + model->UpdateComposingText("鶴"); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴BCDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴BCDE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("が"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(2, 3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); + + // Verify no changes on EndComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がBCDE"); +} + +// Composing sequence with an initial selection. +TEST(TextInputModel, CommitComposingTextWithSelection) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->SetSelection(TextRange(1, 3)); + + // Verify no changes on BeginComposing. + model->BeginComposing(); + EXPECT_EQ(model->selection(), TextRange(1, 3)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); + + // Verify selection is replaced and selection base, extent and composing + // extent increment to the position immediately after the composing text. + // Verify composing base does not change. + model->UpdateComposingText("つ"); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "AつDE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("つる"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "AつるDE"); + + // Verify that cursor position is set to correct offset from composing base. + model->UpdateComposingText("鶴"); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴DE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(2)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴DE"); + + // Verify that further text entry increments the selection base, extent and + // the composing extent. Verify that composing base does not change. + model->UpdateComposingText("が"); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(2, 3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); + + // Verify composing base is set to composing extent on commit. + model->CommitComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); + + // Verify no changes on EndComposing. + model->EndComposing(); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "A鶴がDE"); +} + +TEST(TextInputModel, UpdateComposingRemovesLastComposingCharacter) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + model->SetComposingRange(TextRange(1, 2), 1); + model->UpdateComposingText(""); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1)); + model->SetText("ACDE"); } TEST(TextInputModel, AddCodePoint) { @@ -103,6 +411,7 @@ TEST(TextInputModel, AddCodePoint) { model->AddCodePoint('D'); model->AddCodePoint('E'); EXPECT_EQ(model->selection(), TextRange(6)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB😄DE"); } @@ -112,6 +421,7 @@ TEST(TextInputModel, AddCodePointSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint('x'); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } @@ -121,6 +431,7 @@ TEST(TextInputModel, AddCodePointReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint('x'); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxE"); } @@ -130,6 +441,7 @@ TEST(TextInputModel, AddCodePointSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddCodePoint(0x1f604); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } @@ -139,6 +451,7 @@ TEST(TextInputModel, AddCodePointReverseSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddCodePoint(0x1f604); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄E"); } @@ -148,6 +461,7 @@ TEST(TextInputModel, AddText) { model->AddText("😄"); model->AddText("FGHIJ"); EXPECT_EQ(model->selection(), TextRange(12)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE😄FGHIJ"); } @@ -157,6 +471,7 @@ TEST(TextInputModel, AddTextSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText("xy"); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } @@ -166,6 +481,7 @@ TEST(TextInputModel, AddTextReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText("xy"); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AxyE"); } @@ -175,6 +491,7 @@ TEST(TextInputModel, AddTextSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); model->AddText(u"😄🙃"); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } @@ -184,42 +501,47 @@ TEST(TextInputModel, AddTextReverseSelectionWideCharacter) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); model->AddText(u"😄🙃"); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "A😄🙃E"); } TEST(TextInputModel, DeleteStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "BCDE"); } TEST(TextInputModel, DeleteMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); ASSERT_FALSE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, DeleteWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🧐"); } @@ -229,6 +551,7 @@ TEST(TextInputModel, DeleteSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } @@ -238,96 +561,276 @@ TEST(TextInputModel, DeleteReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } +TEST(TextInputModel, DeleteStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, DeleteStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, DeleteMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + ASSERT_TRUE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + ASSERT_FALSE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, DeleteEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + ASSERT_FALSE(model->Delete()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + TEST(TextInputModel, DeleteSurroundingAtCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); + EXPECT_TRUE(model->DeleteSurrounding(0, 1)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + +TEST(TextInputModel, DeleteSurroundingAtCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); EXPECT_STREQ(model->GetText().c_str(), "ABDE"); } TEST(TextInputModel, DeleteSurroundingAtCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(0, 3)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } +TEST(TextInputModel, DeleteSurroundingAtCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->DeleteSurrounding(0, 2)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingAtCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(0, 4)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AB"); } +TEST(TextInputModel, DeleteSurroundingAtCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->DeleteSurrounding(0, 4)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-1, 1)); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-1, 1)); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-2, 2)); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-2, 2)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ADE"); +} + TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(-3, 3)); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "CDE"); } +TEST(TextInputModel, DeleteSurroundingBeforeCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2)); + EXPECT_TRUE(model->DeleteSurrounding(-3, 3)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ADE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursor) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 1)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 1)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABDE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursorAll) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 2)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorAllComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 2)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingAfterCursorGreedy) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->DeleteSurrounding(1, 3)); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABC"); } +TEST(TextInputModel, DeleteSurroundingAfterCursorGreedyComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->DeleteSurrounding(1, 3)); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 2)); + EXPECT_STREQ(model->GetText().c_str(), "ABE"); +} + TEST(TextInputModel, DeleteSurroundingSelection) { auto model = std::make_unique(); model->SetText("ABCDE"); EXPECT_TRUE(model->SetSelection(TextRange(2, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } @@ -337,42 +840,47 @@ TEST(TextInputModel, DeleteSurroundingReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 3))); EXPECT_TRUE(model->DeleteSurrounding(0, 1)); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCE"); } TEST(TextInputModel, BackspaceStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); ASSERT_FALSE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, BackspaceMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ACDE"); } TEST(TextInputModel, BackspaceEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCD"); } TEST(TextInputModel, BackspaceWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->Backspace()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🤪🧐"); } @@ -382,6 +890,7 @@ TEST(TextInputModel, BackspaceSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } @@ -391,42 +900,113 @@ TEST(TextInputModel, BackspaceReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); ASSERT_TRUE(model->Delete()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "AE"); } +TEST(TextInputModel, BackspaceStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + ASSERT_FALSE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, BackspaceStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + ASSERT_FALSE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, BackspaceMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, BackspaceMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ACDE"); +} + +TEST(TextInputModel, BackspaceEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 3)); + EXPECT_STREQ(model->GetText().c_str(), "ABCE"); +} + +TEST(TextInputModel, BackspaceEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + ASSERT_TRUE(model->Backspace()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(3, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCE"); +} + TEST(TextInputModel, MoveCursorForwardStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_FALSE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorForwardWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(6)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } @@ -436,6 +1016,7 @@ TEST(TextInputModel, MoveCursorForwardSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -445,42 +1026,113 @@ TEST(TextInputModel, MoveCursorForwardReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_FALSE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorForwardEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_FALSE(model->MoveCursorForward()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_FALSE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorBackWideCharacters) { auto model = std::make_unique(); model->SetText("😄🙃🤪🧐"); - EXPECT_TRUE(model->SetSelection(TextRange(4, 4))); + EXPECT_TRUE(model->SetSelection(TextRange(4))); ASSERT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(2)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "😄🙃🤪🧐"); } @@ -490,6 +1142,7 @@ TEST(TextInputModel, MoveCursorBackSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -499,33 +1152,105 @@ TEST(TextInputModel, MoveCursorBackReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorBack()); EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_FALSE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_TRUE(model->SetSelection(TextRange(1))); + EXPECT_FALSE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorBackEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_TRUE(model->MoveCursorBack()); + EXPECT_EQ(model->selection(), TextRange(3)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_FALSE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToBeginningEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -535,6 +1260,7 @@ TEST(TextInputModel, MoveCursorToBeginningSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -544,33 +1270,103 @@ TEST(TextInputModel, MoveCursorToBeginningReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToBeginning()); EXPECT_EQ(model->selection(), TextRange(0)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_FALSE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0)); + EXPECT_FALSE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToBeginningEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_TRUE(model->MoveCursorToBeginning()); + EXPECT_EQ(model->selection(), TextRange(1)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndStart) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndMiddle) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(2, 2))); + EXPECT_TRUE(model->SetSelection(TextRange(2))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } TEST(TextInputModel, MoveCursorToEndEnd) { auto model = std::make_unique(); model->SetText("ABCDE"); - EXPECT_TRUE(model->SetSelection(TextRange(5, 5))); + EXPECT_TRUE(model->SetSelection(TextRange(5))); EXPECT_FALSE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -580,6 +1376,7 @@ TEST(TextInputModel, MoveCursorToEndSelection) { EXPECT_TRUE(model->SetSelection(TextRange(1, 4))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -589,6 +1386,73 @@ TEST(TextInputModel, MoveCursorToEndReverseSelection) { EXPECT_TRUE(model->SetSelection(TextRange(4, 1))); EXPECT_TRUE(model->MoveCursorToEnd()); EXPECT_EQ(model->selection(), TextRange(5)); + EXPECT_EQ(model->composing_range(), TextRange(0)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndStartComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndStartReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndMiddleComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndMiddleReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1)); + EXPECT_TRUE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndEndComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3)); + EXPECT_FALSE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(1, 4)); + EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); +} + +TEST(TextInputModel, MoveCursorToEndEndReverseComposing) { + auto model = std::make_unique(); + model->SetText("ABCDE"); + model->BeginComposing(); + EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3)); + EXPECT_FALSE(model->MoveCursorToEnd()); + EXPECT_EQ(model->selection(), TextRange(4)); + EXPECT_EQ(model->composing_range(), TextRange(4, 1)); EXPECT_STREQ(model->GetText().c_str(), "ABCDE"); } @@ -596,7 +1460,7 @@ TEST(TextInputModel, GetCursorOffset) { auto model = std::make_unique(); // These characters take 1, 2, 3 and 4 bytes in UTF-8. model->SetText("$¢€𐍈"); - EXPECT_TRUE(model->SetSelection(TextRange(0, 0))); + EXPECT_TRUE(model->SetSelection(TextRange(0))); EXPECT_EQ(model->GetCursorOffset(), 0); EXPECT_TRUE(model->MoveCursorForward()); EXPECT_EQ(model->GetCursorOffset(), 1); diff --git a/shell/platform/common/cpp/text_range.h b/shell/platform/common/cpp/text_range.h index 60482da5158ac..d951c45f98e6e 100644 --- a/shell/platform/common/cpp/text_range.h +++ b/shell/platform/common/cpp/text_range.h @@ -21,19 +21,43 @@ class TextRange { virtual ~TextRange() = default; - // Returns the base position of the range. + // The base position of the range. size_t base() const { return base_; } - // Returns the extent position of the range. + // Sets the base position of the range. + void set_base(size_t pos) { base_ = pos; } + + // The extent position of the range. size_t extent() const { return extent_; } - // Returns the lesser of the base and extent positions. + // Sets the extent position of the range. + void set_extent(size_t pos) { extent_ = pos; } + + // The lesser of the base and extent positions. size_t start() const { return std::min(base_, extent_); } - // Returns the greater of the base and extent positions. + // Sets the start position of the range. + void set_start(size_t pos) { + if (base_ <= extent_) { + base_ = pos; + } else { + extent_ = pos; + } + } + + // The greater of the base and extent positions. size_t end() const { return std::max(base_, extent_); } - // Returns the position of a collapsed range. + // Sets the end position of the range. + void set_end(size_t pos) { + if (base_ <= extent_) { + extent_ = pos; + } else { + base_ = pos; + } + } + + // The position of a collapsed range. // // Asserts that the range is of length 0. size_t position() const { @@ -41,7 +65,7 @@ class TextRange { return extent_; } - // Returns the length of the range. + // The length of the range. size_t length() const { return end() - start(); } // Returns true if the range is of length 0. diff --git a/shell/platform/common/cpp/text_range_unittests.cc b/shell/platform/common/cpp/text_range_unittests.cc index eaf30d935a515..9aecf34309b98 100644 --- a/shell/platform/common/cpp/text_range_unittests.cc +++ b/shell/platform/common/cpp/text_range_unittests.cc @@ -50,6 +50,62 @@ TEST(TextRange, TextRangeFromReversedRange) { EXPECT_FALSE(range.collapsed()); } +TEST(TextRange, SetBase) { + TextRange range(3, 7); + range.set_base(4); + EXPECT_EQ(range.base(), size_t(4)); + EXPECT_EQ(range.extent(), size_t(7)); +} + +TEST(TextRange, SetBaseReversed) { + TextRange range(7, 3); + range.set_base(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(3)); +} + +TEST(TextRange, SetExtent) { + TextRange range(3, 7); + range.set_extent(6); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(6)); +} + +TEST(TextRange, SetExtentReversed) { + TextRange range(7, 3); + range.set_extent(4); + EXPECT_EQ(range.base(), size_t(7)); + EXPECT_EQ(range.extent(), size_t(4)); +} + +TEST(TextRange, SetStart) { + TextRange range(3, 7); + range.set_start(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(7)); +} + +TEST(TextRange, SetStartReversed) { + TextRange range(7, 3); + range.set_start(5); + EXPECT_EQ(range.base(), size_t(7)); + EXPECT_EQ(range.extent(), size_t(5)); +} + +TEST(TextRange, SetEnd) { + TextRange range(3, 7); + range.set_end(6); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(6)); +} + +TEST(TextRange, SetEndReversed) { + TextRange range(7, 3); + range.set_end(5); + EXPECT_EQ(range.base(), size_t(5)); + EXPECT_EQ(range.extent(), size_t(3)); +} + TEST(TextRange, ContainsPreStartPosition) { TextRange range(2, 6); EXPECT_FALSE(range.Contains(1)); From 62459b3b8672babd4b18d8018c5402ac3a043441 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 15 Oct 2020 17:48:14 -0700 Subject: [PATCH 134/219] [embedder] Platform View owns lifecycle of external view embedder (#21847) Changing it to shared_ptr and migrating the ownership from surface to platform view. --- .../platform/embedder/embedder_surface_gl.cc | 4 ++-- shell/platform/embedder/embedder_surface_gl.h | 4 ++-- .../embedder/embedder_surface_software.cc | 4 ++-- .../embedder/embedder_surface_software.h | 4 ++-- .../embedder/platform_view_embedder.cc | 20 ++++++++++--------- .../embedder/platform_view_embedder.h | 6 ++++-- 6 files changed, 23 insertions(+), 19 deletions(-) diff --git a/shell/platform/embedder/embedder_surface_gl.cc b/shell/platform/embedder/embedder_surface_gl.cc index 6b88fcb9d14e1..05ff3e95ed3cb 100644 --- a/shell/platform/embedder/embedder_surface_gl.cc +++ b/shell/platform/embedder/embedder_surface_gl.cc @@ -11,10 +11,10 @@ namespace flutter { EmbedderSurfaceGL::EmbedderSurfaceGL( GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, - std::unique_ptr external_view_embedder) + std::shared_ptr external_view_embedder) : gl_dispatch_table_(gl_dispatch_table), fbo_reset_after_present_(fbo_reset_after_present), - external_view_embedder_(std::move(external_view_embedder)) { + external_view_embedder_(external_view_embedder) { // Make sure all required members of the dispatch table are checked. if (!gl_dispatch_table_.gl_make_current_callback || !gl_dispatch_table_.gl_clear_current_callback || diff --git a/shell/platform/embedder/embedder_surface_gl.h b/shell/platform/embedder/embedder_surface_gl.h index 42141d767d074..43ad1e4778bf6 100644 --- a/shell/platform/embedder/embedder_surface_gl.h +++ b/shell/platform/embedder/embedder_surface_gl.h @@ -29,7 +29,7 @@ class EmbedderSurfaceGL final : public EmbedderSurface, EmbedderSurfaceGL( GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, - std::unique_ptr external_view_embedder); + std::shared_ptr external_view_embedder); ~EmbedderSurfaceGL() override; @@ -38,7 +38,7 @@ class EmbedderSurfaceGL final : public EmbedderSurface, GLDispatchTable gl_dispatch_table_; bool fbo_reset_after_present_; - std::unique_ptr external_view_embedder_; + std::shared_ptr external_view_embedder_; // |EmbedderSurface| bool IsValid() const override; diff --git a/shell/platform/embedder/embedder_surface_software.cc b/shell/platform/embedder/embedder_surface_software.cc index 9472404f0d429..914087ec1081b 100644 --- a/shell/platform/embedder/embedder_surface_software.cc +++ b/shell/platform/embedder/embedder_surface_software.cc @@ -11,9 +11,9 @@ namespace flutter { EmbedderSurfaceSoftware::EmbedderSurfaceSoftware( SoftwareDispatchTable software_dispatch_table, - std::unique_ptr external_view_embedder) + std::shared_ptr external_view_embedder) : software_dispatch_table_(software_dispatch_table), - external_view_embedder_(std::move(external_view_embedder)) { + external_view_embedder_(external_view_embedder) { if (!software_dispatch_table_.software_present_backing_store) { return; } diff --git a/shell/platform/embedder/embedder_surface_software.h b/shell/platform/embedder/embedder_surface_software.h index 8ed55ceec5b6a..a08122809f92b 100644 --- a/shell/platform/embedder/embedder_surface_software.h +++ b/shell/platform/embedder/embedder_surface_software.h @@ -22,7 +22,7 @@ class EmbedderSurfaceSoftware final : public EmbedderSurface, EmbedderSurfaceSoftware( SoftwareDispatchTable software_dispatch_table, - std::unique_ptr external_view_embedder); + std::shared_ptr external_view_embedder); ~EmbedderSurfaceSoftware() override; @@ -30,7 +30,7 @@ class EmbedderSurfaceSoftware final : public EmbedderSurface, bool valid_ = false; SoftwareDispatchTable software_dispatch_table_; sk_sp sk_surface_; - std::unique_ptr external_view_embedder_; + std::shared_ptr external_view_embedder_; // |EmbedderSurface| bool IsValid() const override; diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index 8d06b3ce164a6..de51eb31afcc4 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -11,11 +11,12 @@ PlatformViewEmbedder::PlatformViewEmbedder( flutter::TaskRunners task_runners, EmbedderSurfaceSoftware::SoftwareDispatchTable software_dispatch_table, PlatformDispatchTable platform_dispatch_table, - std::unique_ptr external_view_embedder) + std::shared_ptr external_view_embedder) : PlatformView(delegate, std::move(task_runners)), - embedder_surface_(std::make_unique( - software_dispatch_table, - std::move(external_view_embedder))), + external_view_embedder_(external_view_embedder), + embedder_surface_( + std::make_unique(software_dispatch_table, + external_view_embedder_)), platform_dispatch_table_(platform_dispatch_table) {} #ifdef SHELL_ENABLE_GL @@ -25,12 +26,13 @@ PlatformViewEmbedder::PlatformViewEmbedder( EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, PlatformDispatchTable platform_dispatch_table, - std::unique_ptr external_view_embedder) + std::shared_ptr external_view_embedder) : PlatformView(delegate, std::move(task_runners)), - embedder_surface_(std::make_unique( - gl_dispatch_table, - fbo_reset_after_present, - std::move(external_view_embedder))), + external_view_embedder_(external_view_embedder), + embedder_surface_( + std::make_unique(gl_dispatch_table, + fbo_reset_after_present, + external_view_embedder_)), platform_dispatch_table_(platform_dispatch_table) {} #endif diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 85d3a91290dee..6fefcf8b20a9d 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -7,6 +7,7 @@ #include +#include "flow/embedded_views.h" #include "flutter/fml/macros.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -49,7 +50,7 @@ class PlatformViewEmbedder final : public PlatformView { flutter::TaskRunners task_runners, EmbedderSurfaceSoftware::SoftwareDispatchTable software_dispatch_table, PlatformDispatchTable platform_dispatch_table, - std::unique_ptr external_view_embedder); + std::shared_ptr external_view_embedder); #ifdef SHELL_ENABLE_GL // Creates a platform view that sets up an OpenGL rasterizer. @@ -59,7 +60,7 @@ class PlatformViewEmbedder final : public PlatformView { EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, PlatformDispatchTable platform_dispatch_table, - std::unique_ptr external_view_embedder); + std::shared_ptr external_view_embedder); #endif ~PlatformViewEmbedder() override; @@ -74,6 +75,7 @@ class PlatformViewEmbedder final : public PlatformView { fml::RefPtr message) override; private: + std::shared_ptr external_view_embedder_; std::unique_ptr embedder_surface_; PlatformDispatchTable platform_dispatch_table_; From 6a3b5feb62cf29481545c1154c14eb2e3890a0cd Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Thu, 15 Oct 2020 19:56:13 -0700 Subject: [PATCH 135/219] [ios] Refactor IOSSurface factory and unify surface creation (#21877) --- ci/licenses_golden/licenses_flutter | 2 + shell/platform/darwin/ios/BUILD.gn | 2 + .../ios/framework/Source/FlutterEngine.mm | 26 ++++++++---- .../Source/FlutterEnginePlatformViewTest.mm | 1 + .../ios/framework/Source/FlutterOverlayView.h | 2 - .../framework/Source/FlutterOverlayView.mm | 8 ---- .../framework/Source/FlutterPlatformViews.mm | 9 ++-- .../Source/FlutterPlatformViewsTest.mm | 42 +++++++++++++++---- .../Source/FlutterPlatformViews_Internal.h | 9 +++- .../Source/FlutterPlatformViews_Internal.mm | 5 ++- .../darwin/ios/framework/Source/FlutterView.h | 1 - .../ios/framework/Source/FlutterView.mm | 9 ---- .../Source/accessibility_bridge_test.mm | 17 +++++++- .../platform/darwin/ios/ios_surface_factory.h | 39 +++++++++++++++++ .../darwin/ios/ios_surface_factory.mm | 30 +++++++++++++ shell/platform/darwin/ios/platform_view_ios.h | 3 ++ .../platform/darwin/ios/platform_view_ios.mm | 8 +++- 17 files changed, 167 insertions(+), 46 deletions(-) create mode 100644 shell/platform/darwin/ios/ios_surface_factory.h create mode 100644 shell/platform/darwin/ios/ios_surface_factory.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b1018822b05e9..a9d8d5cf71974 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1010,6 +1010,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.mm +FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_factory.h +FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_factory.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.h diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 793c97be93701..05c39560e265b 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -99,6 +99,8 @@ source_set("flutter_framework_source") { "ios_render_target_gl.mm", "ios_surface.h", "ios_surface.mm", + "ios_surface_factory.h", + "ios_surface_factory.mm", "ios_surface_gl.h", "ios_surface_gl.mm", "ios_surface_software.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 5248f59a98b34..2c55925a2c4d5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -28,7 +28,9 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" +#import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "flutter/shell/profiling/sampling_profiler.h" @@ -63,6 +65,7 @@ @implementation FlutterEngine { fml::WeakPtr _viewController; fml::scoped_nsobject _publisher; + std::shared_ptr _surfaceFactory; std::unique_ptr _platformViewsController; std::unique_ptr _profiler_metrics; std::unique_ptr _profiler; @@ -127,7 +130,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix _pluginPublications = [NSMutableDictionary new]; _registrars = [[NSMutableDictionary alloc] init]; - _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); + [self ensurePlatformViewController]; _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self]; _connections.reset(new flutter::ConnectionCollection()); @@ -161,6 +164,16 @@ - (instancetype)initWithName:(NSString*)labelPrefix return self; } +- (void)ensurePlatformViewController { + if (!_platformViewsController) { + auto renderingApi = flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering); + _surfaceFactory = flutter::IOSSurfaceFactory::Create(renderingApi); + auto pvc = new flutter::FlutterPlatformViewsController(_surfaceFactory); + _surfaceFactory->SetPlatformViewsController(pvc); + _platformViewsController.reset(pvc); + } +} + - (void)dealloc { /// Notify plugins of dealloc. This should happen first in dealloc since the /// plugins may be talking to things like the binaryMessenger. @@ -510,13 +523,13 @@ - (BOOL)createShell:(NSString*)entrypoint threadHostType}; // Lambda captures by pointers to ObjC objects are fine here because the - // create call is - // synchronous. + // create call is synchronous. flutter::Shell::CreateCallback on_create_platform_view = - [](flutter::Shell& shell) { + [self](flutter::Shell& shell) { + [self ensurePlatformViewController]; return std::make_unique( shell, flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering), - shell.GetTaskRunners()); + self->_surfaceFactory, shell.GetTaskRunners()); }; flutter::Shell::CreateCallback on_create_rasterizer = @@ -544,9 +557,6 @@ - (BOOL)createShell:(NSString*)entrypoint [self setupChannels]; [self onLocaleUpdated:nil]; [self initializeDisplays]; - if (!_platformViewsController) { - _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); - } _publisher.reset([[FlutterObservatoryPublisher alloc] initWithEnableObservatoryPublication:settings.enable_observatory_publication]); [self maybeSetupPlatformViewChannels]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index a202eba4d8d8d..3e002e2aa78ee 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -67,6 +67,7 @@ - (void)testCallsNotifyLowMemory { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id project = OCMClassMock([FlutterDartProject class]); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h index cb56163c6c5c2..b77ac7181853f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h @@ -35,8 +35,6 @@ - (instancetype)init NS_DESIGNATED_INITIALIZER; - (instancetype)initWithContentsScale:(CGFloat)contentsScale; -- (std::unique_ptr)createSurface: - (std::shared_ptr)ios_context; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm index f6a0cd8239756..345d3a36798cb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm @@ -62,14 +62,6 @@ + (Class)layerClass { return [FlutterView layerClass]; } -- (std::unique_ptr)createSurface: - (std::shared_ptr)ios_context { - return flutter::IOSSurface::Create(std::move(ios_context), // context - fml::scoped_nsobject{[self.layer retain]}, // layer - nullptr // platform views controller - ); -} - // TODO(amirh): implement drawLayer to support snapshotting. @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index c0c185c6ee4b7..240134dc4d5b8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -16,6 +16,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" namespace flutter { @@ -32,8 +33,8 @@ overlay_view.reset([[FlutterOverlayView alloc] init]); overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); - std::unique_ptr ios_surface = - [overlay_view.get() createSurface:std::move(ios_context)]; + auto ca_layer = fml::scoped_nsobject{[[overlay_view.get() layer] retain]}; + std::unique_ptr ios_surface = ios_surface_factory_->CreateSurface(ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(); layer = std::make_shared( @@ -44,8 +45,8 @@ overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); - std::unique_ptr ios_surface = - [overlay_view.get() createSurface:std::move(ios_context)]; + auto ca_layer = fml::scoped_nsobject{[[overlay_view.get() layer] retain]}; + std::unique_ptr ios_surface = ios_surface_factory_->CreateSurface(ca_layer); std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); layer = std::make_shared( diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 283a86cf2ae46..0a890b7e92882 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -121,12 +121,16 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -175,12 +179,16 @@ - (void)testCompositePlatformView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -230,12 +238,16 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -301,12 +313,16 @@ - (void)testClipRect { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -373,12 +389,16 @@ - (void)testClipRRect { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -445,12 +465,16 @@ - (void)testClipPath { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -518,12 +542,16 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = std::make_unique(); + auto flutterPlatformViewsController = + std::make_unique(surface_factory); + surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 4fcaaf3874212..d9638d75b6b15 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -61,6 +61,7 @@ void ResetAnchor(CALayer* layer); class IOSContextGL; class IOSSurface; +class IOSSurfaceFactory; struct FlutterPlatformViewLayer { FlutterPlatformViewLayer(fml::scoped_nsobject overlay_view, @@ -87,7 +88,9 @@ struct FlutterPlatformViewLayer { // This class isn't thread safe. class FlutterPlatformViewLayerPool { public: - FlutterPlatformViewLayerPool() = default; + FlutterPlatformViewLayerPool(std::shared_ptr ios_surface_factory) + : ios_surface_factory_(ios_surface_factory) {} + ~FlutterPlatformViewLayerPool() = default; // Gets a layer from the pool if available, or allocates a new one. @@ -118,12 +121,14 @@ class FlutterPlatformViewLayerPool { size_t available_layer_index_ = 0; std::vector> layers_; + const std::shared_ptr ios_surface_factory_; + FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); }; class FlutterPlatformViewsController { public: - FlutterPlatformViewsController(); + FlutterPlatformViewsController(std::shared_ptr surface_factory); ~FlutterPlatformViewsController(); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index a6d3d03653b17..a46684ccb1064 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -23,8 +23,9 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; -FlutterPlatformViewsController::FlutterPlatformViewsController() - : layer_pool_(std::make_unique()), +FlutterPlatformViewsController::FlutterPlatformViewsController( + std::shared_ptr surface_factory) + : layer_pool_(std::make_unique(surface_factory)), weak_factory_(std::make_unique>(this)){}; FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.h b/shell/platform/darwin/ios/framework/Source/FlutterView.h index 3a9c138de02aa..13d7b6ec27cee 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -32,7 +32,6 @@ - (instancetype)initWithDelegate:(id)delegate opaque:(BOOL)opaque NS_DESIGNATED_INITIALIZER; -- (std::unique_ptr)createSurface:(std::shared_ptr)context; // Set by FlutterEngine or FlutterViewController to override software rendering. @property(class, nonatomic) BOOL forceSoftwareRendering; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 3f6b19decdac3..73a9b8b529088 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -83,15 +83,6 @@ + (Class)layerClass { flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering)); } -- (std::unique_ptr)createSurface: - (std::shared_ptr)ios_context { - return flutter::IOSSurface::Create( - std::move(ios_context), // context - fml::scoped_nsobject{[self.layer retain]}, // layer - [_delegate platformViewsController] // platform views controller - ); -} - - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context { TRACE_EVENT0("flutter", "SnapshotFlutterView"); diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 41970e67c8f73..754495cc527f4 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -137,6 +137,7 @@ - (void)testCreate { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); auto bridge = std::make_unique(/*view=*/nil, @@ -156,6 +157,7 @@ - (void)testUpdateSemanticsEmpty { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -182,6 +184,7 @@ - (void)testUpdateSemanticsOneNode { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -224,9 +227,12 @@ - (void)testSemanticsDeallocated { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); + + auto surfaceFactory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*ios_surface_factory=*/surfaceFactory, /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -234,8 +240,9 @@ - (void)testSemanticsDeallocated { std::string label = "some label"; auto flutterPlatformViewsController = - std::make_unique(); + std::make_unique(surfaceFactory); flutterPlatformViewsController->SetFlutterView(mockFlutterView); + surfaceFactory->SetPlatformViewsController(flutterPlatformViewsController.get()); MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory new] autorelease]; flutterPlatformViewsController->RegisterViewFactory( @@ -279,6 +286,7 @@ - (void)testAnnouncesRouteChanges { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -344,6 +352,7 @@ - (void)testAnnouncesRouteChangesWhenNoNamesRoute { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -411,6 +420,7 @@ - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -477,6 +487,7 @@ - (void)testAnnouncesLayoutChangeWithLastFocused { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -549,6 +560,7 @@ - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -623,6 +635,7 @@ - (void)testAnnouncesScrollChangeWithLastFocused { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -693,6 +706,7 @@ - (void)testAnnouncesIgnoresRouteChangesWhenModal { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -760,6 +774,7 @@ - (void)testAccessibilityMessageAfterDeletion { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); fml::AutoResetWaitableEvent latch; thread_task_runner->PostTask([&] { diff --git a/shell/platform/darwin/ios/ios_surface_factory.h b/shell/platform/darwin/ios/ios_surface_factory.h new file mode 100644 index 0000000000000..a692c56754887 --- /dev/null +++ b/shell/platform/darwin/ios/ios_surface_factory.h @@ -0,0 +1,39 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ + +#include + +#import "flutter/shell/platform/darwin/ios/ios_surface.h" +#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" + +namespace flutter { + +class IOSSurfaceFactory { + public: + static std::shared_ptr Create( + IOSRenderingAPI rendering_api); + + explicit IOSSurfaceFactory(std::shared_ptr ios_context); + + ~IOSSurfaceFactory(); + + void SetPlatformViewsController( + FlutterPlatformViewsController* platform_views_controller); + + std::unique_ptr CreateSurface( + fml::scoped_nsobject ca_layer); + + private: + FlutterPlatformViewsController* platform_views_controller_; + std::shared_ptr ios_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceFactory); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ diff --git a/shell/platform/darwin/ios/ios_surface_factory.mm b/shell/platform/darwin/ios/ios_surface_factory.mm new file mode 100644 index 0000000000000..4ed7d4c0b96a9 --- /dev/null +++ b/shell/platform/darwin/ios/ios_surface_factory.mm @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" +#import "flutter/shell/platform/darwin/ios/ios_context.h" + +namespace flutter { + +IOSSurfaceFactory::IOSSurfaceFactory(std::shared_ptr ios_context) + : ios_context_(ios_context) {} + +std::shared_ptr IOSSurfaceFactory::Create(IOSRenderingAPI rendering_api) { + std::shared_ptr ios_context = IOSContext::Create(rendering_api); + return std::make_shared(ios_context); +} + +IOSSurfaceFactory::~IOSSurfaceFactory() = default; + +void IOSSurfaceFactory::SetPlatformViewsController( + FlutterPlatformViewsController* platform_views_controller) { + platform_views_controller_ = platform_views_controller; +} + +std::unique_ptr IOSSurfaceFactory::CreateSurface( + fml::scoped_nsobject ca_layer) { + return flutter::IOSSurface::Create(ios_context_, ca_layer, platform_views_controller_); +} + +} // namespace flutter diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 8e63bbda2f132..661d5e57f5b85 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -19,6 +19,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @class FlutterViewController; @@ -41,6 +42,7 @@ class PlatformViewIOS final : public PlatformView { public: explicit PlatformViewIOS(PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, + std::shared_ptr surface_factory, flutter::TaskRunners task_runners); ~PlatformViewIOS() override; @@ -124,6 +126,7 @@ class PlatformViewIOS final : public PlatformView { std::mutex ios_surface_mutex_; std::unique_ptr ios_surface_; std::shared_ptr ios_context_; + std::shared_ptr ios_surface_factory_; PlatformMessageRouter platform_message_router_; AccessibilityBridgePtr accessibility_bridge_; fml::scoped_nsprotocol text_input_plugin_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index faf764be7cb0c..43543c1e2fcde 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" +#include #include @@ -46,9 +47,11 @@ PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, + std::shared_ptr surface_factory, flutter::TaskRunners task_runners) : PlatformView(delegate, std::move(task_runners)), ios_context_(IOSContext::Create(rendering_api)), + ios_surface_factory_(surface_factory), accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }) {} PlatformViewIOS::~PlatformViewIOS() = default; @@ -102,8 +105,9 @@ FML_DCHECK(owner_controller_.get().isViewLoaded) << "FlutterViewController's view should be loaded " "before attaching to PlatformViewIOS."; - ios_surface_ = - [static_cast(owner_controller_.get().view) createSurface:ios_context_]; + auto flutter_view = static_cast(owner_controller_.get().view); + auto ca_layer = fml::scoped_nsobject{[[flutter_view layer] retain]}; + ios_surface_ = ios_surface_factory_->CreateSurface(ca_layer); FML_DCHECK(ios_surface_ != nullptr); if (accessibility_bridge_) { From 37428dcb32e9c3d7dcfca386b8afca077627281a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 06:22:06 -0400 Subject: [PATCH 136/219] Roll Fuchsia Mac SDK from SFNhlfVb_... to _FaRRt69Z... (#21906) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1e2dec048aad2..07301989df99c 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'SFNhlfVb_3sGYAFy9FIH5Y3o3ZCd0f6NNPhI4kstCRoC' + 'version': '_FaRRt69ZlrCACiYukNB6u_zVpyECR3W1gPgS__sm-kC' } ], 'condition': 'host_os == "mac"', From 5355f270ba99d23020307d5d2906b93338e9a60e Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 10:02:03 -0400 Subject: [PATCH 137/219] Roll Dart SDK from 04cf6ade9fc4 to 80288ca68c49 (6 revisions) (#21909) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 07301989df99c..039dc138a38fb 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '04cf6ade9fc4fb960a6f43177ecc8b9a20805250', + 'dart_revision': '80288ca68c49dcd97081db9fe35bb6b8b0713268', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 8cc6ff53fe039..afb3259c64cab 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 5dc2e0ab64cbdb1da2ee2a1ab43ec2e4 +Signature: 6ecfde5ebea1b1e1d3f507a2d5f64e41 UNUSED LICENSES: @@ -10204,7 +10204,6 @@ FILE: ../../../third_party/dart/sdk/lib/io/socket.dart FILE: ../../../third_party/dart/sdk/lib/io/stdio.dart FILE: ../../../third_party/dart/sdk/lib/io/string_transformer.dart FILE: ../../../third_party/dart/sdk/lib/js/js.dart -FILE: ../../../third_party/dart/sdk/lib/math/jenkins_smi_hash.dart FILE: ../../../third_party/dart/sdk/lib/math/point.dart FILE: ../../../third_party/dart/sdk/lib/math/rectangle.dart FILE: ../../../third_party/dart/sdk/lib/mirrors/mirrors.dart From 15768e5af4971f39709b1f9996d13f841f6ad226 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 10:42:02 -0400 Subject: [PATCH 138/219] Roll Skia from f1b53836b705 to db0288d747ae (7 revisions) (#21910) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 139 +++++++++++++++++++------------ 2 files changed, 85 insertions(+), 56 deletions(-) diff --git a/DEPS b/DEPS index 039dc138a38fb..46f35937e5e95 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'f1b53836b70549fb9e0a0f0358b6dbf676d35f41', + 'skia_revision': 'db0288d747ae84e8cfb577f70953c1d56eb78110', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 9283ff1fe0aec..6f9d5a8b50d4f 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: a21a1a69f9d735a406af72e69b6ae7e7 +Signature: f6b15586236bf4f932f5c03ac999ee73 UNUSED LICENSES: @@ -435,49 +435,6 @@ FILE: ../../../third_party/skia/bench/StreamBench.cpp FILE: ../../../third_party/skia/bench/SwizzleBench.cpp FILE: ../../../third_party/skia/bench/TileImageFilterBench.cpp FILE: ../../../third_party/skia/bench/VertexColorSpaceBench.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttribute.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttribute.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttributeParser.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttributeParser.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGCircle.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGCircle.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGClipPath.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGClipPath.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGContainer.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGContainer.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGDOM.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGDOM.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGDefs.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGEllipse.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGEllipse.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGG.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGHiddenContainer.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGIDMapper.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLine.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLine.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLinearGradient.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLinearGradient.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGNode.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGNode.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPath.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPath.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPoly.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPoly.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRect.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRect.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRenderContext.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRenderContext.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGSVG.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGSVG.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGShape.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGShape.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGStop.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGStop.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGTransformableNode.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGTransformableNode.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGTypes.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGValue.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGValue.h FILE: ../../../third_party/skia/experimental/xps_to_png/xps_to_png.cs FILE: ../../../third_party/skia/fuzz/Fuzz.cpp FILE: ../../../third_party/skia/fuzz/Fuzz.h @@ -554,6 +511,49 @@ FILE: ../../../third_party/skia/modules/sksg/samples/SampleSVGPong.cpp FILE: ../../../third_party/skia/modules/skshaper/include/SkShaper.h FILE: ../../../third_party/skia/modules/skshaper/src/SkShaper_harfbuzz.cpp FILE: ../../../third_party/skia/modules/skshaper/src/SkShaper_primitive.cpp +FILE: ../../../third_party/skia/modules/svg/include/SkSVGAttribute.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGAttributeParser.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGCircle.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGClipPath.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGContainer.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGDOM.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGDefs.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGEllipse.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGG.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGHiddenContainer.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGIDMapper.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGLine.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGLinearGradient.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGNode.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGPath.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGPoly.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGRect.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGRenderContext.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGSVG.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGShape.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGStop.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGTransformableNode.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGTypes.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGValue.h +FILE: ../../../third_party/skia/modules/svg/src/SkSVGAttribute.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGAttributeParser.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGCircle.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGClipPath.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGContainer.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGDOM.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGEllipse.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGLine.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGLinearGradient.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGNode.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGPath.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGPoly.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGRect.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGRenderContext.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGSVG.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGShape.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGStop.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGTransformableNode.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGValue.cpp FILE: ../../../third_party/skia/samplecode/DecodeFile.h FILE: ../../../third_party/skia/samplecode/Sample.cpp FILE: ../../../third_party/skia/samplecode/SampleAndroidShadows.cpp @@ -3020,8 +3020,6 @@ FILE: ../../../third_party/skia/experimental/ffmpeg/SkVideoDecoder.cpp FILE: ../../../third_party/skia/experimental/ffmpeg/SkVideoDecoder.h FILE: ../../../third_party/skia/experimental/ffmpeg/SkVideoEncoder.cpp FILE: ../../../third_party/skia/experimental/ffmpeg/SkVideoEncoder.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGText.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGText.h FILE: ../../../third_party/skia/gm/backdrop.cpp FILE: ../../../third_party/skia/gm/backdrop_imagefilter_croprect.cpp FILE: ../../../third_party/skia/gm/bug9331.cpp @@ -3092,6 +3090,8 @@ FILE: ../../../third_party/skia/modules/sksg/include/SkSGRenderEffect.h FILE: ../../../third_party/skia/modules/sksg/src/SkSGNodePriv.h FILE: ../../../third_party/skia/modules/sksg/src/SkSGRenderEffect.cpp FILE: ../../../third_party/skia/modules/sksg/src/SkSGTransformPriv.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGText.h +FILE: ../../../third_party/skia/modules/svg/src/SkSVGText.cpp FILE: ../../../third_party/skia/samplecode/SampleDegenerateQuads.cpp FILE: ../../../third_party/skia/samplecode/SampleSG.cpp FILE: ../../../third_party/skia/samplecode/SampleThinAA.cpp @@ -3578,14 +3578,6 @@ FILE: ../../../third_party/skia/bin/sysopen FILE: ../../../third_party/skia/dm/DMGpuTestProcs.cpp FILE: ../../../third_party/skia/example/HelloWorld.cpp FILE: ../../../third_party/skia/example/HelloWorld.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGGradient.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGGradient.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPattern.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPattern.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRadialGradient.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRadialGradient.h -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGUse.cpp -FILE: ../../../third_party/skia/experimental/svg/model/SkSVGUse.h FILE: ../../../third_party/skia/fuzz/FuzzCanvas.cpp FILE: ../../../third_party/skia/gm/alpha_image.cpp FILE: ../../../third_party/skia/gm/bitmaptiled.cpp @@ -3674,6 +3666,14 @@ FILE: ../../../third_party/skia/modules/sksg/src/SkSGPath.cpp FILE: ../../../third_party/skia/modules/sksg/src/SkSGRect.cpp FILE: ../../../third_party/skia/modules/sksg/src/SkSGRenderNode.cpp FILE: ../../../third_party/skia/modules/sksg/src/SkSGTransform.cpp +FILE: ../../../third_party/skia/modules/svg/include/SkSVGGradient.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGPattern.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGRadialGradient.h +FILE: ../../../third_party/skia/modules/svg/include/SkSVGUse.h +FILE: ../../../third_party/skia/modules/svg/src/SkSVGGradient.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGPattern.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGRadialGradient.cpp +FILE: ../../../third_party/skia/modules/svg/src/SkSVGUse.cpp FILE: ../../../third_party/skia/samplecode/SampleCCPRGeometry.cpp FILE: ../../../third_party/skia/samplecode/SampleChineseFling.cpp FILE: ../../../third_party/skia/samplecode/SampleCowboy.cpp @@ -4065,7 +4065,35 @@ FILE: ../../../third_party/skia/experimental/skrive/src/reader/BinaryReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/JsonReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/StreamReader.cpp FILE: ../../../third_party/skia/experimental/skrive/src/reader/StreamReader.h -FILE: ../../../third_party/skia/experimental/svg/utils/SvgTool.cpp +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttribute.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGAttributeParser.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGCircle.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGClipPath.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGContainer.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGDOM.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGDefs.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGEllipse.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGG.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGGradient.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGHiddenContainer.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGIDMapper.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLine.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGLinearGradient.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGNode.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPath.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPattern.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGPoly.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRadialGradient.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRect.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGRenderContext.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGSVG.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGShape.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGStop.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGText.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGTransformableNode.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGTypes.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGUse.h +FILE: ../../../third_party/skia/experimental/svg/model/SkSVGValue.h FILE: ../../../third_party/skia/gm/3d.cpp FILE: ../../../third_party/skia/gm/bc1_transparency.cpp FILE: ../../../third_party/skia/gm/bicubic.cpp @@ -4129,6 +4157,7 @@ FILE: ../../../third_party/skia/modules/sksg/src/SkSGGeometryEffect.cpp FILE: ../../../third_party/skia/modules/skshaper/src/SkShaper_coretext.cpp FILE: ../../../third_party/skia/modules/skshaper/src/SkUnicode.h FILE: ../../../third_party/skia/modules/skshaper/src/SkUnicode_icu.cpp +FILE: ../../../third_party/skia/modules/svg/utils/SvgTool.cpp FILE: ../../../third_party/skia/samplecode/Sample3D.cpp FILE: ../../../third_party/skia/samplecode/SampleAudio.cpp FILE: ../../../third_party/skia/samplecode/SampleFitCubicToCircle.cpp From abdc8d385392e32c025184f6db1154439847c957 Mon Sep 17 00:00:00 2001 From: Mehmet Fidanboylu Date: Fri, 16 Oct 2020 08:21:04 -0700 Subject: [PATCH 139/219] Forward Error objects to uncaught exception handler if there is one. (#21806) --- shell/platform/android/BUILD.gn | 1 + .../embedding/engine/dart/DartMessenger.java | 18 +++++- .../test/io/flutter/FlutterTestSuite.java | 4 +- .../engine/dart/DartExecutorTest.java | 2 +- .../engine/dart/DartMessengerTest.java | 55 +++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 5ccfd56d91aed..91d2a368b1057 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -459,6 +459,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/PluginComponentTest.java", "test/io/flutter/embedding/engine/RenderingComponentTest.java", "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", + "test/io/flutter/embedding/engine/dart/DartMessengerTest.java", "test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java", "test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java", "test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java index 2c4726b7f8fd3..fc03bb1f3c6f6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java @@ -86,6 +86,8 @@ public void handleMessageFromDart( } catch (Exception ex) { Log.e(TAG, "Uncaught exception in binary message listener", ex); flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId); + } catch (Error err) { + handleError(err); } } else { Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message."); @@ -103,6 +105,8 @@ public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) { callback.reply(reply == null ? null : ByteBuffer.wrap(reply)); } catch (Exception ex) { Log.e(TAG, "Uncaught exception in binary message reply handler", ex); + } catch (Error err) { + handleError(err); } } } @@ -123,7 +127,19 @@ public int getPendingChannelResponseCount() { return pendingReplies.size(); } - private static class Reply implements BinaryMessenger.BinaryReply { + // Handles `Error` objects which are not supposed to be caught. + // + // We forward them to the thread's uncaught exception handler if there is one. If not, they + // are rethrown. + private static void handleError(Error err) { + Thread currentThread = Thread.currentThread(); + if (currentThread.getUncaughtExceptionHandler() == null) { + throw err; + } + currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, err); + } + + static class Reply implements BinaryMessenger.BinaryReply { @NonNull private final FlutterJNI flutterJNI; private final int replyId; private final AtomicBoolean done = new AtomicBoolean(false); diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index c96f8d5dd5770..f440f736589e1 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -16,6 +16,8 @@ import io.flutter.embedding.engine.FlutterJNITest; import io.flutter.embedding.engine.LocalizationPluginTest; import io.flutter.embedding.engine.RenderingComponentTest; +import io.flutter.embedding.engine.dart.DartExecutorTest; +import io.flutter.embedding.engine.dart.DartMessengerTest; import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest; import io.flutter.embedding.engine.loader.FlutterLoaderTest; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorViewTest; @@ -41,7 +43,6 @@ import test.io.flutter.embedding.engine.FlutterEngineTest; import test.io.flutter.embedding.engine.FlutterShellArgsTest; import test.io.flutter.embedding.engine.PluginComponentTest; -import test.io.flutter.embedding.engine.dart.DartExecutorTest; @RunWith(Suite.class) @SuiteClasses({ @@ -49,6 +50,7 @@ AndroidKeyProcessorTest.class, ApplicationInfoLoaderTest.class, DartExecutorTest.class, + DartMessengerTest.class, FlutterActivityAndFragmentDelegateTest.class, FlutterActivityTest.class, FlutterAndroidComponentTest.class, diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java index 6c8e766bd557b..51a341ce45f04 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java @@ -1,4 +1,4 @@ -package test.io.flutter.embedding.engine.dart; +package io.flutter.embedding.engine.dart; import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.assertEquals; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java new file mode 100644 index 0000000000000..1665047cad861 --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java @@ -0,0 +1,55 @@ +package io.flutter.embedding.engine.dart; + +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; + +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; +import java.nio.ByteBuffer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class DartMessengerTest { + private static class ReportingUncaughtExceptionHandler + implements Thread.UncaughtExceptionHandler { + public Throwable latestException; + + @Override + public void uncaughtException(Thread t, Throwable e) { + latestException = e; + } + } + + @Test + public void itHandlesErrors() { + // Setup test. + final FlutterJNI fakeFlutterJni = mock(FlutterJNI.class); + final Thread currentThread = Thread.currentThread(); + final Thread.UncaughtExceptionHandler savedHandler = + currentThread.getUncaughtExceptionHandler(); + final ReportingUncaughtExceptionHandler reportingHandler = + new ReportingUncaughtExceptionHandler(); + currentThread.setUncaughtExceptionHandler(reportingHandler); + + // Create object under test. + final DartMessenger messenger = new DartMessenger(fakeFlutterJni); + final BinaryMessageHandler throwingHandler = mock(BinaryMessageHandler.class); + Mockito.doThrow(AssertionError.class) + .when(throwingHandler) + .onMessage(any(ByteBuffer.class), any(DartMessenger.Reply.class)); + + messenger.setMessageHandler("test", throwingHandler); + messenger.handleMessageFromDart("test", new byte[] {}, 0); + assertNotNull(reportingHandler.latestException); + assertTrue(reportingHandler.latestException instanceof AssertionError); + currentThread.setUncaughtExceptionHandler(savedHandler); + } +} + From ed47fc92ee52a2e53d426e8587130cc1c84d4718 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 12:02:01 -0400 Subject: [PATCH 140/219] Roll Skia from db0288d747ae to 839fb228ac44 (1 revision) (#21911) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 46f35937e5e95..7fb1fe320c22d 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'db0288d747ae84e8cfb577f70953c1d56eb78110', + 'skia_revision': '839fb228ac44d101a09306a9b62a96e116cc10b1', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 6f9d5a8b50d4f..9de11cd4a3a1f 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: f6b15586236bf4f932f5c03ac999ee73 +Signature: d3af201134539fd4e7afdc554e2523e9 UNUSED LICENSES: From 4f89ff7a9c979ccab47c48524886ecee477f156f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 14:32:01 -0400 Subject: [PATCH 141/219] Roll Dart SDK from 80288ca68c49 to e655b9a3839e (1 revision) (#21915) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 7fb1fe320c22d..d21e2bd16e701 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '80288ca68c49dcd97081db9fe35bb6b8b0713268', + 'dart_revision': 'e655b9a3839e97957aa9cfffa47a85577e27481d', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index afb3259c64cab..a0e0f6902a0fe 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 6ecfde5ebea1b1e1d3f507a2d5f64e41 +Signature: e370b81d8d5a8d4a3bc357f2d24d4664 UNUSED LICENSES: From c449c8ee5035a562317ab189f7ca603e233d987f Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 16 Oct 2020 12:44:49 -0700 Subject: [PATCH 142/219] Eliminate FLUTTER_NOLINT where possible (#21904) This removes most of the remaining FLUTTER_NOLINT comments and opts these files back into linter enforcement. I've filed https://github.com/flutter/flutter/issues/68273 to require that all FLUTTER_NOLINT comments be followed by a GitHub issue URL describing the problem to be fixed. --- lib/ui/plugins/callback_cache.cc | 1 - shell/common/animator_unittests.cc | 1 - shell/common/engine_unittests.cc | 1 - shell/common/input_events_unittests.cc | 1 - shell/common/isolate_configuration.cc | 1 - shell/common/platform_view.cc | 1 - shell/common/rasterizer.cc | 1 - shell/common/shell.cc | 1 - shell/common/shell_benchmarks.cc | 1 - shell/common/shell_unittests.cc | 1 - shell/common/skia_event_tracer_impl.cc | 1 - shell/common/switches.cc | 1 - shell/common/vsync_waiter_fallback.cc | 1 - shell/gpu/gpu_surface_gl.cc | 1 - shell/gpu/gpu_surface_metal.mm | 1 - shell/gpu/gpu_surface_vulkan.cc | 1 - shell/gpu/gpu_surface_vulkan_delegate.cc | 1 - .../external_view_embedder/external_view_embedder_unittests.cc | 1 - .../android/external_view_embedder/surface_pool_unittests.cc | 1 - .../common/cpp/client_wrapper/encodable_value_unittests.cc | 1 - shell/platform/common/cpp/client_wrapper/standard_codec.cc | 1 - shell/platform/common/cpp/json_message_codec.cc | 1 - shell/platform/common/cpp/json_message_codec_unittests.cc | 1 - shell/platform/common/cpp/json_method_codec.cc | 1 - shell/platform/common/cpp/json_method_codec_unittests.cc | 1 - .../platform/darwin/common/framework/Source/FlutterChannels.mm | 1 - shell/platform/darwin/common/framework/Source/FlutterCodecs.mm | 1 - .../darwin/common/framework/Source/FlutterStandardCodec.mm | 1 - .../common/framework/Source/flutter_standard_codec_unittest.mm | 1 - .../darwin/macos/framework/Source/FlutterAppDelegate.mm | 1 - shell/platform/darwin/macos/framework/Source/FlutterEngine.mm | 1 - .../darwin/macos/framework/Source/FlutterEngineUnittests.mm | 1 - .../darwin/macos/framework/Source/FlutterExternalTextureGL.mm | 1 - .../darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm | 1 - .../darwin/macos/framework/Source/FlutterTextInputModel.mm | 1 - .../darwin/macos/framework/Source/FlutterTextInputPlugin.mm | 1 - .../darwin/macos/framework/Source/FlutterViewController.mm | 1 - shell/platform/embedder/embedder.cc | 2 -- shell/platform/embedder/tests/embedder_a11y_unittests.cc | 1 - shell/platform/embedder/tests/embedder_unittests.cc | 1 - shell/platform/embedder/tests/embedder_unittests_gl.cc | 1 - shell/platform/embedder/tests/embedder_unittests_util.cc | 1 - shell/platform/glfw/flutter_glfw.cc | 1 - shell/platform/glfw/key_event_handler.cc | 1 - shell/testing/tester_main.cc | 1 - testing/mock_canvas.cc | 1 - vulkan/vulkan_swapchain_stub.cc | 1 - 47 files changed, 48 deletions(-) diff --git a/lib/ui/plugins/callback_cache.cc b/lib/ui/plugins/callback_cache.cc index 0194eb507bcdd..218134504712f 100644 --- a/lib/ui/plugins/callback_cache.cc +++ b/lib/ui/plugins/callback_cache.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/lib/ui/plugins/callback_cache.h" diff --git a/shell/common/animator_unittests.cc b/shell/common/animator_unittests.cc index 0c28908af407b..281f204fb869a 100644 --- a/shell/common/animator_unittests.cc +++ b/shell/common/animator_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 4ce3c6d6d7aad..7405511513a9b 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/engine.h" diff --git a/shell/common/input_events_unittests.cc b/shell/common/input_events_unittests.cc index dfb40d8459f4d..f7514beabc4b5 100644 --- a/shell/common/input_events_unittests.cc +++ b/shell/common/input_events_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/shell_test.h" #include "flutter/testing/testing.h" diff --git a/shell/common/isolate_configuration.cc b/shell/common/isolate_configuration.cc index 55b939441bd0d..4d37c03464ff2 100644 --- a/shell/common/isolate_configuration.cc +++ b/shell/common/isolate_configuration.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/isolate_configuration.h" diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index ef9f48df54ad1..a933bd3b3c6bf 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/platform_view.h" diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index ab8f0d1fdad51..3de506ea4f5f4 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/rasterizer.h" diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 787d69b51653d..0f470cff37c6f 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define RAPIDJSON_HAS_STDSTRING 1 #include "flutter/shell/common/shell.h" diff --git a/shell/common/shell_benchmarks.cc b/shell/common/shell_benchmarks.cc index ebf2093ea01b5..012e8bdfd8ec2 100644 --- a/shell/common/shell_benchmarks.cc +++ b/shell/common/shell_benchmarks.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/shell.h" diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index fbf0647eb1191..e349ada81f30e 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/shell/common/skia_event_tracer_impl.cc b/shell/common/skia_event_tracer_impl.cc index c7e3392291575..053df0993c33b 100644 --- a/shell/common/skia_event_tracer_impl.cc +++ b/shell/common/skia_event_tracer_impl.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/skia_event_tracer_impl.h" diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 3f80830136cdc..553e77361c6bc 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include #include diff --git a/shell/common/vsync_waiter_fallback.cc b/shell/common/vsync_waiter_fallback.cc index ef5cb69390cfe..fc23c6180a4ff 100644 --- a/shell/common/vsync_waiter_fallback.cc +++ b/shell/common/vsync_waiter_fallback.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/vsync_waiter_fallback.h" diff --git a/shell/gpu/gpu_surface_gl.cc b/shell/gpu/gpu_surface_gl.cc index 60d02b4a0b376..a539c98570672 100644 --- a/shell/gpu/gpu_surface_gl.cc +++ b/shell/gpu/gpu_surface_gl.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/gpu/gpu_surface_gl.h" diff --git a/shell/gpu/gpu_surface_metal.mm b/shell/gpu/gpu_surface_metal.mm index c6383f99ee846..2fc8a2a02dfaf 100644 --- a/shell/gpu/gpu_surface_metal.mm +++ b/shell/gpu/gpu_surface_metal.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/gpu/gpu_surface_metal.h" diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc index a3982e32a4002..fe45ad5ecfab5 100644 --- a/shell/gpu/gpu_surface_vulkan.cc +++ b/shell/gpu/gpu_surface_vulkan.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/gpu/gpu_surface_vulkan.h" diff --git a/shell/gpu/gpu_surface_vulkan_delegate.cc b/shell/gpu/gpu_surface_vulkan_delegate.cc index fa2056f8b2cdd..8b336faec2ff5 100644 --- a/shell/gpu/gpu_surface_vulkan_delegate.cc +++ b/shell/gpu/gpu_surface_vulkan_delegate.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/gpu/gpu_surface_vulkan_delegate.h" diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index ba16586b723aa..b161a7031d51b 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" diff --git a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc index 7d393832eb1f1..c313a43eb1254 100644 --- a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc +++ b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/android/external_view_embedder/surface_pool.h" diff --git a/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc b/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc index 45a62183127de..1f31c47b67c84 100644 --- a/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc +++ b/shell/platform/common/cpp/client_wrapper/encodable_value_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/encodable_value.h" diff --git a/shell/platform/common/cpp/client_wrapper/standard_codec.cc b/shell/platform/common/cpp/client_wrapper/standard_codec.cc index db2f2422e519d..3e86879b4775a 100644 --- a/shell/platform/common/cpp/client_wrapper/standard_codec.cc +++ b/shell/platform/common/cpp/client_wrapper/standard_codec.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT // This file contains what would normally be standard_codec_serializer.cc, // standard_message_codec.cc, and standard_method_codec.cc. They are grouped diff --git a/shell/platform/common/cpp/json_message_codec.cc b/shell/platform/common/cpp/json_message_codec.cc index 6d89c6acfeb41..d2a43bd806ded 100644 --- a/shell/platform/common/cpp/json_message_codec.cc +++ b/shell/platform/common/cpp/json_message_codec.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/json_message_codec.h" diff --git a/shell/platform/common/cpp/json_message_codec_unittests.cc b/shell/platform/common/cpp/json_message_codec_unittests.cc index 74dca356a56c7..3354f30a728df 100644 --- a/shell/platform/common/cpp/json_message_codec_unittests.cc +++ b/shell/platform/common/cpp/json_message_codec_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/json_message_codec.h" diff --git a/shell/platform/common/cpp/json_method_codec.cc b/shell/platform/common/cpp/json_method_codec.cc index f026061ad235f..f7e4428844eba 100644 --- a/shell/platform/common/cpp/json_method_codec.cc +++ b/shell/platform/common/cpp/json_method_codec.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/json_method_codec.h" diff --git a/shell/platform/common/cpp/json_method_codec_unittests.cc b/shell/platform/common/cpp/json_method_codec_unittests.cc index da1046425702c..f9d2ec882fc45 100644 --- a/shell/platform/common/cpp/json_method_codec_unittests.cc +++ b/shell/platform/common/cpp/json_method_codec_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/common/cpp/json_method_codec.h" diff --git a/shell/platform/darwin/common/framework/Source/FlutterChannels.mm b/shell/platform/darwin/common/framework/Source/FlutterChannels.mm index d225a2ee8d2b0..f5527a438ed9e 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterChannels.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterChannels.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" diff --git a/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm b/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm index 3f5379165cf00..a73ede961e1ac 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterCodecs.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" diff --git a/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm b/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm index a76ec3672e787..984dfff8435b4 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/common/framework/Source/FlutterStandardCodec_Internal.h" diff --git a/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm b/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm index 5095dc273efcc..91ffd60935bc9 100644 --- a/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm +++ b/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm index b251a459beac0..0442fdd70232d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index b5356181e3bce..f6a0027dc3d0f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm index 2637db3a85b0c..1ad6c564e7daf 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineUnittests.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm b/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm index 45d1f624429fa..0314280e9f12d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm index d808d4cc644d4..e73331db17140 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm index 4f576d251f0f7..5208925ec7340 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 08347d7af3ee5..46af8dd3953b1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index e30dbc7d9ae3f..cc5eaba49bf39 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 06b45ac5372e1..9248eb7710b30 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1,8 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER #define RAPIDJSON_HAS_STDSTRING 1 diff --git a/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/shell/platform/embedder/tests/embedder_a11y_unittests.cc index 568505b9ff041..b1d27f8a071a1 100644 --- a/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT // Allow access to fml::MessageLoop::GetCurrent() in order to flush platform // thread tasks. diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 01309bb7366f3..538ab9ae15dad 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index 5c6b160d601d5..5bb104fae97cf 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/shell/platform/embedder/tests/embedder_unittests_util.cc b/shell/platform/embedder/tests/embedder_unittests_util.cc index 78bdae8441a55..5058e2c30815e 100644 --- a/shell/platform/embedder/tests/embedder_unittests_util.cc +++ b/shell/platform/embedder/tests/embedder_unittests_util.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 703048a4626a9..ee26ea2452f4a 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/glfw/public/flutter_glfw.h" diff --git a/shell/platform/glfw/key_event_handler.cc b/shell/platform/glfw/key_event_handler.cc index cdc5deb2bbee1..bdd121091f54e 100644 --- a/shell/platform/glfw/key_event_handler.cc +++ b/shell/platform/glfw/key_event_handler.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/platform/glfw/key_event_handler.h" diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 464b51c147fdb..8c970d59e22b9 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc index 8b2ba26601cda..76da0a5eabed2 100644 --- a/testing/mock_canvas.cc +++ b/testing/mock_canvas.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/testing/mock_canvas.h" diff --git a/vulkan/vulkan_swapchain_stub.cc b/vulkan/vulkan_swapchain_stub.cc index 457c6649f1ec2..54e65e12196c1 100644 --- a/vulkan/vulkan_swapchain_stub.cc +++ b/vulkan/vulkan_swapchain_stub.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "vulkan_swapchain.h" From d03b759d049edf0f97fd16a803c431856cf5fbae Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 16:32:02 -0400 Subject: [PATCH 143/219] Roll Skia from 839fb228ac44 to 418eda2c599a (9 revisions) (#21917) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index d21e2bd16e701..eb24b13ff00e2 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '839fb228ac44d101a09306a9b62a96e116cc10b1', + 'skia_revision': '418eda2c599ab8fec96c6fed749fc117935e0a88', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 9de11cd4a3a1f..15657f563dfcf 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: d3af201134539fd4e7afdc554e2523e9 +Signature: 257d771e71170fd80a1bff5d5902ada4 UNUSED LICENSES: From eba7a1c90d37f8a23d20a5540821b1cc2215ac90 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 16:37:43 -0400 Subject: [PATCH 144/219] Roll Dart SDK from e655b9a3839e to b58cfe5ab24e (1 revision) (#21919) https://dart.googlesource.com/sdk.git/+log/e655b9a3839e..b58cfe5ab24e 2020-10-16 dart-luci-ci-builder@dart-ci.iam.gserviceaccount.com Version 2.11.0-230.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter-engine Please CC dart-vm-team@google.com,asiva@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/master/autoroll/README.md --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index eb24b13ff00e2..5e8f98c29e179 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': 'e655b9a3839e97957aa9cfffa47a85577e27481d', + 'dart_revision': 'b58cfe5ab24e7756d672c0257ed30888edfd3c27', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index a0e0f6902a0fe..37c06213fc85f 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: e370b81d8d5a8d4a3bc357f2d24d4664 +Signature: f220de2417f1bc266ae25ece48bf4216 UNUSED LICENSES: From 512154980616ff62f2e66e73cbbec2a7dc4a313f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 17:02:02 -0400 Subject: [PATCH 145/219] Roll Dart SDK from e655b9a3839e to b58cfe5ab24e (1 revision) (#21920) From 977537b0310faaecfe557c1ed9459dd4ec44f2af Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 17:52:02 -0400 Subject: [PATCH 146/219] Roll Skia from 418eda2c599a to f9c7b2803461 (3 revisions) (#21923) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 5e8f98c29e179..24e82382ecf77 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '418eda2c599ab8fec96c6fed749fc117935e0a88', + 'skia_revision': 'f9c7b2803461b1ff4010a96d79e742849c680068', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 15657f563dfcf..672ac83b1d590 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 257d771e71170fd80a1bff5d5902ada4 +Signature: 0303b9dedbc8528f215b9b008f6166f8 UNUSED LICENSES: @@ -4111,6 +4111,7 @@ FILE: ../../../third_party/skia/include/core/SkM44.h FILE: ../../../third_party/skia/include/effects/SkStrokeAndFillPathEffect.h FILE: ../../../third_party/skia/include/gpu/GrDirectContext.h FILE: ../../../third_party/skia/include/private/SkOpts_spi.h +FILE: ../../../third_party/skia/include/private/SkTPin.h FILE: ../../../third_party/skia/modules/audioplayer/SkAudioPlayer.cpp FILE: ../../../third_party/skia/modules/audioplayer/SkAudioPlayer.h FILE: ../../../third_party/skia/modules/audioplayer/SkAudioPlayer_mac.mm From 5bd7260a1e778ba60b342f780049aeabddd45a7c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 16 Oct 2020 14:53:26 -0700 Subject: [PATCH 147/219] Enable loading snapshots with sound null safety enabled. (#21820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snapshots compiled with sound null-safety enabled require changes to the way in which isolates are launched. Specifically, the `Dart_IsolateFlags::null_safety` field needs to be known upfront. The value of this field can only be determined once the kernel snapshot is available. This poses a problem in the engine because the engine used to launch the isolate at shell initialization and only need the kernel mappings later at isolate launch (when transitioning the root isolate to the `DartIsolate::Phase::Running` phase). This patch delays launch of the isolate on the UI task runner till a kernel mapping is available. The side effects of this delay (callers no longer having access to the non-running isolate handle) have been addressed in this patch. The DartIsolate API has also been amended to hide the method that could return a non-running isolate to the caller. Instead, it has been replaced with a method that requires a valid isolate configuration that returns a running root isolate. The isolate will be launched by asking the isolate configuration for its null-safety characteristics. A side effect of enabling null-safety is that Dart APIs that work with legacy types will now terminate the process if used with an isolate that has sound null-safety enabled. These APIs may no longer be used in the engine. This primarily affects the Dart Convertors in Tonic that convert certain C++ objects into the Dart counterparts. All known Dart Converters have been updated to convert C++ objects to non-nullable Dart types inferred using type traits of the corresponding C++ object. The few spots in the engine that used the old Dart APIs directly have been manually updated. To ensure that no usage of the legacy APIs remain in the engine (as these would cause runtime process terminations), the legacy APIs were prefixed with the `DART_LEGACY_API` macro and the macro defined to `[[deprecated]]` in all engine translation units. While the engine now primarily works with non-nullable Dart types, callers can still use `Dart_TypeToNonNullableType` to acquire nullable types for use directly or with Tonic. One use case that is not addressed with the Tonic Dart Convertors is the creation of non-nullable lists of nullable types. This hasn’t come up so far in the engine. A minor related change is reworking tonic to define a single library target. This allows the various tonic subsystems to depend on one another. Primarily, this is used to make the Dart convertors use the logging utilities. This now allows errors to be more descriptive as the presence of error handles is caught (and logged) earlier. Fixes https://github.com/flutter/flutter/issues/59879 --- common/config.gni | 1 + common/settings.h | 8 +- fml/BUILD.gn | 2 +- fml/dart/dart_converter.h | 6 +- lib/io/dart_io.cc | 3 +- lib/ui/dart_runtime_hooks.cc | 20 +- lib/ui/hooks.dart | 18 +- lib/ui/text/paragraph.cc | 22 +-- lib/ui/window/platform_configuration.h | 14 -- runtime/BUILD.gn | 3 + runtime/dart_isolate.cc | 181 ++++++++++++++---- runtime/dart_isolate.h | 79 +++++--- runtime/dart_isolate_unittests.cc | 78 ++++---- runtime/dart_lifecycle_unittests.cc | 77 +++----- runtime/dart_snapshot.cc | 14 ++ runtime/dart_snapshot.h | 3 + runtime/fixtures/runtime_test.dart | 49 ++++- .../isolate_configuration.cc | 93 +++++++-- .../isolate_configuration.h | 21 +- runtime/runtime_controller.cc | 156 +++++++-------- runtime/runtime_controller.h | 73 ++++--- runtime/type_conversions_unittests.cc | 175 +++++++++++++++++ shell/common/BUILD.gn | 2 - shell/common/engine.cc | 93 ++------- shell/common/engine.h | 11 +- shell/common/fixtures/shell_test.dart | 6 +- shell/common/run_configuration.h | 2 +- shell/platform/embedder/fixtures/main.dart | 100 ++++++++-- shell/platform/fuchsia/flutter/component.cc | 5 +- shell/platform/fuchsia/flutter/engine.cc | 7 +- shell/platform/fuchsia/flutter/engine.h | 4 +- testing/dart_isolate_runner.cc | 126 ++++++------ testing/fixture_test.cc | 13 +- testing/testing.gni | 1 + third_party/tonic/BUILD.gn | 60 +++++- third_party/tonic/common/BUILD.gn | 16 -- third_party/tonic/converter/BUILD.gn | 18 -- third_party/tonic/converter/dart_converter.h | 84 +++++++- third_party/tonic/dart_class_provider.cc | 2 +- third_party/tonic/file_loader/BUILD.gn | 30 --- third_party/tonic/file_loader/file_loader.cc | 6 +- .../tonic/file_loader/file_loader_fuchsia.cc | 4 +- .../tonic/file_loader/file_loader_posix.cc | 4 +- .../tonic/file_loader/file_loader_win.cc | 4 +- .../tonic/filesystem/filesystem/BUILD.gn | 35 ---- .../tonic/filesystem/filesystem/file.cc | 6 +- .../tonic/filesystem/filesystem/file.h | 4 +- .../tonic/filesystem/filesystem/path_posix.cc | 4 +- .../tonic/filesystem/filesystem/path_win.cc | 2 +- third_party/tonic/logging/BUILD.gn | 23 --- third_party/tonic/parsers/BUILD.gn | 16 -- third_party/tonic/scopes/BUILD.gn | 19 -- third_party/tonic/typed_data/BUILD.gn | 31 --- tools/font-subset/BUILD.gn | 2 +- 54 files changed, 1096 insertions(+), 740 deletions(-) rename {shell/common => runtime}/isolate_configuration.cc (70%) rename {shell/common => runtime}/isolate_configuration.h (90%) create mode 100644 runtime/type_conversions_unittests.cc delete mode 100644 third_party/tonic/common/BUILD.gn delete mode 100644 third_party/tonic/converter/BUILD.gn delete mode 100644 third_party/tonic/file_loader/BUILD.gn delete mode 100644 third_party/tonic/filesystem/filesystem/BUILD.gn delete mode 100644 third_party/tonic/logging/BUILD.gn delete mode 100644 third_party/tonic/parsers/BUILD.gn delete mode 100644 third_party/tonic/scopes/BUILD.gn delete mode 100644 third_party/tonic/typed_data/BUILD.gn diff --git a/common/config.gni b/common/config.gni index f7b122a3f480e..3173a5e15b069 100644 --- a/common/config.gni +++ b/common/config.gni @@ -28,6 +28,7 @@ feature_defines_list = [ "FLUTTER_RUNTIME_MODE_PROFILE=2", "FLUTTER_RUNTIME_MODE_RELEASE=3", "FLUTTER_RUNTIME_MODE_JIT_RELEASE=4", + "DART_LEGACY_API=[[deprecated]]", ] if (flutter_runtime_mode == "debug") { diff --git a/common/settings.h b/common/settings.h index d85506b946e25..17408322eedb3 100644 --- a/common/settings.h +++ b/common/settings.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -50,12 +51,11 @@ using UnhandledExceptionCallback = std::function; -// TODO(chinmaygarde): Deprecate all the "path" struct members in favor of the +// TODO(26783): Deprecate all the "path" struct members in favor of the // callback that generates the mapping from these paths. -// https://github.com/flutter/flutter/issues/26783 using MappingCallback = std::function(void)>; -using MappingsCallback = - std::function>(void)>; +using Mappings = std::vector>; +using MappingsCallback = std::function; using FrameRasterizedCallback = std::function; diff --git a/fml/BUILD.gn b/fml/BUILD.gn index 358fbee5483ff..198d61c52c610 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -139,7 +139,7 @@ source_set("fml") { "platform/darwin/string_range_sanitization.mm", ] - libs += [ "Foundation.framework" ] + frameworks = [ "Foundation.framework" ] } if (is_android) { diff --git a/fml/dart/dart_converter.h b/fml/dart/dart_converter.h index 159ec44e25c31..ccdd2422abed6 100644 --- a/fml/dart/dart_converter.h +++ b/fml/dart/dart_converter.h @@ -23,7 +23,11 @@ struct DartConverter { return Dart_Null(); } - auto dart_list_handle = Dart_NewListOf(Dart_CoreType_Int, val->GetSize()); + auto dart_list_handle = Dart_NewListOfTypeFilled( + ToDartTypeHandle(), // type + CreateZeroInitializedDartObject(), // sentinel + val->GetSize() // size + ); if (Dart_IsError(dart_list_handle)) { FML_LOG(ERROR) << "Error while attempting to allocate a list: " diff --git a/lib/io/dart_io.cc b/lib/io/dart_io.cc index 21b5952b15233..cb255fbedd6e8 100644 --- a/lib/io/dart_io.cc +++ b/lib/io/dart_io.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/lib/io/dart_io.h" @@ -23,7 +24,7 @@ void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains, FML_CHECK(!LogIfError(result)); Dart_Handle embedder_config_type = - Dart_GetType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr); + Dart_GetNonNullableType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr); FML_CHECK(!LogIfError(embedder_config_type)); Dart_Handle allow_insecure_connections_result = Dart_SetField( diff --git a/lib/ui/dart_runtime_hooks.cc b/lib/ui/dart_runtime_hooks.cc index 3512f9b446cb8..6cfa20a5333d3 100644 --- a/lib/ui/dart_runtime_hooks.cc +++ b/lib/ui/dart_runtime_hooks.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/lib/ui/dart_runtime_hooks.h" @@ -62,17 +63,19 @@ void DartRuntimeHooks::RegisterNatives(tonic::DartLibraryNatives* natives) { static void PropagateIfError(Dart_Handle result) { if (Dart_IsError(result)) { + FML_LOG(ERROR) << "Dart Error: " << ::Dart_GetError(result); Dart_PropagateError(result); } } -static Dart_Handle GetFunction(Dart_Handle builtin_library, const char* name) { +static Dart_Handle InvokeFunction(Dart_Handle builtin_library, + const char* name) { Dart_Handle getter_name = ToDart(name); return Dart_Invoke(builtin_library, getter_name, 0, nullptr); } static void InitDartInternal(Dart_Handle builtin_library, bool is_ui_isolate) { - Dart_Handle print = GetFunction(builtin_library, "_getPrintClosure"); + Dart_Handle print = InvokeFunction(builtin_library, "_getPrintClosure"); Dart_Handle internal_library = Dart_LookupLibrary(ToDart("dart:_internal")); @@ -112,7 +115,7 @@ static void InitDartAsync(Dart_Handle builtin_library, bool is_ui_isolate) { Dart_Handle schedule_microtask; if (is_ui_isolate) { schedule_microtask = - GetFunction(builtin_library, "_getScheduleMicrotaskClosure"); + InvokeFunction(builtin_library, "_getScheduleMicrotaskClosure"); } else { Dart_Handle isolate_lib = Dart_LookupLibrary(ToDart("dart:isolate")); Dart_Handle method_name = @@ -130,21 +133,24 @@ static void InitDartIO(Dart_Handle builtin_library, const std::string& script_uri) { Dart_Handle io_lib = Dart_LookupLibrary(ToDart("dart:io")); Dart_Handle platform_type = - Dart_GetType(io_lib, ToDart("_Platform"), 0, nullptr); + Dart_GetNonNullableType(io_lib, ToDart("_Platform"), 0, nullptr); if (!script_uri.empty()) { Dart_Handle result = Dart_SetField(platform_type, ToDart("_nativeScript"), ToDart(script_uri)); PropagateIfError(result); } - Dart_Handle locale_closure = - GetFunction(builtin_library, "_getLocaleClosure"); + // typedef _LocaleClosure = String Function(); + Dart_Handle /* _LocaleClosure? */ locale_closure = + InvokeFunction(builtin_library, "_getLocaleClosure"); + PropagateIfError(locale_closure); + // static String Function()? _localeClosure; Dart_Handle result = Dart_SetField(platform_type, ToDart("_localeClosure"), locale_closure); PropagateIfError(result); // Register dart:io service extensions used for network profiling. Dart_Handle network_profiling_type = - Dart_GetType(io_lib, ToDart("_NetworkProfiling"), 0, nullptr); + Dart_GetNonNullableType(io_lib, ToDart("_NetworkProfiling"), 0, nullptr); PropagateIfError(network_profiling_type); result = Dart_Invoke(network_profiling_type, ToDart("_registerServiceExtension"), 0, nullptr); diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index a911d79f63a8d..f3e4d1ee5ec81 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -53,15 +53,15 @@ void _updateWindowMetrics( _invoke(window.onMetricsChanged, window._onMetricsChangedZone); } -typedef _LocaleClosure = String? Function(); - -String? _localeClosure() { +String _localeClosure() { if (window.locale == null) { - return null; + return ''; } return window.locale.toString(); } +typedef _LocaleClosure = String Function(); + @pragma('vm:entry-point') // ignore: unused_element _LocaleClosure? _getLocaleClosure() => _localeClosure; @@ -210,9 +210,7 @@ void _drawFrame() { } // ignore: always_declare_return_types, prefer_generic_function_type_aliases -typedef _UnaryFunction(Null args); -// ignore: always_declare_return_types, prefer_generic_function_type_aliases -typedef _BinaryFunction(Null args, Null message); +typedef _ListStringArgFunction(List args); @pragma('vm:entry-point') // ignore: unused_element @@ -221,11 +219,7 @@ void _runMainZoned(Function startMainIsolateFunction, List args) { startMainIsolateFunction((){ runZonedGuarded(() { - if (userMainFunction is _BinaryFunction) { - // This seems to be undocumented but supported by the command line VM. - // Let's do the same in case old entry-points are ported to Flutter. - (userMainFunction as dynamic)(args, ''); - } else if (userMainFunction is _UnaryFunction) { + if (userMainFunction is _ListStringArgFunction) { (userMainFunction as dynamic)(args); } else { userMainFunction(); diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 31897c82d9b3b..f8beda3e94899 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/lib/ui/text/paragraph.h" @@ -132,20 +133,19 @@ tonic::Float32List Paragraph::getRectsForPlaceholders() { } Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) { - Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2); txt::Paragraph::PositionWithAffinity pos = m_paragraph->GetGlyphPositionAtCoordinate(dx, dy); - Dart_ListSetAt(result, 0, ToDart(pos.position)); - Dart_ListSetAt(result, 1, ToDart(static_cast(pos.affinity))); - return result; + std::vector result = { + pos.position, // size_t already + static_cast(pos.affinity) // affinity (enum) + }; + return tonic::DartConverter::ToDart(result); } Dart_Handle Paragraph::getWordBoundary(unsigned offset) { txt::Paragraph::Range point = m_paragraph->GetWordBoundary(offset); - Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2); - Dart_ListSetAt(result, 0, ToDart(point.start)); - Dart_ListSetAt(result, 1, ToDart(point.end)); - return result; + std::vector result = {point.start, point.end}; + return tonic::DartConverter::ToDart(result); } Dart_Handle Paragraph::getLineBoundary(unsigned offset) { @@ -159,10 +159,8 @@ Dart_Handle Paragraph::getLineBoundary(unsigned offset) { break; } } - Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2); - Dart_ListSetAt(result, 0, ToDart(line_start)); - Dart_ListSetAt(result, 1, ToDart(line_end)); - return result; + std::vector result = {line_start, line_end}; + return tonic::DartConverter::ToDart(result); } tonic::Float64List Paragraph::computeLineMetrics() { diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 4652c7c5dcdbb..01089dac9c202 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -19,20 +19,6 @@ #include "flutter/lib/ui/window/window.h" #include "third_party/tonic/dart_persistent_value.h" -namespace tonic { -class DartLibraryNatives; - -// So tonic::ToDart> returns List instead of -// List. -template <> -struct DartListFactory { - static Dart_Handle NewList(intptr_t length) { - return Dart_NewListOf(Dart_CoreType_Int, length); - } -}; - -} // namespace tonic - namespace flutter { class FontCollection; class PlatformMessage; diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index d584693fdcfe7..9788794f7da83 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -53,6 +53,8 @@ source_set("runtime") { "dart_vm_lifecycle.h", "embedder_resources.cc", "embedder_resources.h", + "isolate_configuration.cc", + "isolate_configuration.h", "platform_data.cc", "platform_data.h", "ptrace_check.h", @@ -112,6 +114,7 @@ if (enable_unittests) { "dart_lifecycle_unittests.cc", "dart_service_isolate_unittests.cc", "dart_vm_unittests.cc", + "type_conversions_unittests.cc", ] public_configs = [ "//flutter:export_dynamic_symbols" ] diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 919a9a532c17a..80583aab1fd6b 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/runtime/dart_isolate.h" @@ -17,6 +18,7 @@ #include "flutter/runtime/dart_service_isolate.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/runtime/isolate_configuration.h" #include "third_party/dart/runtime/include/dart_api.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "third_party/tonic/converter/dart_converter.h" @@ -52,6 +54,126 @@ class DartErrorString { } // anonymous namespace +DartIsolate::Flags::Flags() : Flags(nullptr) {} + +DartIsolate::Flags::Flags(const Dart_IsolateFlags* flags) { + if (flags) { + flags_ = *flags; + } else { + ::Dart_IsolateFlagsInitialize(&flags_); + } +} + +DartIsolate::Flags::~Flags() = default; + +void DartIsolate::Flags::SetNullSafetyEnabled(bool enabled) { + flags_.null_safety = enabled; +} + +Dart_IsolateFlags DartIsolate::Flags::Get() const { + return flags_; +} + +std::weak_ptr DartIsolate::CreateRunningRootIsolate( + const Settings& settings, + fml::RefPtr isolate_snapshot, + TaskRunners task_runners, + std::unique_ptr platform_configuration, + fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, + fml::WeakPtr io_manager, + fml::RefPtr skia_unref_queue, + fml::WeakPtr image_decoder, + std::string advisory_script_uri, + std::string advisory_script_entrypoint, + Flags isolate_flags, + const fml::closure& isolate_create_callback, + const fml::closure& isolate_shutdown_callback, + std::optional dart_entrypoint, + std::optional dart_entrypoint_library, + std::unique_ptr isolate_configration) { + if (!isolate_snapshot) { + FML_LOG(ERROR) << "Invalid isolate snapshot."; + return {}; + } + + if (!isolate_configration) { + FML_LOG(ERROR) << "Invalid isolate configuration."; + return {}; + } + + isolate_flags.SetNullSafetyEnabled( + isolate_configration->IsNullSafetyEnabled(*isolate_snapshot)); + + auto isolate = CreateRootIsolate(settings, // + isolate_snapshot, // + task_runners, // + std::move(platform_configuration), // + snapshot_delegate, // + hint_freed_delegate, // + io_manager, // + skia_unref_queue, // + image_decoder, // + advisory_script_uri, // + advisory_script_entrypoint, // + isolate_flags, // + isolate_create_callback, // + isolate_shutdown_callback // + ) + .lock(); + + if (!isolate) { + FML_LOG(ERROR) << "Could not create root isolate."; + return {}; + } + + fml::ScopedCleanupClosure shutdown_on_error([isolate]() { + if (!isolate->Shutdown()) { + FML_DLOG(ERROR) << "Could not shutdown transient isolate."; + } + }); + + if (isolate->GetPhase() != DartIsolate::Phase::LibrariesSetup) { + FML_LOG(ERROR) << "Root isolate was created in an incorrect phase."; + return {}; + } + + if (!isolate_configration->PrepareIsolate(*isolate.get())) { + FML_LOG(ERROR) << "Could not prepare isolate."; + return {}; + } + + if (isolate->GetPhase() != DartIsolate::Phase::Ready) { + FML_LOG(ERROR) << "Root isolate not in the ready phase for Dart entrypoint " + "invocation."; + return {}; + } + + if (!isolate->RunFromLibrary(dart_entrypoint_library, // + dart_entrypoint, // + settings.dart_entrypoint_args, // + settings.root_isolate_create_callback // + )) { + FML_LOG(ERROR) << "Could not run the run main Dart entrypoint."; + return {}; + } + + if (settings.root_isolate_create_callback) { + // Isolate callbacks always occur in isolate scope. + tonic::DartState::Scope scope(isolate.get()); + settings.root_isolate_create_callback(); + } + + if (settings.root_isolate_shutdown_callback) { + isolate->AddIsolateShutdownCallback( + settings.root_isolate_shutdown_callback); + } + + shutdown_on_error.Release(); + + return isolate; +} + std::weak_ptr DartIsolate::CreateRootIsolate( const Settings& settings, fml::RefPtr isolate_snapshot, @@ -64,7 +186,7 @@ std::weak_ptr DartIsolate::CreateRootIsolate( fml::WeakPtr image_decoder, std::string advisory_script_uri, std::string advisory_script_entrypoint, - Dart_IsolateFlags* flags, + Flags flags, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback) { TRACE_EVENT0("flutter", "DartIsolate::CreateRootIsolate"); @@ -98,9 +220,10 @@ std::weak_ptr DartIsolate::CreateRootIsolate( ))); DartErrorString error; - Dart_Isolate vm_isolate = - CreateDartIsolateGroup(std::move(isolate_group_data), - std::move(isolate_data), flags, error.error()); + auto isolate_flags = flags.Get(); + Dart_Isolate vm_isolate = CreateDartIsolateGroup( + std::move(isolate_group_data), std::move(isolate_data), &isolate_flags, + error.error()); if (error) { FML_LOG(ERROR) << "CreateDartIsolateGroup failed: " << error.str(); @@ -493,40 +616,10 @@ bool DartIsolate::MarkIsolateRunnable() { return true; } -/// @note Procedure doesn't copy all closures. -[[nodiscard]] bool DartIsolate::Run(const std::string& entrypoint_name, - const std::vector& args, - const fml::closure& on_run) { - TRACE_EVENT0("flutter", "DartIsolate::Run"); - if (phase_ != Phase::Ready) { - return false; - } - - tonic::DartState::Scope scope(this); - - auto user_entrypoint_function = - Dart_GetField(Dart_RootLibrary(), tonic::ToDart(entrypoint_name.c_str())); - - auto entrypoint_args = tonic::ToDart(args); - - if (!InvokeMainEntrypoint(user_entrypoint_function, entrypoint_args)) { - return false; - } - - phase_ = Phase::Running; - - if (on_run) { - on_run(); - } - return true; -} - -/// @note Procedure doesn't copy all closures. -[[nodiscard]] bool DartIsolate::RunFromLibrary( - const std::string& library_name, - const std::string& entrypoint_name, - const std::vector& args, - const fml::closure& on_run) { +bool DartIsolate::RunFromLibrary(std::optional library_name, + std::optional entrypoint, + const std::vector& args, + const fml::closure& on_run) { TRACE_EVENT0("flutter", "DartIsolate::RunFromLibrary"); if (phase_ != Phase::Ready) { return false; @@ -534,9 +627,15 @@ bool DartIsolate::MarkIsolateRunnable() { tonic::DartState::Scope scope(this); + auto library_handle = + library_name.has_value() && !library_name.value().empty() + ? ::Dart_LookupLibrary(tonic::ToDart(library_name.value().c_str())) + : ::Dart_RootLibrary(); + auto entrypoint_handle = entrypoint.has_value() && !entrypoint.value().empty() + ? tonic::ToDart(entrypoint.value().c_str()) + : tonic::ToDart("main"); auto user_entrypoint_function = - Dart_GetField(Dart_LookupLibrary(tonic::ToDart(library_name.c_str())), - tonic::ToDart(entrypoint_name.c_str())); + ::Dart_GetField(library_handle, entrypoint_handle); auto entrypoint_args = tonic::ToDart(args); @@ -613,7 +712,7 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( {}, // Image Decoder DART_VM_SERVICE_ISOLATE_NAME, // script uri DART_VM_SERVICE_ISOLATE_NAME, // script entrypoint - flags, // flags + DartIsolate::Flags{flags}, // flags nullptr, // isolate create callback nullptr // isolate shutdown callback ); diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index d1f741518ec21..b7ef2be67e3fb 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -6,6 +6,7 @@ #define FLUTTER_RUNTIME_DART_ISOLATE_H_ #include +#include #include #include @@ -26,6 +27,7 @@ namespace flutter { class DartVM; class DartIsolateGroupData; +class IsolateConfiguration; //------------------------------------------------------------------------------ /// @brief Represents an instance of a live isolate. An isolate is a @@ -59,6 +61,22 @@ class DartIsolateGroupData; /// class DartIsolate : public UIDartState { public: + class Flags { + public: + Flags(); + + explicit Flags(const Dart_IsolateFlags* flags); + + ~Flags(); + + void SetNullSafetyEnabled(bool enabled); + + Dart_IsolateFlags Get() const; + + private: + Dart_IsolateFlags flags_; + }; + //---------------------------------------------------------------------------- /// @brief The engine represents all dart isolates as being in one of the /// known phases. By invoking various methods on the Dart isolate, @@ -174,14 +192,19 @@ class DartIsolate : public UIDartState { /// the root isolate. The isolate is /// already in the running state at /// this point and an isolate scope is - /// current. + /// current. This callback is made for + /// all isolate launches (including + /// the children of the root isolate). /// @param[in] isolate_shutdown_callback The isolate shutdown callback. /// This will be called before the /// isolate is about to transition /// into the Shutdown phase. The /// isolate is still running at this /// point and an isolate scope is - /// current. + /// current. This callback is made + /// for all isolate shutdowns + /// (including the children of the + /// root isolate). /// /// @return A weak pointer to the root Dart isolate. The caller must /// ensure that the isolate is not referenced for long periods of @@ -189,7 +212,7 @@ class DartIsolate : public UIDartState { /// terminates itself. The caller may also only use the isolate on /// the thread on which the isolate was created. /// - static std::weak_ptr CreateRootIsolate( + static std::weak_ptr CreateRunningRootIsolate( const Settings& settings, fml::RefPtr isolate_snapshot, TaskRunners task_runners, @@ -201,9 +224,12 @@ class DartIsolate : public UIDartState { fml::WeakPtr image_decoder, std::string advisory_script_uri, std::string advisory_script_entrypoint, - Dart_IsolateFlags* flags, + Flags flags, const fml::closure& isolate_create_callback, - const fml::closure& isolate_shutdown_callback); + const fml::closure& isolate_shutdown_callback, + std::optional dart_entrypoint, + std::optional dart_entrypoint_library, + std::unique_ptr isolate_configration); // |UIDartState| ~DartIsolate() override; @@ -306,26 +332,6 @@ class DartIsolate : public UIDartState { [[nodiscard]] bool PrepareForRunningFromKernels( std::vector> kernels); - //---------------------------------------------------------------------------- - /// @brief Transition the root isolate to the `Phase::Running` phase and - /// invoke the main entrypoint (the "main" method) in the root - /// library. The isolate must already be in the `Phase::Ready` - /// phase. - /// - /// @param[in] entrypoint The entrypoint in the root library. - /// @param[in] args A list of string arguments to the entrypoint. - /// @param[in] on_run A callback to run in isolate scope after the main - /// entrypoint has been invoked. There is no isolate - /// scope current on the thread once this method - /// returns. - /// - /// @return If the isolate successfully transitioned to the running phase - /// and the main entrypoint was invoked. - /// - [[nodiscard]] bool Run(const std::string& entrypoint, - const std::vector& args, - const fml::closure& on_run = nullptr); - //---------------------------------------------------------------------------- /// @brief Transition the root isolate to the `Phase::Running` phase and /// invoke the main entrypoint (the "main" method) in the @@ -344,11 +350,12 @@ class DartIsolate : public UIDartState { /// @return If the isolate successfully transitioned to the running phase /// and the main entrypoint was invoked. /// - [[nodiscard]] bool RunFromLibrary(const std::string& library_name, - const std::string& entrypoint, + [[nodiscard]] bool RunFromLibrary(std::optional library_name, + std::optional entrypoint, const std::vector& args, const fml::closure& on_run = nullptr); + public: //---------------------------------------------------------------------------- /// @brief Transition the isolate to the `Phase::Shutdown` phase. The /// only thing left to do is to collect the isolate. @@ -384,6 +391,7 @@ class DartIsolate : public UIDartState { fml::RefPtr GetMessageHandlingTaskRunner() const; private: + friend class IsolateConfiguration; class AutoFireClosure { public: AutoFireClosure(const fml::closure& closure); @@ -403,6 +411,22 @@ class DartIsolate : public UIDartState { const bool may_insecurely_connect_to_all_domains_; std::string domain_network_policy_; + static std::weak_ptr CreateRootIsolate( + const Settings& settings, + fml::RefPtr isolate_snapshot, + TaskRunners task_runners, + std::unique_ptr platform_configuration, + fml::WeakPtr snapshot_delegate, + fml::WeakPtr hint_freed_delegate, + fml::WeakPtr io_manager, + fml::RefPtr skia_unref_queue, + fml::WeakPtr image_decoder, + std::string advisory_script_uri, + std::string advisory_script_entrypoint, + Flags flags, + const fml::closure& isolate_create_callback, + const fml::closure& isolate_shutdown_callback); + DartIsolate(const Settings& settings, TaskRunners task_runners, fml::WeakPtr snapshot_delegate, @@ -413,6 +437,7 @@ class DartIsolate : public UIDartState { std::string advisory_script_uri, std::string advisory_script_entrypoint, bool is_root_isolate); + [[nodiscard]] bool Initialize(Dart_Isolate isolate); void SetMessageHandlingTaskRunner(fml::RefPtr runner); diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index 8a1bcb2cf2469..ddbc402e563ef 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/runtime/dart_isolate.h" @@ -10,6 +11,7 @@ #include "flutter/fml/thread.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/runtime/isolate_configuration.h" #include "flutter/testing/dart_isolate_runner.h" #include "flutter/testing/fixture_test.h" #include "flutter/testing/testing.h" @@ -46,25 +48,32 @@ TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) { GetCurrentTaskRunner(), // GetCurrentTaskRunner() // ); - auto weak_isolate = DartIsolate::CreateRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - {}, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint, - nullptr, // flags - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback + + auto isolate_configuration = + IsolateConfiguration::InferFromSettings(settings); + + auto weak_isolate = DartIsolate::CreateRunningRootIsolate( + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint, + DartIsolate::Flags{}, // flags + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback, // isolate shutdown callback + "main", // dart entrypoint + std::nullopt, // dart entrypoint library + std::move(isolate_configuration) // isolate configuration ); auto root_isolate = weak_isolate.lock(); ASSERT_TRUE(root_isolate); - ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::LibrariesSetup); + ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running); ASSERT_TRUE(root_isolate->Shutdown()); } @@ -81,25 +90,30 @@ TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) { GetCurrentTaskRunner(), // GetCurrentTaskRunner() // ); - auto weak_isolate = DartIsolate::CreateRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - {}, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint - nullptr, // flags - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback + auto isolate_configuration = + IsolateConfiguration::InferFromSettings(settings); + auto weak_isolate = DartIsolate::CreateRunningRootIsolate( + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint + DartIsolate::Flags{}, // flags + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback, // isolate shutdown callback + "main", // dart entrypoint + std::nullopt, // dart entrypoint library + std::move(isolate_configuration) // isolate configuration ); auto root_isolate = weak_isolate.lock(); ASSERT_TRUE(root_isolate); - ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::LibrariesSetup); + ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running); size_t destruction_callback_count = 0; root_isolate->AddIsolateShutdownCallback([&destruction_callback_count]() { ASSERT_NE(Dart_CurrentIsolate(), nullptr); diff --git a/runtime/dart_lifecycle_unittests.cc b/runtime/dart_lifecycle_unittests.cc index 3c45c702a80fe..e6fd7b1093351 100644 --- a/runtime/dart_lifecycle_unittests.cc +++ b/runtime/dart_lifecycle_unittests.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/common/task_runners.h" #include "flutter/fml/paths.h" @@ -8,6 +9,7 @@ #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/runtime/isolate_configuration.h" #include "flutter/testing/fixture_test.h" namespace flutter { @@ -50,57 +52,34 @@ static std::shared_ptr CreateAndRunRootIsolate( FML_CHECK(entrypoint.size() > 0); TaskRunners runners("io.flutter.test", task_runner, task_runner, task_runner, task_runner); - auto isolate_weak = DartIsolate::CreateRootIsolate( - vm.GetSettings(), // settings - vm.GetIsolateSnapshot(), // isolate_snapshot - runners, // task_runners - {}, // window - {}, // snapshot_delegate - {}, // hint_freed_delegate - {}, // io_manager - {}, // unref_queue - {}, // image_decoder - "main.dart", // advisory_script_uri - entrypoint.c_str(), // advisory_script_entrypoint - nullptr, // flags - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback - ); - - auto isolate = isolate_weak.lock(); - if (!isolate) { - FML_LOG(ERROR) << "Could not create valid isolate."; - return nullptr; - } - - if (DartVM::IsRunningPrecompiledCode()) { - if (!isolate->PrepareForRunningFromPrecompiledCode()) { - FML_LOG(ERROR) - << "Could not prepare to run the isolate from precompiled code."; - return nullptr; - } - - } else { - if (!isolate->PrepareForRunningFromKernels( - settings.application_kernels())) { - FML_LOG(ERROR) << "Could not prepare isolate from application kernels."; - return nullptr; - } - } + auto isolate_configuration = + IsolateConfiguration::InferFromSettings(settings); + + auto isolate = + DartIsolate::CreateRunningRootIsolate( + vm.GetSettings(), // settings + vm.GetIsolateSnapshot(), // isolate_snapshot + runners, // task_runners + {}, // window + {}, // snapshot_delegate + {}, // hint_freed_delegate + {}, // io_manager + {}, // unref_queue + {}, // image_decoder + "main.dart", // advisory_script_uri + entrypoint.c_str(), // advisory_script_entrypoint + DartIsolate::Flags{}, // flags + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback, // isolate shutdown callback, + entrypoint, // dart entrypoint + std::nullopt, // dart entrypoint library + std::move(isolate_configuration) // isolate configuration + ) + .lock(); - if (isolate->GetPhase() != DartIsolate::Phase::Ready) { - FML_LOG(ERROR) << "Isolate was not ready."; - return nullptr; - } - - if (!isolate->Run(entrypoint, {}, settings.root_isolate_create_callback)) { - FML_LOG(ERROR) << "Could not run entrypoint: " << entrypoint << "."; - return nullptr; - } - - if (isolate->GetPhase() != DartIsolate::Phase::Running) { - FML_LOG(ERROR) << "Isolate was not Running."; + if (!isolate) { + FML_LOG(ERROR) << "Could not launch the root isolate."; return nullptr; } diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index 050592d04a967..dd2d45c4d40f4 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/runtime/dart_snapshot.h" @@ -11,6 +12,7 @@ #include "flutter/fml/trace_event.h" #include "flutter/lib/snapshot/snapshot.h" #include "flutter/runtime/dart_vm.h" +#include "third_party/dart/runtime/include/dart_api.h" namespace flutter { @@ -202,4 +204,16 @@ const uint8_t* DartSnapshot::GetInstructionsMapping() const { return instructions_ ? instructions_->GetMapping() : nullptr; } +bool DartSnapshot::IsNullSafetyEnabled(const fml::Mapping* kernel) const { + return ::Dart_DetectNullSafety( + nullptr, // script_uri (unsupported by Flutter) + nullptr, // package_config (package resolution of parent used) + nullptr, // original_working_directory (no package config) + GetDataMapping(), // snapshot_data + GetInstructionsMapping(), // snapshot_instructions + kernel ? kernel->GetMapping() : nullptr, // kernel_buffer + kernel ? kernel->GetSize() : 0u // kernel_buffer_size + ); +} + } // namespace flutter diff --git a/runtime/dart_snapshot.h b/runtime/dart_snapshot.h index 97038aac4aee9..15ca157c7a273 100644 --- a/runtime/dart_snapshot.h +++ b/runtime/dart_snapshot.h @@ -139,6 +139,9 @@ class DartSnapshot : public fml::RefCountedThreadSafe { /// const uint8_t* GetInstructionsMapping() const; + bool IsNullSafetyEnabled( + const fml::Mapping* application_kernel_mapping) const; + private: std::shared_ptr data_; std::shared_ptr instructions_; diff --git a/runtime/fixtures/runtime_test.dart b/runtime/fixtures/runtime_test.dart index 821e01e8fa433..616e026a5ccce 100644 --- a/runtime/fixtures/runtime_test.dart +++ b/runtime/fixtures/runtime_test.dart @@ -32,13 +32,7 @@ void testIsolateShutdown() { } @pragma('vm:entry-point') void testCanSaveCompilationTrace() { - List trace; - try { - trace = saveCompilationTrace(); - } catch (exception) { - print('Could not save compilation trace: ' + exception); - } - notifyResult(trace != null && trace.isNotEmpty); + notifyResult(saveCompilationTrace().isNotEmpty); } void notifyResult(bool success) native 'NotifyNative'; @@ -58,5 +52,44 @@ void testCanLaunchSecondaryIsolate() { @pragma('vm:entry-point') void testCanRecieveArguments(List args) { - notifyResult(args != null && args.length == 1 && args[0] == 'arg1'); + notifyResult(args.length == 1 && args[0] == 'arg1'); +} + +@pragma('vm:entry-point') +void trampoline() { + notifyNative(); +} + +void notifySuccess(bool success) native 'NotifySuccess'; + +@pragma('vm:entry-point') +void testCanConvertEmptyList(List args){ + notifySuccess(args.length == 0); +} + +@pragma('vm:entry-point') +void testCanConvertListOfStrings(List args){ + notifySuccess(args.length == 4 && + args[0] == 'tinker' && + args[1] == 'tailor' && + args[2] == 'soldier' && + args[3] == 'sailor'); +} + +@pragma('vm:entry-point') +void testCanConvertListOfDoubles(List args){ + notifySuccess(args.length == 4 && + args[0] == 1.0 && + args[1] == 2.0 && + args[2] == 3.0 && + args[3] == 4.0); +} + +@pragma('vm:entry-point') +void testCanConvertListOfInts(List args){ + notifySuccess(args.length == 4 && + args[0] == 1 && + args[1] == 2 && + args[2] == 3 && + args[3] == 4); } diff --git a/shell/common/isolate_configuration.cc b/runtime/isolate_configuration.cc similarity index 70% rename from shell/common/isolate_configuration.cc rename to runtime/isolate_configuration.cc index 4d37c03464ff2..a7f4aa19da5c9 100644 --- a/shell/common/isolate_configuration.cc +++ b/runtime/isolate_configuration.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/common/isolate_configuration.h" +#include "flutter/runtime/isolate_configuration.h" #include "flutter/fml/make_copyable.h" #include "flutter/runtime/dart_vm.h" @@ -32,6 +32,11 @@ class AppSnapshotIsolateConfiguration final : public IsolateConfiguration { return isolate.PrepareForRunningFromPrecompiledCode(); } + // |IsolateConfiguration| + bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override { + return snapshot.IsNullSafetyEnabled(nullptr); + } + private: FML_DISALLOW_COPY_AND_ASSIGN(AppSnapshotIsolateConfiguration); }; @@ -49,6 +54,11 @@ class KernelIsolateConfiguration : public IsolateConfiguration { return isolate.PrepareForRunningFromKernel(std::move(kernel_)); } + // |IsolateConfiguration| + bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override { + return snapshot.IsNullSafetyEnabled(kernel_.get()); + } + private: std::unique_ptr kernel_; @@ -60,7 +70,12 @@ class KernelListIsolateConfiguration final : public IsolateConfiguration { KernelListIsolateConfiguration( std::vector>> kernel_pieces) - : kernel_pieces_(std::move(kernel_pieces)) {} + : kernel_piece_futures_(std::move(kernel_pieces)) { + if (kernel_piece_futures_.empty()) { + FML_LOG(ERROR) << "Attempted to create kernel list configuration without " + "any kernel blobs."; + } + } // |IsolateConfiguration| bool DoPrepareIsolate(DartIsolate& isolate) override { @@ -68,11 +83,22 @@ class KernelListIsolateConfiguration final : public IsolateConfiguration { return false; } - for (size_t i = 0; i < kernel_pieces_.size(); i++) { - bool last_piece = i + 1 == kernel_pieces_.size(); + ResolveKernelPiecesIfNecessary(); - if (!isolate.PrepareForRunningFromKernel(kernel_pieces_[i].get(), - last_piece)) { + if (resolved_kernel_pieces_.empty()) { + FML_DLOG(ERROR) << "No kernel pieces provided to prepare this isolate."; + return false; + } + + for (size_t i = 0; i < resolved_kernel_pieces_.size(); i++) { + if (!resolved_kernel_pieces_[i]) { + FML_DLOG(ERROR) << "This kernel list isolate configuration was already " + "used to prepare an isolate."; + return false; + } + const bool last_piece = i + 1 == resolved_kernel_pieces_.size(); + if (!isolate.PrepareForRunningFromKernel( + std::move(resolved_kernel_pieces_[i]), last_piece)) { return false; } } @@ -80,8 +106,36 @@ class KernelListIsolateConfiguration final : public IsolateConfiguration { return true; } + // |IsolateConfiguration| + bool IsNullSafetyEnabled(const DartSnapshot& snapshot) override { + ResolveKernelPiecesIfNecessary(); + const auto kernel = resolved_kernel_pieces_.empty() + ? nullptr + : resolved_kernel_pieces_.front().get(); + return snapshot.IsNullSafetyEnabled(kernel); + } + + // This must be call as late as possible before accessing any of the kernel + // pieces. This will delay blocking on the futures for as long as possible. So + // far, only Fuchsia depends on this optimization and only on the non-AOT + // configs. + void ResolveKernelPiecesIfNecessary() { + if (resolved_kernel_pieces_.size() == kernel_piece_futures_.size()) { + return; + } + + resolved_kernel_pieces_.clear(); + for (auto& piece : kernel_piece_futures_) { + // The get() call will xfer the unique pointer out and leave an empty + // future in the original vector. + resolved_kernel_pieces_.emplace_back(piece.get()); + } + } + private: - std::vector>> kernel_pieces_; + std::vector>> + kernel_piece_futures_; + std::vector> resolved_kernel_pieces_; FML_DISALLOW_COPY_AND_ASSIGN(KernelListIsolateConfiguration); }; @@ -150,10 +204,6 @@ std::unique_ptr IsolateConfiguration::InferFromSettings( return CreateForAppSnapshot(); } - if (!asset_manager) { - return nullptr; - } - if (settings.application_kernels) { return CreateForKernelList(settings.application_kernels()); } @@ -165,7 +215,13 @@ std::unique_ptr IsolateConfiguration::InferFromSettings( return nullptr; } - // Running from kernel snapshot. + if (!asset_manager) { + FML_DLOG(ERROR) << "No asset manager specified when attempting to create " + "isolate configuration."; + return nullptr; + } + + // Running from kernel snapshot. Requires asset manager. { std::unique_ptr kernel = asset_manager->GetAsMapping(settings.application_kernel_asset); @@ -174,7 +230,14 @@ std::unique_ptr IsolateConfiguration::InferFromSettings( } } - // Running from kernel divided into several pieces (for sharing). + // Running from kernel divided into several pieces (for sharing). Requires + // asset manager and io worker. + + if (!io_worker) { + FML_DLOG(ERROR) << "No IO worker specified to load kernel pieces."; + return nullptr; + } + { std::unique_ptr kernel_list = asset_manager->GetAsMapping(settings.application_kernel_list_asset); @@ -206,6 +269,10 @@ std::unique_ptr IsolateConfiguration::CreateForKernelList( std::vector> kernel_pieces) { std::vector>> pieces; for (auto& piece : kernel_pieces) { + if (!piece) { + FML_DLOG(ERROR) << "Invalid kernel piece."; + continue; + } std::promise> promise; pieces.push_back(promise.get_future()); promise.set_value(std::move(piece)); diff --git a/shell/common/isolate_configuration.h b/runtime/isolate_configuration.h similarity index 90% rename from shell/common/isolate_configuration.h rename to runtime/isolate_configuration.h index c5f286103a0d6..1fc3efef51323 100644 --- a/shell/common/isolate_configuration.h +++ b/runtime/isolate_configuration.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_COMMON_ISOLATE_CONFIGURATION_H_ -#define FLUTTER_SHELL_COMMON_ISOLATE_CONFIGURATION_H_ +#ifndef FLUTTER_SHELL_RUNTIME_ISOLATE_CONFIGURATION_H_ +#define FLUTTER_SHELL_RUNTIME_ISOLATE_CONFIGURATION_H_ #include #include @@ -50,7 +50,10 @@ class IsolateConfiguration { /// for which this run configuration is used is collected. /// /// @param[in] settings The settings - /// @param[in] asset_manager The asset manager + /// @param[in] asset_manager The optional asset manager. This is used when + /// using the legacy settings fields that specify + /// the asset by name instead of a mappings + /// callback. /// @param[in] io_worker An optional IO worker. Specify `nullptr` is a /// worker should not be used or one is not /// available. @@ -58,10 +61,10 @@ class IsolateConfiguration { /// @return An isolate configuration if one can be inferred from the /// settings. If not, returns `nullptr`. /// - static std::unique_ptr InferFromSettings( + [[nodiscard]] static std::unique_ptr InferFromSettings( const Settings& settings, - std::shared_ptr asset_manager, - fml::RefPtr io_worker); + std::shared_ptr asset_manager = nullptr, + fml::RefPtr io_worker = nullptr); //---------------------------------------------------------------------------- /// @brief Creates an AOT isolate configuration using snapshot symbols @@ -152,7 +155,9 @@ class IsolateConfiguration { /// returns true, the engine will not move the isolate to the /// `DartIsolate::Phase::Ready` phase for subsequent run. /// - bool PrepareIsolate(DartIsolate& isolate); + [[nodiscard]] bool PrepareIsolate(DartIsolate& isolate); + + virtual bool IsNullSafetyEnabled(const DartSnapshot& snapshot) = 0; protected: virtual bool DoPrepareIsolate(DartIsolate& isolate) = 0; @@ -163,4 +168,4 @@ class IsolateConfiguration { } // namespace flutter -#endif // FLUTTER_SHELL_COMMON_ISOLATE_CONFIGURATION_H_ +#endif // FLUTTER_SHELL_RUNTIME_ISOLATE_CONFIGURATION_H_ diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 7697d70e37c0f..b2a8f7a14389f 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/runtime/runtime_controller.h" @@ -11,6 +12,7 @@ #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" +#include "flutter/runtime/isolate_configuration.h" #include "flutter/runtime/runtime_delegate.h" #include "third_party/tonic/dart_message_handler.h" @@ -18,10 +20,7 @@ namespace flutter { RuntimeController::RuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners) - : client_(client), - vm_(nullptr), - task_runners_(p_task_runners), - weak_factory_(this) {} + : client_(client), vm_(nullptr), task_runners_(p_task_runners) {} RuntimeController::RuntimeController( RuntimeDelegate& p_client, @@ -55,82 +54,7 @@ RuntimeController::RuntimeController( platform_data_(std::move(p_platform_data)), isolate_create_callback_(p_isolate_create_callback), isolate_shutdown_callback_(p_isolate_shutdown_callback), - persistent_isolate_data_(std::move(p_persistent_isolate_data)), - weak_factory_(this) { - // Create the root isolate as soon as the runtime controller is initialized, - // but not using a synchronous way to avoid blocking the platform thread a - // long time as it is waiting while creating `Shell` on that platform thread. - // It will be run at a later point when the engine provides a run - // configuration and then runs the isolate. - create_and_config_root_isolate_ = - std::async(std::launch::deferred, [self = weak_factory_.GetWeakPtr()]() { - if (!self) { - return; - } - - auto strong_root_isolate = - DartIsolate::CreateRootIsolate( - self->vm_->GetVMData()->GetSettings(), // - self->isolate_snapshot_, // - self->task_runners_, // - std::make_unique(self.get()), // - self->snapshot_delegate_, // - self->hint_freed_delegate_, // - self->io_manager_, // - self->unref_queue_, // - self->image_decoder_, // - self->advisory_script_uri_, // - self->advisory_script_entrypoint_, // - nullptr, // - self->isolate_create_callback_, // - self->isolate_shutdown_callback_ // - ) - .lock(); - - FML_CHECK(strong_root_isolate) << "Could not create root isolate."; - - // The root isolate ivar is weak. - self->root_isolate_ = strong_root_isolate; - - strong_root_isolate->SetReturnCodeCallback([self](uint32_t code) { - if (!self) { - return; - } - - self->root_isolate_return_code_ = {true, code}; - }); - - if (auto* platform_configuration = - self->GetPlatformConfigurationIfAvailable()) { - tonic::DartState::Scope scope(strong_root_isolate); - platform_configuration->DidCreateIsolate(); - if (!self->FlushRuntimeStateToIsolate()) { - FML_DLOG(ERROR) << "Could not setup initial isolate state."; - } - } else { - FML_DCHECK(false) - << "RuntimeController created without window binding."; - } - - FML_DCHECK(Dart_CurrentIsolate() == nullptr); - - self->client_.OnRootIsolateCreated(); - return; - }); - - // We're still trying to create the root isolate as soon as possible here on - // the UI thread although it's deferred a little bit by - // std::async(std::launch::deferred, ...). So the callers of `GetRootIsolate` - // should get a quick return after this UI thread task. - task_runners_.GetUITaskRunner()->PostTask( - [self = weak_factory_.GetWeakPtr()]() { - if (!self) { - return; - } - - self->GetRootIsolate(); - }); -} + persistent_isolate_data_(std::move(p_persistent_isolate_data)) {} RuntimeController::~RuntimeController() { FML_DCHECK(Dart_CurrentIsolate() == nullptr); @@ -418,20 +342,82 @@ tonic::DartErrorHandleType RuntimeController::GetLastError() { return root_isolate ? root_isolate->GetLastError() : tonic::kNoError; } +bool RuntimeController::LaunchRootIsolate( + std::optional dart_entrypoint, + std::optional dart_entrypoint_library, + std::unique_ptr isolate_configuration) { + if (root_isolate_.lock()) { + FML_LOG(ERROR) << "Root isolate was already running."; + return false; + } + + auto strong_root_isolate = + DartIsolate::CreateRunningRootIsolate( + vm_->GetVMData()->GetSettings(), // + isolate_snapshot_, // + task_runners_, // + std::make_unique(this), // + snapshot_delegate_, // + hint_freed_delegate_, // + io_manager_, // + unref_queue_, // + image_decoder_, // + advisory_script_uri_, // + advisory_script_entrypoint_, // + DartIsolate::Flags{}, // + isolate_create_callback_, // + isolate_shutdown_callback_, // + dart_entrypoint, // + dart_entrypoint_library, // + std::move(isolate_configuration) // + ) + .lock(); + + if (!strong_root_isolate) { + FML_LOG(ERROR) << "Could not create root isolate."; + return false; + } + + // The root isolate ivar is weak. + root_isolate_ = strong_root_isolate; + + // Capture by `this` here is safe because the callback is made by the dart + // state itself. The isolate (and its Dart state) is owned by this object and + // it will be collected before this object. + strong_root_isolate->SetReturnCodeCallback( + [this](uint32_t code) { root_isolate_return_code_ = code; }); + + if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { + tonic::DartState::Scope scope(strong_root_isolate); + platform_configuration->DidCreateIsolate(); + if (!FlushRuntimeStateToIsolate()) { + FML_DLOG(ERROR) << "Could not setup initial isolate state."; + } + } else { + FML_DCHECK(false) << "RuntimeController created without window binding."; + } + + FML_DCHECK(Dart_CurrentIsolate() == nullptr); + return true; +} + +std::optional RuntimeController::GetRootIsolateServiceID() const { + if (auto isolate = root_isolate_.lock()) { + return isolate->GetServiceId(); + } + return std::nullopt; +} + std::weak_ptr RuntimeController::GetRootIsolate() { std::shared_ptr root_isolate = root_isolate_.lock(); if (root_isolate) { return root_isolate_; } - // Root isolate is not yet created, get it and do some configuration. - FML_DCHECK(create_and_config_root_isolate_.valid()); - create_and_config_root_isolate_.get(); - return root_isolate_; } -std::pair RuntimeController::GetRootIsolateReturnCode() { +std::optional RuntimeController::GetRootIsolateReturnCode() { return root_isolate_return_code_; } diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index b3f1a41d3716f..9b5ef99da617c 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -96,7 +96,7 @@ class RuntimeController : public PlatformConfigurationClient { /// code in isolate scope when the VM /// is about to be notified that the /// engine is going to be idle. - /// @param[in] platform_data The window data (if exists). + /// @param[in] platform_data The window data (if exists). /// @param[in] isolate_create_callback The isolate create callback. This /// allows callers to run native code /// in isolate scope on the UI task @@ -133,10 +133,39 @@ class RuntimeController : public PlatformConfigurationClient { ~RuntimeController() override; //---------------------------------------------------------------------------- - /// @brief Clone the the runtime controller. This re-creates the root - /// isolate with the same snapshots and copies all window data to - /// the new instance. This is usually only used in the debug - /// runtime mode to support the cold-restart scenario. + /// @brief Launches the isolate using the window data associated with + /// this runtime controller. Before this call, the Dart isolate + /// has not been initialized. On successful return, the caller can + /// assume that the isolate is in the + /// `DartIsolate::Phase::Running` phase. + /// + /// This call will fail if a root isolate is already running. To + /// re-create an isolate with the window data associated with this + /// runtime controller, `Clone` this runtime controller and + /// Launch an isolate in that runtime controller instead. + /// + /// @param[in] dart_entrypoint The dart entrypoint. If + /// `std::nullopt` or empty, `main` will + /// be attempted. + /// @param[in] dart_entrypoint_library The dart entrypoint library. If + /// `std::nullopt` or empty, the core + /// library will be attempted. + /// @param[in] isolate_configuration The isolate configuration + /// + /// @return If the isolate could be launched and guided to the + /// `DartIsolate::Phase::Running` phase. + /// + [[nodiscard]] bool LaunchRootIsolate( + std::optional dart_entrypoint, + std::optional dart_entrypoint_library, + std::unique_ptr isolate_configuration); + + //---------------------------------------------------------------------------- + /// @brief Clone the the runtime controller. Launching an isolate with a + /// cloned runtime controller will use the same snapshots and + /// copies all window data to the new instance. This is usually + /// only used in the debug runtime mode to support the + /// cold-restart scenario. /// /// @return A clone of the existing runtime controller. /// @@ -428,29 +457,20 @@ class RuntimeController : public PlatformConfigurationClient { tonic::DartErrorHandleType GetLastError(); //---------------------------------------------------------------------------- - /// @brief Get a weak pointer to the root Dart isolate. This isolate may - /// only be locked on the UI task runner. Callers use this - /// accessor to transition to the root isolate to the running - /// phase. Note that it might take times if the isolate is not yet - /// created, which should be done in a subsequence task after - /// constructing `RuntimeController`, or it should get a quick - /// return otherwise. + /// @brief Get the service ID of the root isolate if the root isolate is + /// running. /// - /// @return The root isolate reference. + /// @return The root isolate service id. /// - std::weak_ptr GetRootIsolate(); + std::optional GetRootIsolateServiceID() const; //---------------------------------------------------------------------------- /// @brief Get the return code specified by the root isolate (if one is /// present). /// - /// @bug Change this method to return `std::optional` - /// instead. - /// - /// @return The root isolate return code. The first argument in the pair - /// indicates if one is specified by the root isolate. + /// @return The root isolate return code if the isolate has specified one. /// - std::pair GetRootIsolateReturnCode(); + std::optional GetRootIsolateReturnCode(); protected: /// Constructor for Mocks. @@ -489,11 +509,10 @@ class RuntimeController : public PlatformConfigurationClient { // `RuntimeController`, be careful to use it directly while it might have not // been created yet. Call `GetRootIsolate()` instead which guarantees that. std::weak_ptr root_isolate_; - std::pair root_isolate_return_code_ = {false, 0}; + std::optional root_isolate_return_code_; const fml::closure isolate_create_callback_; const fml::closure isolate_shutdown_callback_; std::shared_ptr persistent_isolate_data_; - fml::WeakPtrFactory weak_factory_; PlatformConfiguration* GetPlatformConfigurationIfAvailable(); @@ -531,6 +550,16 @@ class RuntimeController : public PlatformConfigurationClient { std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; + //---------------------------------------------------------------------------- + /// @brief Get a weak pointer to the root Dart isolate. This isolate may + /// only be locked on the UI task runner. Callers use this + /// accessor to transition to the root isolate to the running + /// phase. + /// + /// @return The root isolate reference. + /// + std::weak_ptr GetRootIsolate(); + FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController); }; diff --git a/runtime/type_conversions_unittests.cc b/runtime/type_conversions_unittests.cc new file mode 100644 index 0000000000000..fe02962a3aa56 --- /dev/null +++ b/runtime/type_conversions_unittests.cc @@ -0,0 +1,175 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// FLUTTER_NOLINT + +#include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/testing/dart_isolate_runner.h" +#include "flutter/testing/fixture_test.h" +#include "flutter/testing/testing.h" +#include "flutter/third_party/tonic/converter/dart_converter.h" + +namespace flutter { +namespace testing { + +class TypeConversionsTest : public FixtureTest { + public: + TypeConversionsTest() + : settings_(CreateSettingsForFixture()), + vm_(DartVMRef::Create(settings_)) {} + + ~TypeConversionsTest() = default; + + [[nodiscard]] bool RunWithEntrypoint(const std::string& entrypoint) { + if (running_isolate_) { + return false; + } + auto thread = CreateNewThread(); + TaskRunners single_threaded_task_runner(GetCurrentTestName(), thread, + thread, thread, thread); + auto isolate = + RunDartCodeInIsolate(vm_, settings_, single_threaded_task_runner, + entrypoint, {}, GetFixturesPath()); + if (!isolate || isolate->get()->GetPhase() != DartIsolate::Phase::Running) { + return false; + } + + running_isolate_ = std::move(isolate); + return true; + } + + private: + Settings settings_; + DartVMRef vm_; + std::unique_ptr running_isolate_; + FML_DISALLOW_COPY_AND_ASSIGN(TypeConversionsTest); +}; + +TEST_F(TypeConversionsTest, TestFixture) { + ASSERT_TRUE(RunWithEntrypoint("main")); +} + +TEST_F(TypeConversionsTest, CanConvertEmptyList) { + fml::AutoResetWaitableEvent event; + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + event.Signal(); + })); + AddNativeCallback( + "NotifyNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { + std::vector items; + auto items_handle = tonic::ToDart(items); + ASSERT_FALSE(tonic::LogIfError(items_handle)); + tonic::DartInvokeField(::Dart_RootLibrary(), "testCanConvertEmptyList", + {items_handle}); + })); + ASSERT_TRUE(RunWithEntrypoint("trampoline")); + event.Wait(); +} + +TEST_F(TypeConversionsTest, CanConvertListOfStrings) { + fml::AutoResetWaitableEvent event; + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + event.Signal(); + })); + AddNativeCallback( + "NotifyNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { + std::vector items; + items.push_back("tinker"); + items.push_back("tailor"); + items.push_back("soldier"); + items.push_back("sailor"); + auto items_handle = tonic::ToDart(items); + ASSERT_FALSE(tonic::LogIfError(items_handle)); + tonic::DartInvokeField(::Dart_RootLibrary(), + "testCanConvertListOfStrings", {items_handle}); + })); + ASSERT_TRUE(RunWithEntrypoint("trampoline")); + event.Wait(); +} + +TEST_F(TypeConversionsTest, CanConvertListOfDoubles) { + fml::AutoResetWaitableEvent event; + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + event.Signal(); + })); + AddNativeCallback( + "NotifyNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { + std::vector items; + items.push_back(1.0); + items.push_back(2.0); + items.push_back(3.0); + items.push_back(4.0); + auto items_handle = tonic::ToDart(items); + ASSERT_FALSE(tonic::LogIfError(items_handle)); + tonic::DartInvokeField(::Dart_RootLibrary(), + "testCanConvertListOfDoubles", {items_handle}); + })); + ASSERT_TRUE(RunWithEntrypoint("trampoline")); + event.Wait(); +} + +TEST_F(TypeConversionsTest, CanConvertListOfInts) { + fml::AutoResetWaitableEvent event; + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + event.Signal(); + })); + AddNativeCallback( + "NotifyNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { + std::vector items; + items.push_back(1); + items.push_back(2); + items.push_back(3); + items.push_back(4); + auto items_handle = tonic::ToDart(items); + ASSERT_FALSE(tonic::LogIfError(items_handle)); + tonic::DartInvokeField(::Dart_RootLibrary(), "testCanConvertListOfInts", + {items_handle}); + })); + ASSERT_TRUE(RunWithEntrypoint("trampoline")); + event.Wait(); +} + +TEST_F(TypeConversionsTest, CanConvertListOfFloatsToListOfDartDoubles) { + fml::AutoResetWaitableEvent event; + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + event.Signal(); + })); + AddNativeCallback( + "NotifyNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { + std::vector items; + items.push_back(1.0f); + items.push_back(2.0f); + items.push_back(3.0f); + items.push_back(4.0f); + auto items_handle = tonic::ToDart(items); + ASSERT_FALSE(tonic::LogIfError(items_handle)); + // This will fail on type mismatch. + tonic::DartInvokeField(::Dart_RootLibrary(), + "testCanConvertListOfDoubles", {items_handle}); + })); + ASSERT_TRUE(RunWithEntrypoint("trampoline")); + event.Wait(); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index eff5742eebee8..c988e88c2a1a6 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -69,8 +69,6 @@ source_set("common") { "display_manager.h", "engine.cc", "engine.h", - "isolate_configuration.cc", - "isolate_configuration.h", "persistent_cache.cc", "persistent_cache.h", "pipeline.cc", diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 310ebc4ce925b..31901318a8243 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/shell/common/engine.h" @@ -153,90 +154,32 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) { last_entry_point_ = configuration.GetEntrypoint(); last_entry_point_library_ = configuration.GetEntrypointLibrary(); - auto isolate_launch_status = - PrepareAndLaunchIsolate(std::move(configuration)); - if (isolate_launch_status == Engine::RunStatus::Failure) { - FML_LOG(ERROR) << "Engine not prepare and launch isolate."; - return isolate_launch_status; - } else if (isolate_launch_status == - Engine::RunStatus::FailureAlreadyRunning) { - return isolate_launch_status; - } - - std::shared_ptr isolate = - runtime_controller_->GetRootIsolate().lock(); - - bool isolate_running = - isolate && isolate->GetPhase() == DartIsolate::Phase::Running; - - if (isolate_running) { - tonic::DartState::Scope scope(isolate.get()); - - if (settings_.root_isolate_create_callback) { - settings_.root_isolate_create_callback(); - } - - if (settings_.root_isolate_shutdown_callback) { - isolate->AddIsolateShutdownCallback( - settings_.root_isolate_shutdown_callback); - } - - std::string service_id = isolate->GetServiceId(); - fml::RefPtr service_id_message = - fml::MakeRefCounted( - kIsolateChannel, - std::vector(service_id.begin(), service_id.end()), - nullptr); - HandlePlatformMessage(service_id_message); - } - - return isolate_running ? Engine::RunStatus::Success - : Engine::RunStatus::Failure; -} - -Engine::RunStatus Engine::PrepareAndLaunchIsolate( - RunConfiguration configuration) { - TRACE_EVENT0("flutter", "Engine::PrepareAndLaunchIsolate"); - UpdateAssetManager(configuration.GetAssetManager()); - auto isolate_configuration = configuration.TakeIsolateConfiguration(); - - std::shared_ptr isolate = - runtime_controller_->GetRootIsolate().lock(); - - if (!isolate) { - return RunStatus::Failure; - } - - // This can happen on iOS after a plugin shows a native window and returns to - // the Flutter ViewController. - if (isolate->GetPhase() == DartIsolate::Phase::Running) { - FML_DLOG(WARNING) << "Isolate was already running!"; + if (runtime_controller_->IsRootIsolateRunning()) { return RunStatus::FailureAlreadyRunning; } - if (!isolate_configuration->PrepareIsolate(*isolate)) { - FML_LOG(ERROR) << "Could not prepare to run the isolate."; + if (!runtime_controller_->LaunchRootIsolate( + configuration.GetEntrypoint(), // + configuration.GetEntrypointLibrary(), // + configuration.TakeIsolateConfiguration()) // + ) { return RunStatus::Failure; } - if (configuration.GetEntrypointLibrary().empty()) { - if (!isolate->Run(configuration.GetEntrypoint(), - settings_.dart_entrypoint_args)) { - FML_LOG(ERROR) << "Could not run the isolate."; - return RunStatus::Failure; - } - } else { - if (!isolate->RunFromLibrary(configuration.GetEntrypointLibrary(), - configuration.GetEntrypoint(), - settings_.dart_entrypoint_args)) { - FML_LOG(ERROR) << "Could not run the isolate."; - return RunStatus::Failure; - } + auto service_id = runtime_controller_->GetRootIsolateServiceID(); + if (service_id.has_value()) { + fml::RefPtr service_id_message = + fml::MakeRefCounted( + kIsolateChannel, + std::vector(service_id.value().begin(), + service_id.value().end()), + nullptr); + HandlePlatformMessage(service_id_message); } - return RunStatus::Success; + return Engine::RunStatus::Success; } void Engine::BeginFrame(fml::TimePoint frame_time) { @@ -261,7 +204,7 @@ void Engine::NotifyIdle(int64_t deadline) { hint_freed_bytes_since_last_idle_ = 0; } -std::pair Engine::GetUIIsolateReturnCode() { +std::optional Engine::GetUIIsolateReturnCode() { return runtime_controller_->GetRootIsolateReturnCode(); } diff --git a/shell/common/engine.h b/shell/common/engine.h index c0e53c3d742a4..3319e9c9e28c5 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -608,14 +608,9 @@ class Engine final : public RuntimeDelegate, /// /// @see `UIIsolateHasLivePorts` /// - // TODO(chinmaygarde): Use std::optional instead of the pair now that it is - // available. + /// @return The return code (if specified) by the isolate. /// - /// @return A pair containing a boolean value indicating if the isolate - /// set a "return value" and that value if present. When the first - /// item of the pair is false, second item is meaningless. - /// - std::pair GetUIIsolateReturnCode(); + std::optional GetUIIsolateReturnCode(); //---------------------------------------------------------------------------- /// @brief Indicates to the Flutter application that it has obtained a @@ -837,8 +832,6 @@ class Engine final : public RuntimeDelegate, bool GetAssetAsBuffer(const std::string& name, std::vector* data); - RunStatus PrepareAndLaunchIsolate(RunConfiguration configuration); - friend class testing::ShellTest; FML_DISALLOW_COPY_AND_ASSIGN(Engine); diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 492ddb8960d03..15d9386df12e7 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -87,7 +87,7 @@ void testCanLaunchSecondaryIsolate() { @pragma('vm:entry-point') void testSkiaResourceCacheSendsResponse() { - final PlatformMessageResponseCallback callback = (ByteData data) { + final PlatformMessageResponseCallback callback = (ByteData? data) { if (data == null) { throw 'Response must not be null.'; } @@ -130,7 +130,7 @@ void canCreateImageFromDecompressedData() { @pragma('vm:entry-point') void canAccessIsolateLaunchData() { - notifyMessage(utf8.decode(window.getPersistentIsolateData().buffer.asUint8List())); + notifyMessage(utf8.decode(window.getPersistentIsolateData()!.buffer.asUint8List())); } void notifyMessage(String string) native 'NotifyMessage'; @@ -174,7 +174,7 @@ void canAccessResourceFromAssetDir() async { window.sendPlatformMessage( 'flutter/assets', Uint8List.fromList(utf8.encode('kernel_blob.bin')).buffer.asByteData(), - (ByteData byteData) { + (ByteData? byteData) { notifyCanAccessResource(byteData != null); }, ); diff --git a/shell/common/run_configuration.h b/shell/common/run_configuration.h index 45dfed1c2d232..cb5a5b531d248 100644 --- a/shell/common/run_configuration.h +++ b/shell/common/run_configuration.h @@ -14,7 +14,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "flutter/fml/unique_fd.h" -#include "flutter/shell/common/isolate_configuration.h" +#include "flutter/runtime/isolate_configuration.h" namespace flutter { diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 5dae4e494745c..1df95c1c8727c 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -79,12 +79,12 @@ class SemanticsActionData { const SemanticsActionData(this.id, this.action, this.args); final int id; final SemanticsAction action; - final ByteData args; + final ByteData? args; } Future get semanticsAction { final Completer actionReceived = Completer(); - window.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { + window.onSemanticsAction = (int id, SemanticsAction action, ByteData? args) { actionReceived.complete(SemanticsActionData(id, action, args)); }; return actionReceived.future; @@ -115,12 +115,52 @@ void a11y_main() async { // ignore: non_constant_identifier_names transform: kTestTransform, childrenInTraversalOrder: Int32List.fromList([84, 96]), childrenInHitTestOrder: Int32List.fromList([96, 84]), + actions: 0, + flags: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: 0, + textSelectionExtent: 0, + platformViewId: 0, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0.0, + scrollExtentMax: 0.0, + scrollExtentMin: 0.0, + elevation: 0.0, + thickness: 0.0, + hint: "", + value: "", + increasedValue: "", + decreasedValue: "", + additionalActions: Int32List(0), ) ..updateNode( id: 84, label: 'B: leaf', rect: Rect.fromLTRB(40.0, 40.0, 80.0, 80.0), transform: kTestTransform, + actions: 0, + flags: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: 0, + textSelectionExtent: 0, + platformViewId: 0, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0.0, + scrollExtentMax: 0.0, + scrollExtentMin: 0.0, + elevation: 0.0, + thickness: 0.0, + hint: "", + value: "", + increasedValue: "", + decreasedValue: "", + additionalActions: Int32List(0), + childrenInHitTestOrder: Int32List(0), + childrenInTraversalOrder: Int32List(0), ) ..updateNode( id: 96, @@ -129,6 +169,25 @@ void a11y_main() async { // ignore: non_constant_identifier_names transform: kTestTransform, childrenInTraversalOrder: Int32List.fromList([128]), childrenInHitTestOrder: Int32List.fromList([128]), + actions: 0, + flags: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: 0, + textSelectionExtent: 0, + platformViewId: 0, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0.0, + scrollExtentMax: 0.0, + scrollExtentMin: 0.0, + elevation: 0.0, + thickness: 0.0, + hint: "", + value: "", + increasedValue: "", + decreasedValue: "", + additionalActions: Int32List(0), ) ..updateNode( id: 128, @@ -137,6 +196,25 @@ void a11y_main() async { // ignore: non_constant_identifier_names transform: kTestTransform, additionalActions: Int32List.fromList([21]), platformViewId: 0x3f3, + actions: 0, + flags: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: 0, + textSelectionExtent: 0, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0.0, + scrollExtentMax: 0.0, + scrollExtentMin: 0.0, + elevation: 0.0, + thickness: 0.0, + hint: "", + value: "", + increasedValue: "", + decreasedValue: "", + childrenInHitTestOrder: Int32List(0), + childrenInTraversalOrder: Int32List(0), ) ..updateCustomAction( id: 21, @@ -148,7 +226,7 @@ void a11y_main() async { // ignore: non_constant_identifier_names // Await semantics action from embedder. final SemanticsActionData data = await semanticsAction; - final List actionArgs = [data.args.getInt8(0), data.args.getInt8(1)]; + final List actionArgs = [data.args!.getInt8(0), data.args!.getInt8(1)]; notifySemanticsAction(data.id, data.action.index, actionArgs); // Await semantics disabled from embedder. @@ -159,20 +237,20 @@ void a11y_main() async { // ignore: non_constant_identifier_names @pragma('vm:entry-point') void platform_messages_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { - callback(data); + window.onPlatformMessage = (String name, ByteData? data, PlatformMessageResponseCallback? callback) { + callback!(data); }; signalNativeTest(); } @pragma('vm:entry-point') void platform_messages_no_response() { - window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { - var list = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + window.onPlatformMessage = (String name, ByteData? data, PlatformMessageResponseCallback? callback) { + var list = data!.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); signalNativeMessage(utf8.decode(list)); // This does nothing because no one is listening on the other side. But complete the loop anyway // to make sure all null checking on response handles in the engine is in place. - callback(data); + callback!(data); }; signalNativeTest(); } @@ -180,10 +258,10 @@ void platform_messages_no_response() { @pragma('vm:entry-point') void null_platform_messages() { window.onPlatformMessage = - (String name, ByteData data, PlatformMessageResponseCallback callback) { + (String name, ByteData? data, PlatformMessageResponseCallback? callback) { // This checks if the platform_message null data is converted to Flutter null. signalNativeMessage((null == data).toString()); - callback(data); + callback!(data); }; signalNativeTest(); } @@ -507,7 +585,7 @@ void can_display_platform_view_with_pixel_ratio() { @pragma('vm:entry-point') void can_receive_locale_updates() { window.onLocaleChanged = (){ - signalNativeCount(window.locales.length); + signalNativeCount(window.locales!.length); }; signalNativeTest(); } diff --git a/shell/platform/fuchsia/flutter/component.cc b/shell/platform/fuchsia/flutter/component.cc index d60dff5c8d111..04cd15bfc6cb0 100644 --- a/shell/platform/fuchsia/flutter/component.cc +++ b/shell/platform/fuchsia/flutter/component.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "component.h" @@ -606,8 +607,8 @@ void Application::OnEngineTerminate(const Engine* shell_holder) { // terminate when the last shell goes away. The error code return to the // application controller will be the last isolate that had an error. auto return_code = shell_holder->GetEngineReturnCode(); - if (return_code.first) { - last_return_code_ = return_code; + if (return_code.has_value()) { + last_return_code_ = {true, return_code.value()}; } shell_holders_.erase(found); diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index c0f52142c934a..0ef28c3566965 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "engine.h" @@ -406,11 +407,11 @@ Engine::~Engine() { } } -std::pair Engine::GetEngineReturnCode() const { - std::pair code(false, 0); +std::optional Engine::GetEngineReturnCode() const { if (!shell_) { - return code; + return std::nullopt; } + std::optional code; fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( shell_->GetTaskRunners().GetUITaskRunner(), diff --git a/shell/platform/fuchsia/flutter/engine.h b/shell/platform/fuchsia/flutter/engine.h index 29868c731c45b..a1335c59c78e5 100644 --- a/shell/platform/fuchsia/flutter/engine.h +++ b/shell/platform/fuchsia/flutter/engine.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_ENGINE_H_ #define FLUTTER_SHELL_PLATFORM_FUCHSIA_ENGINE_H_ +#include + #include #include #include @@ -56,7 +58,7 @@ class Engine final { // Returns the Dart return code for the root isolate if one is present. This // call is thread safe and synchronous. This call must be made infrequently. - std::pair GetEngineReturnCode() const; + std::optional GetEngineReturnCode() const; #if !defined(DART_PRODUCT) void WriteProfileToTrace() const; diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index 1515f2e5afb15..8ba08719bc77e 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -1,9 +1,12 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/testing/dart_isolate_runner.h" +#include "flutter/runtime/isolate_configuration.h" + namespace flutter { namespace testing { AutoIsolateShutdown::AutoIsolateShutdown(std::shared_ptr isolate, @@ -47,55 +50,29 @@ AutoIsolateShutdown::~AutoIsolateShutdown() { return true; } -void RunDartCodeInIsolate(DartVMRef& vm_ref, - std::unique_ptr& result, - const Settings& settings, - const TaskRunners& task_runners, - std::string entrypoint, - const std::vector& args, - const std::string& fixtures_path, - fml::WeakPtr io_manager) { +std::unique_ptr RunDartCodeInIsolateOnUITaskRunner( + DartVMRef& vm_ref, + const Settings& p_settings, + const TaskRunners& task_runners, + std::string entrypoint, + const std::vector& args, + const std::string& fixtures_path, + fml::WeakPtr io_manager) { FML_CHECK(task_runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); if (!vm_ref) { - return; + return nullptr; } auto vm_data = vm_ref.GetVMData(); if (!vm_data) { - return; + return nullptr; } - auto weak_isolate = DartIsolate::CreateRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - io_manager, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint - nullptr, // flags - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback - ); - - auto root_isolate = std::make_unique( - weak_isolate.lock(), task_runners.GetUITaskRunner()); - - if (!root_isolate->IsValid()) { - FML_LOG(ERROR) << "Could not create isolate."; - return; - } + auto settings = p_settings; - if (root_isolate->get()->GetPhase() != DartIsolate::Phase::LibrariesSetup) { - FML_LOG(ERROR) << "Created isolate is in unexpected phase."; - return; - } + settings.dart_entrypoint_args = args; if (!DartVM::IsRunningPrecompiledCode()) { auto kernel_file_path = @@ -103,7 +80,7 @@ void RunDartCodeInIsolate(DartVMRef& vm_ref, if (!fml::IsFile(kernel_file_path)) { FML_LOG(ERROR) << "Could not locate kernel file."; - return; + return nullptr; } auto kernel_file = fml::OpenFile(kernel_file_path.c_str(), false, @@ -111,46 +88,56 @@ void RunDartCodeInIsolate(DartVMRef& vm_ref, if (!kernel_file.is_valid()) { FML_LOG(ERROR) << "Kernel file descriptor was invalid."; - return; + return nullptr; } auto kernel_mapping = std::make_unique(kernel_file); if (kernel_mapping->GetMapping() == nullptr) { FML_LOG(ERROR) << "Could not setup kernel mapping."; - return; - } - - if (!root_isolate->get()->PrepareForRunningFromKernel( - std::move(kernel_mapping))) { - FML_LOG(ERROR) - << "Could not prepare to run the isolate from the kernel file."; - return; + return nullptr; } - } else { - if (!root_isolate->get()->PrepareForRunningFromPrecompiledCode()) { - FML_LOG(ERROR) - << "Could not prepare to run the isolate from precompiled code."; - return; - } - } - if (root_isolate->get()->GetPhase() != DartIsolate::Phase::Ready) { - FML_LOG(ERROR) << "Isolate is in unexpected phase."; - return; + settings.application_kernels = fml::MakeCopyable( + [kernel_mapping = std::move(kernel_mapping)]() mutable -> Mappings { + Mappings mappings; + mappings.emplace_back(std::move(kernel_mapping)); + return mappings; + }); } - if (!root_isolate->get()->Run(entrypoint, args, - settings.root_isolate_create_callback)) { - FML_LOG(ERROR) << "Could not run the method \"" << entrypoint - << "\" in the isolate."; - return; + auto isolate_configuration = + IsolateConfiguration::InferFromSettings(settings); + + auto isolate = + DartIsolate::CreateRunningRootIsolate( + settings, // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + io_manager, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + entrypoint.c_str(), // advisory entrypoint + DartIsolate::Flags{}, // flags + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback, // isolate shutdown callback + entrypoint, // entrypoint + std::nullopt, // library + std::move(isolate_configuration) // isolate configuration + ) + .lock(); + + if (!isolate) { + FML_LOG(ERROR) << "Could not create running isolate."; + return nullptr; } - root_isolate->get()->AddIsolateShutdownCallback( - settings.root_isolate_shutdown_callback); - - result = std::move(root_isolate); + return std::make_unique(isolate, + task_runners.GetUITaskRunner()); } std::unique_ptr RunDartCodeInIsolate( @@ -165,8 +152,9 @@ std::unique_ptr RunDartCodeInIsolate( fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( task_runners.GetUITaskRunner(), fml::MakeCopyable([&]() mutable { - RunDartCodeInIsolate(vm_ref, result, settings, task_runners, entrypoint, - args, fixtures_path, io_manager); + result = RunDartCodeInIsolateOnUITaskRunner( + vm_ref, settings, task_runners, entrypoint, args, fixtures_path, + io_manager); latch.Signal(); })); latch.Wait(); diff --git a/testing/fixture_test.cc b/testing/fixture_test.cc index 06cb2227fdfeb..d001a8b35ab16 100644 --- a/testing/fixture_test.cc +++ b/testing/fixture_test.cc @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT #include "flutter/testing/fixture_test.h" @@ -40,10 +41,16 @@ void FixtureTest::SetSnapshotsAndAssets(Settings& settings) { if (DartVM::IsRunningPrecompiledCode()) { FML_CHECK(PrepareSettingsForAOTWithSymbols(settings, aot_symbols_)); } else { - settings.application_kernels = [this]() { + settings.application_kernels = [this]() -> Mappings { std::vector> kernel_mappings; - kernel_mappings.emplace_back( - fml::FileMapping::CreateReadOnly(assets_dir_, "kernel_blob.bin")); + auto kernel_mapping = + fml::FileMapping::CreateReadOnly(assets_dir_, "kernel_blob.bin"); + if (!kernel_mapping || !kernel_mapping->IsValid()) { + FML_LOG(ERROR) << "Could not find kernel blob for test fixture not " + "running in precompiled mode."; + return kernel_mappings; + } + kernel_mappings.emplace_back(std::move(kernel_mapping)); return kernel_mappings; }; } diff --git a/testing/testing.gni b/testing/testing.gni index 488310a05032a..2e0bf8bf78963 100644 --- a/testing/testing.gni +++ b/testing/testing.gni @@ -80,6 +80,7 @@ template("dart_snapshot_kernel") { rebase_path("$root_out_dir/flutter_patched_sdk"), "--target", "flutter", + "--enable-experiment=non-nullable", "--output-dill", rebase_path(invoker.dart_kernel, root_out_dir), "--depfile", diff --git a/third_party/tonic/BUILD.gn b/third_party/tonic/BUILD.gn index eb0136d54bd59..f5bedda8dab0d 100644 --- a/third_party/tonic/BUILD.gn +++ b/third_party/tonic/BUILD.gn @@ -11,6 +11,12 @@ config("config") { source_set("tonic") { sources = [ + "common/build_config.h", + "common/log.cc", + "common/log.h", + "common/macros.h", + "converter/dart_converter.cc", + "converter/dart_converter.h", "dart_args.h", "dart_binding_macros.h", "dart_class_library.cc", @@ -32,17 +38,53 @@ source_set("tonic") { "dart_wrappable.cc", "dart_wrappable.h", "dart_wrapper_info.h", - ] + "file_loader/file_loader.cc", + "file_loader/file_loader.h", + "logging/dart_error.cc", + "logging/dart_error.h", + "logging/dart_invoke.cc", + "logging/dart_invoke.h", + "scopes/dart_api_scope.h", + "scopes/dart_isolate_scope.cc", + "scopes/dart_isolate_scope.h", + "typed_data/dart_byte_data.cc", + "typed_data/dart_byte_data.h", - public_deps = [ - "common", - "converter", - "file_loader", - "logging", - "scopes", - "typed_data", - "//third_party/dart/runtime:dart_api", + # Deprecated. + "filesystem/filesystem/eintr_wrapper.h", + "filesystem/filesystem/file.cc", + "filesystem/filesystem/file.h", + "filesystem/filesystem/path.h", + "filesystem/filesystem/portable_unistd.h", + "parsers/packages_map.cc", + "parsers/packages_map.h", + "typed_data/float32_list.h", + "typed_data/float64_list.h", + "typed_data/int32_list.h", + "typed_data/typed_list.cc", + "typed_data/typed_list.h", + "typed_data/uint16_list.h", + "typed_data/uint8_list.h", ] + if (is_win) { + sources += [ + "file_loader/file_loader_win.cc", + "filesystem/filesystem/path_win.cc", + ] + } else if (is_fuchsia) { + sources += [ + "file_loader/file_loader_fuchsia.cc", + "filesystem/filesystem/path_posix.cc", + ] + } else { + sources += [ + "file_loader/file_loader_posix.cc", + "filesystem/filesystem/path_posix.cc", + ] + } + + public_deps = [ "//third_party/dart/runtime:dart_api" ] + public_configs = [ ":config" ] } diff --git a/third_party/tonic/common/BUILD.gn b/third_party/tonic/common/BUILD.gn deleted file mode 100644 index 2af823348c554..0000000000000 --- a/third_party/tonic/common/BUILD.gn +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("common") { - visibility = [ "../*" ] - - public_configs = [ "../:config" ] - - sources = [ - "build_config.h", - "log.cc", - "log.h", - "macros.h", - ] -} diff --git a/third_party/tonic/converter/BUILD.gn b/third_party/tonic/converter/BUILD.gn deleted file mode 100644 index 1cde8c17e5ff4..0000000000000 --- a/third_party/tonic/converter/BUILD.gn +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("converter") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - sources = [ - "dart_converter.cc", - "dart_converter.h", - ] - - deps = [ "../common" ] - - public_deps = [ "//third_party/dart/runtime:dart_api" ] -} diff --git a/third_party/tonic/converter/dart_converter.h b/third_party/tonic/converter/dart_converter.h index 6693615a2d98d..2ba75a9c96aa9 100644 --- a/third_party/tonic/converter/dart_converter.h +++ b/third_party/tonic/converter/dart_converter.h @@ -6,10 +6,12 @@ #define LIB_CONVERTER_TONIC_DART_CONVERTER_H_ #include +#include #include #include "third_party/dart/runtime/include/dart_api.h" #include "tonic/common/macros.h" +#include "tonic/logging/dart_error.h" namespace tonic { @@ -279,15 +281,80 @@ struct DartConverter { //////////////////////////////////////////////////////////////////////////////// // Collections +inline Dart_Handle LookupNonNullableType(const std::string& library_name, + const std::string& type_name) { + auto library = + Dart_LookupLibrary(DartConverter::ToDart(library_name)); + if (LogIfError(library)) { + return library; + } + auto type_string = DartConverter::ToDart(type_name); + if (LogIfError(type_string)) { + return type_string; + } + auto type = Dart_GetNonNullableType(library, type_string, 0, nullptr); + if (LogIfError(type)) { + return type; + } + return type; +} + +template ::value, int> = 0> +Dart_Handle ToDartTypeHandle() { + return LookupNonNullableType("dart:core", "String"); +} + +template ::value, int> = 0> +Dart_Handle ToDartTypeHandle() { + return LookupNonNullableType("dart:core", "int"); +} + +template ::value, int> = 0> +Dart_Handle ToDartTypeHandle() { + return LookupNonNullableType("dart:core", "double"); +} + +template +Dart_Handle CreateZeroInitializedDartObject( + Dart_Handle type_handle_or_null = ::Dart_Null()) { + if constexpr (std::is_same::value) { + return ::Dart_EmptyString(); + } else if constexpr (std::is_integral::value) { + return ::Dart_NewIntegerFromUint64(0u); + } else if constexpr (std::is_floating_point::value) { + return ::Dart_NewDouble(0.0); + } else { + auto object = ::Dart_New(type_handle_or_null, ::Dart_Null(), 0, nullptr); + LogIfError(object); + return object; + } + return ::Dart_Null(); +} + template struct DartListFactory { - static Dart_Handle NewList(intptr_t length) { return Dart_NewList(length); } -}; - -template <> -struct DartListFactory { - static Dart_Handle NewList(intptr_t length) { - return Dart_NewListOf(Dart_CoreType_String, length); + static Dart_Handle NewList(Dart_Handle type_handle, intptr_t length) { + bool is_nullable = false; + auto is_nullable_handle = ::Dart_IsNullableType(type_handle, &is_nullable); + if (LogIfError(is_nullable_handle)) { + return is_nullable_handle; + } + if (is_nullable) { + auto list = ::Dart_NewListOfType(type_handle, length); + LogIfError(list); + return list; + } else { + auto sentinel = CreateZeroInitializedDartObject(type_handle); + if (LogIfError(sentinel)) { + return sentinel; + } + auto list = ::Dart_NewListOfTypeFilled(type_handle, sentinel, length); + LogIfError(list); + return list; + } + return ::Dart_Null(); } }; @@ -297,7 +364,8 @@ struct DartConverter> { using ConverterType = typename DartConverterTypes::ConverterType; static Dart_Handle ToDart(const std::vector& val) { - Dart_Handle list = DartListFactory::NewList(val.size()); + Dart_Handle list = DartListFactory::NewList( + ToDartTypeHandle(), val.size()); if (Dart_IsError(list)) return list; for (size_t i = 0; i < val.size(); i++) { diff --git a/third_party/tonic/dart_class_provider.cc b/third_party/tonic/dart_class_provider.cc index 2d9ca00642fbd..590b0b959a224 100644 --- a/third_party/tonic/dart_class_provider.cc +++ b/third_party/tonic/dart_class_provider.cc @@ -20,7 +20,7 @@ DartClassProvider::~DartClassProvider() {} Dart_Handle DartClassProvider::GetClassByName(const char* class_name) { Dart_Handle name_handle = ToDart(class_name); Dart_Handle class_handle = - Dart_GetType(library_.value(), name_handle, 0, nullptr); + Dart_GetNonNullableType(library_.value(), name_handle, 0, nullptr); TONIC_DCHECK(!Dart_IsError(class_handle)); return class_handle; } diff --git a/third_party/tonic/file_loader/BUILD.gn b/third_party/tonic/file_loader/BUILD.gn deleted file mode 100644 index e350a447c438f..0000000000000 --- a/third_party/tonic/file_loader/BUILD.gn +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("file_loader") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - sources = [ - "file_loader.cc", - "file_loader.h", - ] - - if (is_win) { - sources += [ "file_loader_win.cc" ] - } else if (is_fuchsia) { - sources += [ "file_loader_fuchsia.cc" ] - } else { - sources += [ "file_loader_posix.cc" ] - } - - deps = [ - "../common", - "../converter", - "../filesystem", - "../parsers", - "//third_party/dart/runtime:dart_api", - ] -} diff --git a/third_party/tonic/file_loader/file_loader.cc b/third_party/tonic/file_loader/file_loader.cc index 32019b2de42cd..4a9e1791794ac 100644 --- a/third_party/tonic/file_loader/file_loader.cc +++ b/third_party/tonic/file_loader/file_loader.cc @@ -8,11 +8,11 @@ #include #include -#include "filesystem/file.h" -#include "filesystem/path.h" -#include "filesystem/portable_unistd.h" #include "tonic/common/macros.h" #include "tonic/converter/dart_converter.h" +#include "tonic/filesystem/filesystem/file.h" +#include "tonic/filesystem/filesystem/path.h" +#include "tonic/filesystem/filesystem/portable_unistd.h" #include "tonic/parsers/packages_map.h" namespace tonic { diff --git a/third_party/tonic/file_loader/file_loader_fuchsia.cc b/third_party/tonic/file_loader/file_loader_fuchsia.cc index 794a298b49124..359f88df39930 100644 --- a/third_party/tonic/file_loader/file_loader_fuchsia.cc +++ b/third_party/tonic/file_loader/file_loader_fuchsia.cc @@ -13,10 +13,10 @@ #include #include -#include "filesystem/file.h" -#include "filesystem/path.h" #include "tonic/common/macros.h" #include "tonic/converter/dart_converter.h" +#include "tonic/filesystem/filesystem/file.h" +#include "tonic/filesystem/filesystem/path.h" #include "tonic/parsers/packages_map.h" namespace tonic { diff --git a/third_party/tonic/file_loader/file_loader_posix.cc b/third_party/tonic/file_loader/file_loader_posix.cc index 13f3bd3caebd3..f5167f6e7f5ca 100644 --- a/third_party/tonic/file_loader/file_loader_posix.cc +++ b/third_party/tonic/file_loader/file_loader_posix.cc @@ -8,10 +8,10 @@ #include #include -#include "filesystem/file.h" -#include "filesystem/path.h" #include "tonic/common/macros.h" #include "tonic/converter/dart_converter.h" +#include "tonic/filesystem/filesystem/file.h" +#include "tonic/filesystem/filesystem/path.h" #include "tonic/parsers/packages_map.h" namespace tonic { diff --git a/third_party/tonic/file_loader/file_loader_win.cc b/third_party/tonic/file_loader/file_loader_win.cc index e79e86f1a0439..f312a0ac1dce5 100644 --- a/third_party/tonic/file_loader/file_loader_win.cc +++ b/third_party/tonic/file_loader/file_loader_win.cc @@ -8,9 +8,9 @@ #include #include -#include "filesystem/file.h" -#include "filesystem/path.h" #include "tonic/common/macros.h" +#include "tonic/filesystem/filesystem/file.h" +#include "tonic/filesystem/filesystem/path.h" #include "tonic/parsers/packages_map.h" namespace tonic { diff --git a/third_party/tonic/filesystem/filesystem/BUILD.gn b/third_party/tonic/filesystem/filesystem/BUILD.gn deleted file mode 100644 index 197a35e907ab4..0000000000000 --- a/third_party/tonic/filesystem/filesystem/BUILD.gn +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -config("filesystem_config") { - visibility = [ ":*" ] - - # We want callers to refer to headers in this folders using the "files" - # prefix. - include_dirs = [ ".." ] -} - -source_set("filesystem") { - visibility = [ "../*" ] - - configs += [ "../../:config" ] - - sources = [ - "eintr_wrapper.h", - "file.cc", - "file.h", - "path.h", - "portable_unistd.h", - ] - - if (is_win) { - sources += [ "path_win.cc" ] - } else { - sources += [ "path_posix.cc" ] - } - - deps = [ "../../common" ] - - public_configs = [ ":filesystem_config" ] -} diff --git a/third_party/tonic/filesystem/filesystem/file.cc b/third_party/tonic/filesystem/filesystem/file.cc index 0bc6eed02b0e8..c8436c89054a3 100644 --- a/third_party/tonic/filesystem/filesystem/file.cc +++ b/third_party/tonic/filesystem/filesystem/file.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "filesystem/file.h" +#include "tonic/filesystem/filesystem/file.h" #include #include @@ -23,8 +23,8 @@ typedef SSIZE_T ssize_t; #endif -#include "filesystem/eintr_wrapper.h" -#include "filesystem/portable_unistd.h" +#include "tonic/filesystem/filesystem/eintr_wrapper.h" +#include "tonic/filesystem/filesystem/portable_unistd.h" namespace filesystem { namespace { diff --git a/third_party/tonic/filesystem/filesystem/file.h b/third_party/tonic/filesystem/filesystem/file.h index ee206850df50c..fc6e3bc166ed7 100644 --- a/third_party/tonic/filesystem/filesystem/file.h +++ b/third_party/tonic/filesystem/filesystem/file.h @@ -8,8 +8,8 @@ #include #include -#include "filesystem/eintr_wrapper.h" -#include "filesystem/portable_unistd.h" +#include "tonic/filesystem/filesystem/eintr_wrapper.h" +#include "tonic/filesystem/filesystem/portable_unistd.h" namespace filesystem { diff --git a/third_party/tonic/filesystem/filesystem/path_posix.cc b/third_party/tonic/filesystem/filesystem/path_posix.cc index ac4b6ac69b3f6..bdce0852bf87c 100644 --- a/third_party/tonic/filesystem/filesystem/path_posix.cc +++ b/third_party/tonic/filesystem/filesystem/path_posix.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "filesystem/path.h" +#include "tonic/filesystem/filesystem/path.h" #include #include @@ -17,8 +17,8 @@ #include #include -#include "filesystem/portable_unistd.h" #include "tonic/common/build_config.h" +#include "tonic/filesystem/filesystem/portable_unistd.h" namespace filesystem { namespace { diff --git a/third_party/tonic/filesystem/filesystem/path_win.cc b/third_party/tonic/filesystem/filesystem/path_win.cc index 04a5e681025d6..9b74f07a3d0bc 100644 --- a/third_party/tonic/filesystem/filesystem/path_win.cc +++ b/third_party/tonic/filesystem/filesystem/path_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "filesystem/path.h" +#include "tonic/filesystem/filesystem/path.h" #include diff --git a/third_party/tonic/logging/BUILD.gn b/third_party/tonic/logging/BUILD.gn deleted file mode 100644 index cee90880f6070..0000000000000 --- a/third_party/tonic/logging/BUILD.gn +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("logging") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - sources = [ - "dart_error.cc", - "dart_error.h", - "dart_invoke.cc", - "dart_invoke.h", - ] - - deps = [ - "../common", - "../converter", - ] - - public_deps = [ "//third_party/dart/runtime:dart_api" ] -} diff --git a/third_party/tonic/parsers/BUILD.gn b/third_party/tonic/parsers/BUILD.gn deleted file mode 100644 index b03ae32a1363b..0000000000000 --- a/third_party/tonic/parsers/BUILD.gn +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("parsers") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - deps = [ "../common" ] - - sources = [ - "packages_map.cc", - "packages_map.h", - ] -} diff --git a/third_party/tonic/scopes/BUILD.gn b/third_party/tonic/scopes/BUILD.gn deleted file mode 100644 index af2a3a9fc6f5c..0000000000000 --- a/third_party/tonic/scopes/BUILD.gn +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("scopes") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - sources = [ - "dart_api_scope.h", - "dart_isolate_scope.cc", - "dart_isolate_scope.h", - ] - - deps = [ "../common" ] - - public_deps = [ "//third_party/dart/runtime:dart_api" ] -} diff --git a/third_party/tonic/typed_data/BUILD.gn b/third_party/tonic/typed_data/BUILD.gn deleted file mode 100644 index cdc58d43afa62..0000000000000 --- a/third_party/tonic/typed_data/BUILD.gn +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("typed_data") { - visibility = [ "../*" ] - - configs += [ "../:config" ] - - sources = [ - "dart_byte_data.cc", - "dart_byte_data.h", - "typed_list.cc", - "typed_list.h", - - # Deprecated. - "float32_list.h", - "float64_list.h", - "int32_list.h", - "uint16_list.h", - "uint8_list.h", - ] - - deps = [ "../common" ] - - public_deps = [ - "../converter", - "../logging", - "//third_party/dart/runtime:dart_api", - ] -} diff --git a/tools/font-subset/BUILD.gn b/tools/font-subset/BUILD.gn index 54c5f75e3a101..42341c09f7614 100644 --- a/tools/font-subset/BUILD.gn +++ b/tools/font-subset/BUILD.gn @@ -12,7 +12,7 @@ executable("font-subset") { libs = [] if (is_mac) { - libs += [ + frameworks = [ "Foundation.framework", "CoreGraphics.framework", "CoreText.framework", From 787b9c537606e58d8a6cb993b295485257fcd72c Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 16 Oct 2020 15:03:26 -0700 Subject: [PATCH 148/219] Break the reference cycle between the surface factory and the external view embedder (#21918) See https://github.com/flutter/flutter/issues/68315 --- .../platform/android/platform_view_android.cc | 32 +++++++++---------- .../platform/android/platform_view_android.h | 10 +++--- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 8ecba33be57f4..0fc9af5c59b51 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -30,31 +30,28 @@ namespace flutter { AndroidSurfaceFactoryImpl::AndroidSurfaceFactoryImpl( std::shared_ptr context, - std::shared_ptr jni_facade) { - android_context_ = context; - jni_facade_ = jni_facade; -} + std::shared_ptr jni_facade, + std::weak_ptr external_view_embedder) + : android_context_(context), jni_facade_(jni_facade), + external_view_embedder_(external_view_embedder) {} AndroidSurfaceFactoryImpl::~AndroidSurfaceFactoryImpl() = default; -void AndroidSurfaceFactoryImpl::SetExternalViewEmbedder( - std::shared_ptr external_view_embedder) { - external_view_embedder_ = external_view_embedder; -} - std::unique_ptr AndroidSurfaceFactoryImpl::CreateSurface() { - FML_CHECK(external_view_embedder_); + std::shared_ptr external_view_embedder = + external_view_embedder_.lock(); + FML_CHECK(external_view_embedder); switch (android_context_->RenderingApi()) { case AndroidRenderingAPI::kSoftware: return std::make_unique( - android_context_, jni_facade_, external_view_embedder_); + android_context_, jni_facade_, external_view_embedder); case AndroidRenderingAPI::kOpenGLES: return std::make_unique(android_context_, jni_facade_, - external_view_embedder_); + external_view_embedder); case AndroidRenderingAPI::kVulkan: #if SHELL_ENABLE_VULKAN return std::make_unique( - android_context_, jni_facade_, external_view_embedder_); + android_context_, jni_facade_, external_view_embedder); #endif // SHELL_ENABLE_VULKAN default: return nullptr; @@ -87,11 +84,12 @@ PlatformViewAndroid::PlatformViewAndroid( FML_CHECK(android_context && android_context->IsValid()) << "Could not create an Android context."; + external_view_embedder_ = + std::make_shared(android_context, jni_facade, + surface_factory_); surface_factory_ = - std::make_shared(android_context, jni_facade); - surface_factory_->SetExternalViewEmbedder( - std::make_shared(android_context, jni_facade, - surface_factory_)); + std::make_shared(android_context, jni_facade, + external_view_embedder_); android_surface_ = surface_factory_->CreateSurface(); FML_CHECK(android_surface_ && android_surface_->IsValid()) diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 317784c729a96..2ae51464df3c2 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -25,20 +25,17 @@ namespace flutter { class AndroidSurfaceFactoryImpl : public AndroidSurfaceFactory { public: AndroidSurfaceFactoryImpl(std::shared_ptr context, - std::shared_ptr jni_facade); + std::shared_ptr jni_facade, + std::weak_ptr external_view_embedder); ~AndroidSurfaceFactoryImpl() override; std::unique_ptr CreateSurface() override; - void SetExternalViewEmbedder( - std::shared_ptr external_view_embedder); - private: std::shared_ptr android_context_; std::shared_ptr jni_facade_; - std::shared_ptr external_view_embedder_; - + std::weak_ptr external_view_embedder_; }; class PlatformViewAndroid final : public PlatformView { @@ -99,6 +96,7 @@ class PlatformViewAndroid final : public PlatformView { private: const std::shared_ptr jni_facade_; + std::shared_ptr external_view_embedder_; std::shared_ptr surface_factory_; PlatformViewAndroidDelegate platform_view_android_delegate_; From 9b752790f4d25ab238576c719208d7a38c6293f3 Mon Sep 17 00:00:00 2001 From: Ren You Date: Fri, 16 Oct 2020 18:25:13 -0400 Subject: [PATCH 149/219] Revert "[fuchsia] External view embedder will be shared with platform view (#21850)" (#21924) This reverts commit 1bc025d6cbf1136c2e96c6fb041d24202ea47ff0. --- flow/layers/child_scene_layer.cc | 4 +- flow/layers/child_scene_layer.h | 2 +- flow/layers/clip_path_layer.cc | 2 +- flow/layers/clip_path_layer.h | 2 +- flow/layers/clip_rect_layer.cc | 2 +- flow/layers/clip_rect_layer.h | 2 +- flow/layers/clip_rrect_layer.cc | 2 +- flow/layers/clip_rrect_layer.h | 2 +- flow/layers/container_layer.cc | 7 +- flow/layers/container_layer.h | 4 +- flow/layers/fuchsia_layer_unittests.cc | 12 ++-- flow/layers/layer.cc | 4 +- flow/layers/layer.h | 2 +- flow/layers/layer_tree.cc | 6 +- flow/layers/layer_tree.h | 2 +- flow/layers/opacity_layer.cc | 8 +-- flow/layers/opacity_layer.h | 2 +- flow/layers/platform_view_layer.cc | 5 +- flow/layers/platform_view_layer.h | 2 +- flow/layers/transform_layer.cc | 2 +- flow/layers/transform_layer.h | 2 +- flow/scene_update_context.cc | 70 +++++++++---------- flow/scene_update_context.h | 16 ++--- .../fuchsia/flutter/compositor_context.cc | 8 +-- .../fuchsia/flutter/compositor_context.h | 9 ++- shell/platform/fuchsia/flutter/engine.cc | 36 ++++------ shell/platform/fuchsia/flutter/engine.h | 6 +- .../platform/fuchsia/flutter/platform_view.cc | 3 - .../platform/fuchsia/flutter/platform_view.h | 4 -- .../fuchsia/flutter/platform_view_unittest.cc | 17 +---- shell/platform/fuchsia/flutter/surface.cc | 4 +- shell/platform/fuchsia/flutter/surface.h | 4 +- 32 files changed, 107 insertions(+), 146 deletions(-) diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index f15173c064d51..9ba5eff3c5021 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -26,10 +26,10 @@ void ChildSceneLayer::Paint(PaintContext& context) const { FML_NOTREACHED(); } -void ChildSceneLayer::UpdateScene(std::shared_ptr context) { +void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "ChildSceneLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); - context->UpdateView(layer_id_, offset_, size_, hit_testable_); + context.UpdateView(layer_id_, offset_, size_, hit_testable_); } } // namespace flutter diff --git a/flow/layers/child_scene_layer.h b/flow/layers/child_scene_layer.h index 0ec7f3b16efb1..cad47db00462a 100644 --- a/flow/layers/child_scene_layer.h +++ b/flow/layers/child_scene_layer.h @@ -26,7 +26,7 @@ class ChildSceneLayer : public Layer { void Paint(PaintContext& context) const override; - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; private: zx_koid_t layer_id_ = ZX_KOID_INVALID; diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 0b714392b60f5..038ead12a6805 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -41,7 +41,7 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipPathLayer::UpdateScene(std::shared_ptr context) { +void ClipPathLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "ClipPathLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index eac015db9e5d1..ce2e4e54d66e3 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -22,7 +22,7 @@ class ClipPathLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index cd4443b5c0198..fbd36dab65d05 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -36,7 +36,7 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipRectLayer::UpdateScene(std::shared_ptr context) { +void ClipRectLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "ClipRectLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index 9c57861d87fe2..b52a9512262d3 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -21,7 +21,7 @@ class ClipRectLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 32ed1a39096ab..b3ff833c4a4f3 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -37,7 +37,7 @@ void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void ClipRRectLayer::UpdateScene(std::shared_ptr context) { +void ClipRRectLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "ClipRRectLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 1422615c5c6b3..314886a1fed30 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -22,7 +22,7 @@ class ClipRRectLayer : public ContainerLayer { } #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index a6ad98ba1aae4..825826b70835f 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -98,19 +98,18 @@ void ContainerLayer::CheckForChildLayerBelow(PrerollContext* context) { // All ContainerLayers make the check in PrerollChildren. } -void ContainerLayer::UpdateScene(std::shared_ptr context) { +void ContainerLayer::UpdateScene(SceneUpdateContext& context) { UpdateSceneChildren(context); } -void ContainerLayer::UpdateSceneChildren( - std::shared_ptr context) { +void ContainerLayer::UpdateSceneChildren(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); std::optional frame; if (child_layer_exists_below_) { frame.emplace( context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT, - SkScalarRoundToInt(context->alphaf() * 255), "flutter::ContainerLayer"); + SkScalarRoundToInt(context.alphaf() * 255), "flutter::ContainerLayer"); frame->AddPaintLayer(this); } diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index 11eae5f3fe123..0e5b27d7083fd 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -21,7 +21,7 @@ class ContainerLayer : public Layer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) void CheckForChildLayerBelow(PrerollContext* context) override; - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif const std::vector>& layers() const { return layers_; } @@ -33,7 +33,7 @@ class ContainerLayer : public Layer { void PaintChildren(PaintContext& context) const; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateSceneChildren(std::shared_ptr context); + void UpdateSceneChildren(SceneUpdateContext& context); #endif // Try to prepare the raster cache for a given layer. diff --git a/flow/layers/fuchsia_layer_unittests.cc b/flow/layers/fuchsia_layer_unittests.cc index 85278b8c60af5..df8f8e746ae00 100644 --- a/flow/layers/fuchsia_layer_unittests.cc +++ b/flow/layers/fuchsia_layer_unittests.cc @@ -261,7 +261,7 @@ struct TestContext { std::unique_ptr mock_session_wrapper; // SceneUpdateContext. - std::shared_ptr scene_update_context; + std::unique_ptr scene_update_context; // PrerollContext. MutatorsStack unused_stack; @@ -286,7 +286,7 @@ std::unique_ptr InitTest() { std::make_unique(std::move(session_ptr)); // Init SceneUpdateContext. - context->scene_update_context = std::make_shared( + context->scene_update_context = std::make_unique( "fuchsia_layer_unittest", fuchsia::ui::views::ViewToken(), scenic::ViewRefPair::New(), *(context->mock_session_wrapper)); @@ -410,7 +410,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_PhysicalShapeLayersAndChildSceneLayers) { // Create another frame to be the "real" root. Required because // UpdateScene() traversal expects there to already be a top node. - SceneUpdateContext::Frame frame(test_context->scene_update_context, + SceneUpdateContext::Frame frame(*(test_context->scene_update_context), SkRRect::MakeRect(SkRect::MakeWH(100, 100)), SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "fuchsia test root"); @@ -555,7 +555,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_PhysicalShapeLayersAndChildSceneLayers) { // Finally, UpdateScene(). The MockSession will check the emitted commands // against the list above. - root->UpdateScene(test_context->scene_update_context); + root->UpdateScene(*(test_context->scene_update_context)); test_context->mock_session_wrapper->Present(); @@ -626,7 +626,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_OpacityAndTransformLayer) { // Create another frame to be the "real" root. Required because // UpdateScene() traversal expects there to already be a top node. - SceneUpdateContext::Frame frame(test_context->scene_update_context, + SceneUpdateContext::Frame frame(*(test_context->scene_update_context), SkRRect::MakeRect(SkRect::MakeWH(100, 100)), SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "fuchsia test root"); @@ -737,7 +737,7 @@ TEST_F(FuchsiaLayerTest, DISABLED_OpacityAndTransformLayer) { // Finally, UpdateScene(). The MockSession will check the emitted // commands against the list above. - root->UpdateScene(test_context->scene_update_context); + root->UpdateScene(*(test_context->scene_update_context)); test_context->mock_session_wrapper->Present(); diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index 006f5cb84e916..a242f977cde29 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -67,13 +67,13 @@ void Layer::CheckForChildLayerBelow(PrerollContext* context) { } } -void Layer::UpdateScene(std::shared_ptr context) { +void Layer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); FML_DCHECK(child_layer_exists_below_); SceneUpdateContext::Frame frame( context, SkRRect::MakeRect(paint_bounds()), SK_ColorTRANSPARENT, - SkScalarRoundToInt(context->alphaf() * 255), "flutter::Layer"); + SkScalarRoundToInt(context.alphaf() * 255), "flutter::Layer"); frame.AddPaintLayer(this); } diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 0ce351db63b2f..eeffabacbb325 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -154,7 +154,7 @@ class Layer { #if defined(LEGACY_FUCHSIA_EMBEDDER) // Updates the system composited scene. - virtual void UpdateScene(std::shared_ptr context); + virtual void UpdateScene(SceneUpdateContext& context); virtual void CheckForChildLayerBelow(PrerollContext* context); #endif diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index d669591da1654..e0ac5157fc746 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -62,11 +62,11 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, } #if defined(LEGACY_FUCHSIA_EMBEDDER) -void LayerTree::UpdateScene(std::shared_ptr context) { +void LayerTree::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "LayerTree::UpdateScene"); // Reset for a new Scene. - context->Reset(); + context.Reset(); const float inv_dpr = 1.0f / device_pixel_ratio_; SceneUpdateContext::Transform transform(context, inv_dpr, inv_dpr, 1.0f); @@ -82,7 +82,7 @@ void LayerTree::UpdateScene(std::shared_ptr context) { if (root_layer_->needs_painting()) { frame.AddPaintLayer(root_layer_.get()); } - context->root_node().AddChild(transform.entity_node()); + context.root_node().AddChild(transform.entity_node()); } #endif diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index b59f278296381..b9cd8d37de7c0 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -32,7 +32,7 @@ class LayerTree { bool ignore_raster_cache = false); #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context); + void UpdateScene(SceneUpdateContext& context); #endif void Paint(CompositorContext::ScopedFrame& frame, diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 9e8001d6e3283..131b1d9f253b7 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -85,11 +85,11 @@ void OpacityLayer::Paint(PaintContext& context) const { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void OpacityLayer::UpdateScene(std::shared_ptr context) { - float saved_alpha = context->alphaf(); - context->set_alphaf(context->alphaf() * (alpha_ / 255.f)); +void OpacityLayer::UpdateScene(SceneUpdateContext& context) { + float saved_alpha = context.alphaf(); + context.set_alphaf(context.alphaf() * (alpha_ / 255.f)); ContainerLayer::UpdateScene(context); - context->set_alphaf(saved_alpha); + context.set_alphaf(saved_alpha); } #endif diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index 25f1c7944a829..ed5f0283ad356 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -32,7 +32,7 @@ class OpacityLayer : public MergedContainerLayer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 0971f93db777e..80514b5213e18 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -47,11 +47,10 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } #if defined(LEGACY_FUCHSIA_EMBEDDER) -void PlatformViewLayer::UpdateScene( - std::shared_ptr context) { +void PlatformViewLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "PlatformViewLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); - context->UpdateView(view_id_, offset_, size_); + context.UpdateView(view_id_, offset_, size_); } #endif diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index e9d6fac42eb23..a75c69689dabf 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -19,7 +19,7 @@ class PlatformViewLayer : public Layer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) // Updates the system composited scene. - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 3e26e525ac637..8fe5dd32e1e85 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -54,7 +54,7 @@ void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) -void TransformLayer::UpdateScene(std::shared_ptr context) { +void TransformLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "TransformLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index 7956e3906a680..63a212f9236de 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -20,7 +20,7 @@ class TransformLayer : public ContainerLayer { void Paint(PaintContext& context) const override; #if defined(LEGACY_FUCHSIA_EMBEDDER) - void UpdateScene(std::shared_ptr context) override; + void UpdateScene(SceneUpdateContext& context) override; #endif private: diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 141e2caafeb35..3edd9c4d9fe26 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -215,30 +215,29 @@ void SceneUpdateContext::DestroyView(int64_t view_id) { ViewHolder::Destroy(view_id); } -SceneUpdateContext::Entity::Entity(std::shared_ptr context) +SceneUpdateContext::Entity::Entity(SceneUpdateContext& context) : context_(context), - previous_entity_(context->top_entity_), - entity_node_(context->session_.get()) { - context->top_entity_ = this; + previous_entity_(context.top_entity_), + entity_node_(context.session_.get()) { + context.top_entity_ = this; } SceneUpdateContext::Entity::~Entity() { if (previous_entity_) { previous_entity_->embedder_node().AddChild(entity_node_); } else { - context_->root_node_.AddChild(entity_node_); + context_.root_node_.AddChild(entity_node_); } - FML_DCHECK(context_->top_entity_ == this); - context_->top_entity_ = previous_entity_; + FML_DCHECK(context_.top_entity_ == this); + context_.top_entity_ = previous_entity_; } -SceneUpdateContext::Transform::Transform( - std::shared_ptr context, - const SkMatrix& transform) +SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, + const SkMatrix& transform) : Entity(context), - previous_scale_x_(context->top_scale_x_), - previous_scale_y_(context->top_scale_y_) { + previous_scale_x_(context.top_scale_x_), + previous_scale_y_(context.top_scale_y_) { entity_node().SetLabel("flutter::Transform"); if (!transform.isIdentity()) { // TODO(SCN-192): The perspective and shear components in the matrix @@ -256,8 +255,8 @@ SceneUpdateContext::Transform::Transform( decomposition.scale().y, // 1.f // ); - context->top_scale_x_ *= decomposition.scale().x; - context->top_scale_y_ *= decomposition.scale().y; + context.top_scale_x_ *= decomposition.scale().x; + context.top_scale_y_ *= decomposition.scale().y; entity_node().SetRotation(decomposition.rotation().x, // decomposition.rotation().y, // @@ -268,46 +267,45 @@ SceneUpdateContext::Transform::Transform( } } -SceneUpdateContext::Transform::Transform( - std::shared_ptr context, - float scale_x, - float scale_y, - float scale_z) +SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, + float scale_x, + float scale_y, + float scale_z) : Entity(context), - previous_scale_x_(context->top_scale_x_), - previous_scale_y_(context->top_scale_y_) { + previous_scale_x_(context.top_scale_x_), + previous_scale_y_(context.top_scale_y_) { entity_node().SetLabel("flutter::Transform"); if (scale_x != 1.f || scale_y != 1.f || scale_z != 1.f) { entity_node().SetScale(scale_x, scale_y, scale_z); - context->top_scale_x_ *= scale_x; - context->top_scale_y_ *= scale_y; + context.top_scale_x_ *= scale_x; + context.top_scale_y_ *= scale_y; } } SceneUpdateContext::Transform::~Transform() { - context()->top_scale_x_ = previous_scale_x_; - context()->top_scale_y_ = previous_scale_y_; + context().top_scale_x_ = previous_scale_x_; + context().top_scale_y_ = previous_scale_y_; } -SceneUpdateContext::Frame::Frame(std::shared_ptr context, +SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, SkAlpha opacity, std::string label) : Entity(context), - previous_elevation_(context->top_elevation_), + previous_elevation_(context.top_elevation_), rrect_(rrect), color_(color), opacity_(opacity), - opacity_node_(context->session_.get()), + opacity_node_(context.session_.get()), paint_bounds_(SkRect::MakeEmpty()) { // Increment elevation trackers before calculating any local elevation. - // |UpdateView| can modify context->next_elevation_, which is why it is + // |UpdateView| can modify context.next_elevation_, which is why it is // neccesary to track this addtional state. - context->top_elevation_ += kScenicZElevationBetweenLayers; - context->next_elevation_ += kScenicZElevationBetweenLayers; + context.top_elevation_ += kScenicZElevationBetweenLayers; + context.next_elevation_ += kScenicZElevationBetweenLayers; - float local_elevation = context->next_elevation_ - previous_elevation_; + float local_elevation = context.next_elevation_ - previous_elevation_; entity_node().SetTranslation(0.f, 0.f, -local_elevation); entity_node().SetLabel(label); entity_node().AddChild(opacity_node_); @@ -320,11 +318,11 @@ SceneUpdateContext::Frame::Frame(std::shared_ptr context, } SceneUpdateContext::Frame::~Frame() { - context()->top_elevation_ = previous_elevation_; + context().top_elevation_ = previous_elevation_; // Add a part which represents the frame's geometry for clipping purposes - context()->CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_, - std::move(paint_layers_)); + context().CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_, + std::move(paint_layers_)); } void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { @@ -333,7 +331,7 @@ void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { paint_bounds_.join(layer->paint_bounds()); } -SceneUpdateContext::Clip::Clip(std::shared_ptr context, +SceneUpdateContext::Clip::Clip(SceneUpdateContext& context, const SkRect& shape_bounds) : Entity(context) { entity_node().SetLabel("flutter::Clip"); diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index d95513bc259b5..f15c59fa7f6ea 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -46,15 +46,15 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { public: class Entity { public: - Entity(std::shared_ptr context); + Entity(SceneUpdateContext& context); virtual ~Entity(); - std::shared_ptr context() { return context_; } + SceneUpdateContext& context() { return context_; } scenic::EntityNode& entity_node() { return entity_node_; } virtual scenic::ContainerNode& embedder_node() { return entity_node_; } private: - std::shared_ptr context_; + SceneUpdateContext& context_; Entity* const previous_entity_; scenic::EntityNode entity_node_; @@ -62,9 +62,8 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { class Transform : public Entity { public: - Transform(std::shared_ptr context, - const SkMatrix& transform); - Transform(std::shared_ptr context, + Transform(SceneUpdateContext& context, const SkMatrix& transform); + Transform(SceneUpdateContext& context, float scale_x, float scale_y, float scale_z); @@ -80,7 +79,7 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { // When layer is not nullptr, the frame is associated with a layer subtree // rooted with that layer. The frame may then create a surface that will be // retained for that layer. - Frame(std::shared_ptr context, + Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, SkAlpha opacity, @@ -105,8 +104,7 @@ class SceneUpdateContext : public flutter::ExternalViewEmbedder { class Clip : public Entity { public: - Clip(std::shared_ptr context, - const SkRect& shape_bounds); + Clip(SceneUpdateContext& context, const SkRect& shape_bounds); ~Clip() = default; }; diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc index 2f2a83eba345a..005a9cca0705b 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.cc +++ b/shell/platform/fuchsia/flutter/compositor_context.cc @@ -23,7 +23,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { fml::RefPtr raster_thread_merger, SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, - std::shared_ptr scene_update_context) + flutter::SceneUpdateContext& scene_update_context) : flutter::CompositorContext::ScopedFrame(context, surface_producer.gr_context(), canvas, @@ -39,7 +39,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { private: SessionConnection& session_connection_; VulkanSurfaceProducer& surface_producer_; - std::shared_ptr scene_update_context_; + flutter::SceneUpdateContext& scene_update_context_; flutter::RasterStatus Raster(flutter::LayerTree& layer_tree, bool ignore_raster_cache) override { @@ -64,7 +64,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { // Flush all pending session ops: create surfaces and enqueue session // Image ops for the frame's paint tasks, then Present. TRACE_EVENT0("flutter", "SessionPresent"); - frame_paint_tasks = scene_update_context_->GetPaintTasks(); + frame_paint_tasks = scene_update_context_.GetPaintTasks(); for (auto& task : frame_paint_tasks) { SkISize physical_size = SkISize::Make(layer_tree.device_pixel_ratio() * task.scale_x * @@ -142,7 +142,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { CompositorContext::CompositorContext( SessionConnection& session_connection, VulkanSurfaceProducer& surface_producer, - std::shared_ptr scene_update_context) + flutter::SceneUpdateContext& scene_update_context) : session_connection_(session_connection), surface_producer_(surface_producer), scene_update_context_(scene_update_context) {} diff --git a/shell/platform/fuchsia/flutter/compositor_context.h b/shell/platform/fuchsia/flutter/compositor_context.h index 4a943a499ad63..542e5d314fa71 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.h +++ b/shell/platform/fuchsia/flutter/compositor_context.h @@ -21,17 +21,16 @@ namespace flutter_runner { // Fuchsia. class CompositorContext final : public flutter::CompositorContext { public: - CompositorContext( - SessionConnection& session_connection, - VulkanSurfaceProducer& surface_producer, - std::shared_ptr scene_update_context); + CompositorContext(SessionConnection& session_connection, + VulkanSurfaceProducer& surface_producer, + flutter::SceneUpdateContext& scene_update_context); ~CompositorContext() override; private: SessionConnection& session_connection_; VulkanSurfaceProducer& surface_producer_; - std::shared_ptr scene_update_context_; + flutter::SceneUpdateContext& scene_update_context_; // |flutter::CompositorContext| std::unique_ptr AcquireFrame( diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index 0ef28c3566965..a7d85630c5bc9 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -9,7 +9,6 @@ #include #include "../runtime/dart/utils/files.h" -#include "flow/embedded_views.h" #include "flutter/common/task_runners.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/synchronization/waitable_event.h" @@ -139,18 +138,15 @@ Engine::Engine(Delegate& delegate, surface_producer_.emplace(session_connection_->get()); #if defined(LEGACY_FUCHSIA_EMBEDDER) if (use_legacy_renderer_) { - legacy_external_view_embedder_ = - std::make_shared( - thread_label_, std::move(view_token), - std::move(view_ref_pair), session_connection_.value()); + legacy_external_view_embedder_.emplace( + thread_label_, std::move(view_token), std::move(view_ref_pair), + session_connection_.value()); } else #endif { - external_view_embedder_ = - std::make_shared( - thread_label_, std::move(view_token), - std::move(view_ref_pair), session_connection_.value(), - surface_producer_.value()); + external_view_embedder_.emplace( + thread_label_, std::move(view_token), std::move(view_ref_pair), + session_connection_.value(), surface_producer_.value()); } })); @@ -210,7 +206,6 @@ Engine::Engine(Delegate& delegate, on_update_view_callback = std::move(on_update_view_callback), on_destroy_view_callback = std::move(on_destroy_view_callback), on_create_surface_callback = std::move(on_create_surface_callback), - external_view_embedder = GetExternalViewEmbedder(), vsync_offset = product_config.get_vsync_offset(), vsync_handle = vsync_event_.get()](flutter::Shell& shell) mutable { return std::make_unique( @@ -228,7 +223,6 @@ Engine::Engine(Delegate& delegate, std::move(on_update_view_callback), std::move(on_destroy_view_callback), std::move(on_create_surface_callback), - external_view_embedder, // external view embedder std::move(vsync_offset), // vsync offset vsync_handle); }); @@ -245,7 +239,7 @@ Engine::Engine(Delegate& delegate, auto compositor_context = std::make_unique( session_connection_.value(), surface_producer_.value(), - legacy_external_view_embedder_); + legacy_external_view_embedder_.value()); return std::make_unique( shell, std::move(compositor_context)); } else { @@ -584,28 +578,22 @@ void Engine::DestroyView(int64_t view_id) { } std::unique_ptr Engine::CreateSurface() { - return std::make_unique(thread_label_, GetExternalViewEmbedder(), - surface_producer_->gr_context()); -} - -std::shared_ptr -Engine::GetExternalViewEmbedder() { - std::shared_ptr external_view_embedder = - nullptr; + flutter::ExternalViewEmbedder* external_view_embedder = nullptr; #if defined(LEGACY_FUCHSIA_EMBEDDER) if (use_legacy_renderer_) { FML_CHECK(legacy_external_view_embedder_); - external_view_embedder = legacy_external_view_embedder_; + external_view_embedder = &legacy_external_view_embedder_.value(); } else #endif { FML_CHECK(external_view_embedder_); - external_view_embedder = external_view_embedder_; + external_view_embedder = &external_view_embedder_.value(); } FML_CHECK(external_view_embedder); - return external_view_embedder; + return std::make_unique(thread_label_, external_view_embedder, + surface_producer_->gr_context()); } #if !defined(DART_PRODUCT) diff --git a/shell/platform/fuchsia/flutter/engine.h b/shell/platform/fuchsia/flutter/engine.h index a1335c59c78e5..4fc29e7cfac10 100644 --- a/shell/platform/fuchsia/flutter/engine.h +++ b/shell/platform/fuchsia/flutter/engine.h @@ -16,7 +16,6 @@ #include #include -#include "flow/embedded_views.h" #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" #include "flutter/shell/common/shell.h" @@ -72,9 +71,9 @@ class Engine final { std::optional session_connection_; std::optional surface_producer_; - std::shared_ptr external_view_embedder_; + std::optional external_view_embedder_; #if defined(LEGACY_FUCHSIA_EMBEDDER) - std::shared_ptr legacy_external_view_embedder_; + std::optional legacy_external_view_embedder_; #endif std::unique_ptr isolate_configurator_; @@ -100,7 +99,6 @@ class Engine final { void CreateView(int64_t view_id, bool hit_testable, bool focusable); void UpdateView(int64_t view_id, bool hit_testable, bool focusable); void DestroyView(int64_t view_id); - std::shared_ptr GetExternalViewEmbedder(); std::unique_ptr CreateSurface(); diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index fa7e9a77c9357..db227ffd336de 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flow/embedded_views.h" #define RAPIDJSON_HAS_STDSTRING 1 #include "platform_view.h" @@ -67,7 +66,6 @@ PlatformView::PlatformView( OnUpdateView on_update_view_callback, OnDestroyView on_destroy_view_callback, OnCreateSurface on_create_surface_callback, - std::shared_ptr external_view_embedder, fml::TimeDelta vsync_offset, zx_handle_t vsync_event_handle) : flutter::PlatformView(delegate, std::move(task_runners)), @@ -82,7 +80,6 @@ PlatformView::PlatformView( on_update_view_callback_(std::move(on_update_view_callback)), on_destroy_view_callback_(std::move(on_destroy_view_callback)), on_create_surface_callback_(std::move(on_create_surface_callback)), - external_view_embedder_(external_view_embedder), ime_client_(this), vsync_offset_(std::move(vsync_offset)), vsync_event_handle_(vsync_event_handle) { diff --git a/shell/platform/fuchsia/flutter/platform_view.h b/shell/platform/fuchsia/flutter/platform_view.h index 9f29b3397bf09..a888298f9ef38 100644 --- a/shell/platform/fuchsia/flutter/platform_view.h +++ b/shell/platform/fuchsia/flutter/platform_view.h @@ -14,13 +14,11 @@ #include #include -#include "flow/embedded_views.h" #include "flutter/fml/macros.h" #include "flutter/fml/time/time_delta.h" #include "flutter/shell/common/platform_view.h" #include "accessibility_bridge.h" -#include "shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h" namespace flutter_runner { @@ -63,7 +61,6 @@ class PlatformView final : public flutter::PlatformView, OnUpdateView on_update_view_callback, OnDestroyView on_destroy_view_callback, OnCreateSurface on_create_surface_callback, - std::shared_ptr view_embedder, fml::TimeDelta vsync_offset, zx_handle_t vsync_event_handle); @@ -172,7 +169,6 @@ class PlatformView final : public flutter::PlatformView, OnUpdateView on_update_view_callback_; OnDestroyView on_destroy_view_callback_; OnCreateSurface on_create_surface_callback_; - std::shared_ptr external_view_embedder_; int current_text_input_client_ = 0; fidl::Binding ime_client_; diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 38f771ce257b9..63f010ed663c6 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -184,11 +184,10 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { // Test create surface callback function. sk_sp gr_context = GrDirectContext::MakeMock(nullptr, GrContextOptions()); - std::shared_ptr view_embedder = - std::make_shared(); + MockExternalViewEmbedder view_embedder; auto CreateSurfaceCallback = [&view_embedder, gr_context]() { return std::make_unique( - "PlatformViewTest", view_embedder, gr_context.get()); + "PlatformViewTest", &view_embedder, gr_context.get()); }; auto platform_view = flutter_runner::PlatformView( @@ -206,7 +205,6 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, CreateSurfaceCallback, // on_create_surface_callback, - view_embedder, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -215,7 +213,7 @@ TEST_F(PlatformViewTests, CreateSurfaceTest) { RunLoopUntilIdle(); EXPECT_EQ(gr_context.get(), delegate.surface()->GetContext()); - EXPECT_EQ(view_embedder.get(), delegate.surface()->GetExternalViewEmbedder()); + EXPECT_EQ(&view_embedder, delegate.surface()->GetExternalViewEmbedder()); } // This test makes sure that the PlatformView correctly registers Scenic @@ -250,7 +248,6 @@ TEST_F(PlatformViewTests, SetViewportMetrics) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -383,7 +380,6 @@ TEST_F(PlatformViewTests, ChangesAccessibilitySettings) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -434,7 +430,6 @@ TEST_F(PlatformViewTests, EnableWireframeTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -496,7 +491,6 @@ TEST_F(PlatformViewTests, CreateViewTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -560,7 +554,6 @@ TEST_F(PlatformViewTests, UpdateViewTest) { UpdateViewCallback, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -624,7 +617,6 @@ TEST_F(PlatformViewTests, DestroyViewTest) { nullptr, // on_update_view_callback, DestroyViewCallback, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -685,7 +677,6 @@ TEST_F(PlatformViewTests, ViewEventsTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -768,7 +759,6 @@ TEST_F(PlatformViewTests, RequestFocusTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); @@ -845,7 +835,6 @@ TEST_F(PlatformViewTests, RequestFocusFailTest) { nullptr, // on_update_view_callback, nullptr, // on_destroy_view_callback, nullptr, // on_create_surface_callback, - nullptr, // external_view_embedder, fml::TimeDelta::Zero(), // vsync_offset ZX_HANDLE_INVALID // vsync_event_handle ); diff --git a/shell/platform/fuchsia/flutter/surface.cc b/shell/platform/fuchsia/flutter/surface.cc index 27753721bcc1c..61f2d25878598 100644 --- a/shell/platform/fuchsia/flutter/surface.cc +++ b/shell/platform/fuchsia/flutter/surface.cc @@ -14,7 +14,7 @@ namespace flutter_runner { Surface::Surface(std::string debug_label, - std::shared_ptr view_embedder, + flutter::ExternalViewEmbedder* view_embedder, GrDirectContext* gr_context) : debug_label_(std::move(debug_label)), view_embedder_(view_embedder), @@ -53,7 +53,7 @@ SkMatrix Surface::GetRootTransformation() const { // |flutter::GetViewEmbedder| flutter::ExternalViewEmbedder* Surface::GetExternalViewEmbedder() { - return view_embedder_.get(); + return view_embedder_; } } // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/surface.h b/shell/platform/fuchsia/flutter/surface.h index bc95d88cc10d4..96222c0f2d326 100644 --- a/shell/platform/fuchsia/flutter/surface.h +++ b/shell/platform/fuchsia/flutter/surface.h @@ -16,14 +16,14 @@ namespace flutter_runner { class Surface final : public flutter::Surface { public: Surface(std::string debug_label, - std::shared_ptr view_embedder, + flutter::ExternalViewEmbedder* view_embedder, GrDirectContext* gr_context); ~Surface() override; private: const std::string debug_label_; - std::shared_ptr view_embedder_; + flutter::ExternalViewEmbedder* view_embedder_; GrDirectContext* gr_context_; // |flutter::Surface| From 04bf8791feb9c8b56f3fa03ddea3d5286d74a18f Mon Sep 17 00:00:00 2001 From: George Wright Date: Fri, 16 Oct 2020 16:07:02 -0700 Subject: [PATCH 150/219] Add plumbing to grab dart entrypoint args on macOS (#21789) --- .../macos/framework/Headers/FlutterDartProject.h | 10 ++++++++++ .../macos/framework/Source/FlutterDartProject.mm | 4 ++++ .../darwin/macos/framework/Source/FlutterEngine.mm | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h b/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h index ab4f0d9e60bab..e87ddf54eaded 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h @@ -36,6 +36,16 @@ FLUTTER_EXPORT */ @property(nonatomic) bool enableMirrors; +/** + * An NSArray of NSStrings to be passed as command line arguments to the Dart entrypoint. + * + * If this is not explicitly set, this will default to the contents of + * [NSProcessInfo arguments], without the binary name. + * + * Set this to nil to pass no arguments to the Dart entrypoint. + */ +@property(nonatomic, nullable) NSArray* dartEntrypointArguments; + @end #endif // FLUTTER_FLUTTERDARTPROJECT_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm index a1648b332a156..6343729d09ac7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm @@ -27,6 +27,10 @@ - (instancetype)initWithPrecompiledDartBundle:(NSBundle*)bundle { NSAssert(self, @"Super init cannot be nil"); _dartBundle = bundle ?: [NSBundle bundleWithIdentifier:kAppBundleIdentifier]; + _dartEntrypointArguments = [[NSProcessInfo processInfo] arguments]; + // Remove the first element as it's the binary name + _dartEntrypointArguments = [_dartEntrypointArguments + subarrayWithRange:NSMakeRange(1, _dartEntrypointArguments.count - 1)]; return self; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index f6a0027dc3d0f..60968ceec0031 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -263,6 +263,11 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { std::transform(switches.begin(), switches.end(), std::back_inserter(argv), [](const std::string& arg) -> const char* { return arg.c_str(); }); + std::vector dartEntrypointArgs; + for (NSString* argument in [_project dartEntrypointArguments]) { + dartEntrypointArgs.push_back([argument UTF8String]); + } + FlutterProjectArgs flutterArguments = {}; flutterArguments.struct_size = sizeof(FlutterProjectArgs); flutterArguments.assets_path = _project.assetsPath.UTF8String; @@ -272,6 +277,9 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage; flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String; flutterArguments.shutdown_dart_vm_when_done = true; + flutterArguments.dart_entrypoint_argc = dartEntrypointArgs.size(); + flutterArguments.dart_entrypoint_argv = dartEntrypointArgs.data(); + static size_t sTaskRunnerIdentifiers = 0; const FlutterTaskRunnerDescription cocoa_task_runner_description = { .struct_size = sizeof(FlutterTaskRunnerDescription), From b8fd0013a8ff2cd5a75af4aecab6e8e39af592d2 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 16 Oct 2020 16:08:36 -0700 Subject: [PATCH 151/219] Update FLUTTER_NOLINT uses to include issue link (#21921) In an upcoming patch, we'll enable enforcement that all FLUTTER_NOLINT comments include an issue link. This migrates the remaining uses to that format. Bug: https://github.com/flutter/flutter/issues/68273 --- shell/platform/linux/fl_json_message_codec.cc | 4 ---- shell/version/version.cc | 3 ++- vulkan/vulkan_window.cc | 3 ++- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/shell/platform/linux/fl_json_message_codec.cc b/shell/platform/linux/fl_json_message_codec.cc index 3fbfb1238ea74..7441ebe63c645 100644 --- a/shell/platform/linux/fl_json_message_codec.cc +++ b/shell/platform/linux/fl_json_message_codec.cc @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(cbracken): lint disabled due to rapidjson null deref issue. -// https://github.com/flutter/flutter/issues/65676 -// FLUTTER_NOLINT - #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" #include diff --git a/shell/version/version.cc b/shell/version/version.cc index 54ff4cb07babd..77baa13107c71 100644 --- a/shell/version/version.cc +++ b/shell/version/version.cc @@ -1,7 +1,8 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT + +// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/68332 #include "flutter/shell/version/version.h" diff --git a/vulkan/vulkan_window.cc b/vulkan/vulkan_window.cc index 2e1b99567438f..2c4618f06e796 100644 --- a/vulkan/vulkan_window.cc +++ b/vulkan/vulkan_window.cc @@ -1,7 +1,8 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT + +// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/68331 #include "vulkan_window.h" From 869d3c8c7a99a8a8ddcd5bb1d1c490a9006da70e Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Fri, 16 Oct 2020 16:08:46 -0700 Subject: [PATCH 152/219] Set strokeCap, strokeJoin, and strokeMiter when resurrecting Paint (#21926) --- lib/web_ui/lib/src/engine/canvaskit/painting.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/web_ui/lib/src/engine/canvaskit/painting.dart b/lib/web_ui/lib/src/engine/canvaskit/painting.dart index 550ff12351e40..276e6bb1ccfc3 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/painting.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/painting.dart @@ -227,6 +227,9 @@ class CkPaint extends ManagedSkiaObject implements ui.Paint { paint.setColorFilter(_ckColorFilter?.skiaObject); paint.setImageFilter(_imageFilter?.skiaObject); paint.setFilterQuality(toSkFilterQuality(_filterQuality)); + paint.setStrokeCap(toSkStrokeCap(_strokeCap)); + paint.setStrokeJoin(toSkStrokeJoin(_strokeJoin)); + paint.setStrokeMiter(_strokeMiterLimit); return paint; } From 18357a85819200b18e58eef19f6048e90cb69143 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 19:22:01 -0400 Subject: [PATCH 153/219] Roll Skia from f9c7b2803461 to f60a76e2ac01 (4 revisions) (#21929) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 24e82382ecf77..19beae836e331 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'f9c7b2803461b1ff4010a96d79e742849c680068', + 'skia_revision': 'f60a76e2ac019356c94f71682205d7f5a94c220e', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 672ac83b1d590..396d8323d6071 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 0303b9dedbc8528f215b9b008f6166f8 +Signature: 05f66a144cd90f4b3f945c832b2e6354 UNUSED LICENSES: @@ -3902,6 +3902,7 @@ FILE: ../../../third_party/skia/gm/ycbcrimage.cpp FILE: ../../../third_party/skia/include/core/SkYUVAInfo.h FILE: ../../../third_party/skia/include/core/SkYUVAPixmaps.h FILE: ../../../third_party/skia/include/gpu/GrBackendSurfaceMutableState.h +FILE: ../../../third_party/skia/include/gpu/GrYUVABackendTextures.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DBackendContext.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DTypes.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DTypesMinimal.h @@ -3940,6 +3941,7 @@ FILE: ../../../third_party/skia/src/gpu/GrUniformDataManager.cpp FILE: ../../../third_party/skia/src/gpu/GrUniformDataManager.h FILE: ../../../third_party/skia/src/gpu/GrUnrefDDLTask.h FILE: ../../../third_party/skia/src/gpu/GrUtil.cpp +FILE: ../../../third_party/skia/src/gpu/GrYUVABackendTextures.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAttachment.cpp From 49c35b6177b0dd7ebeda275587dd5a5631c8b688 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 16 Oct 2020 17:24:23 -0700 Subject: [PATCH 154/219] Eliminate unnecessary linter opt-outs (#21935) Eliminates FLUTTER_NOLINT where they can be landed without triggering lint failures. --- lib/io/dart_io.cc | 1 - lib/ui/dart_runtime_hooks.cc | 1 - lib/ui/text/paragraph.cc | 1 - runtime/dart_isolate.cc | 1 - runtime/dart_isolate_unittests.cc | 1 - runtime/dart_lifecycle_unittests.cc | 1 - runtime/dart_snapshot.cc | 1 - runtime/runtime_controller.cc | 1 - runtime/type_conversions_unittests.cc | 1 - shell/common/engine.cc | 1 - shell/platform/fuchsia/flutter/component.cc | 1 - shell/platform/fuchsia/flutter/engine.cc | 1 - testing/dart_isolate_runner.cc | 1 - testing/fixture_test.cc | 1 - 14 files changed, 14 deletions(-) diff --git a/lib/io/dart_io.cc b/lib/io/dart_io.cc index cb255fbedd6e8..a4dfa9351c095 100644 --- a/lib/io/dart_io.cc +++ b/lib/io/dart_io.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/lib/io/dart_io.h" diff --git a/lib/ui/dart_runtime_hooks.cc b/lib/ui/dart_runtime_hooks.cc index 6cfa20a5333d3..cb619aadc1a42 100644 --- a/lib/ui/dart_runtime_hooks.cc +++ b/lib/ui/dart_runtime_hooks.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/lib/ui/dart_runtime_hooks.h" diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index f8beda3e94899..e8186be6c4067 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/lib/ui/text/paragraph.h" diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 80583aab1fd6b..2fa50132e6750 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/runtime/dart_isolate.h" diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index ddbc402e563ef..c95235025ee8e 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/runtime/dart_isolate.h" diff --git a/runtime/dart_lifecycle_unittests.cc b/runtime/dart_lifecycle_unittests.cc index e6fd7b1093351..8576b3fb11292 100644 --- a/runtime/dart_lifecycle_unittests.cc +++ b/runtime/dart_lifecycle_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/common/task_runners.h" #include "flutter/fml/paths.h" diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index dd2d45c4d40f4..e9ed35418880a 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/runtime/dart_snapshot.h" diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index b2a8f7a14389f..c0874db466c7c 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/runtime/runtime_controller.h" diff --git a/runtime/type_conversions_unittests.cc b/runtime/type_conversions_unittests.cc index fe02962a3aa56..f1ed831e174cc 100644 --- a/runtime/type_conversions_unittests.cc +++ b/runtime/type_conversions_unittests.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/testing/dart_isolate_runner.h" diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 31901318a8243..f3374a036070a 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/shell/common/engine.h" diff --git a/shell/platform/fuchsia/flutter/component.cc b/shell/platform/fuchsia/flutter/component.cc index 04cd15bfc6cb0..900cd6d051bd0 100644 --- a/shell/platform/fuchsia/flutter/component.cc +++ b/shell/platform/fuchsia/flutter/component.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "component.h" diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index a7d85630c5bc9..67af712aadb64 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "engine.h" diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index 8ba08719bc77e..43d77c0165c28 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/testing/dart_isolate_runner.h" diff --git a/testing/fixture_test.cc b/testing/fixture_test.cc index d001a8b35ab16..3a8a94c138805 100644 --- a/testing/fixture_test.cc +++ b/testing/fixture_test.cc @@ -1,7 +1,6 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT #include "flutter/testing/fixture_test.h" From 2df836f6e29149392f618a72f53bac43482de7b9 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 16 Oct 2020 17:46:42 -0700 Subject: [PATCH 155/219] Require that FLUTTER_NOLINT include issue link (#21922) This adds enforcement to the linter that all FLUTTER_NOLINT comments be of the form: // FLUTTER_NOLINT: https://github.com/flutter/flutter/issue/ID Every linter opt-out should have an associated bug describing what issue it works around so that others can work on eliminating it, or at least understanding the rationale and whether it's still relevant. This also reduces the likelihood of inadvertent copy-pasting into new files either because the author fails to spot it when copying the copyright block from another file, or assumes that it's necessary for some subcomponent of the engine. Bug: https://github.com/flutter/flutter/issues/68273 --- ci/bin/lint.dart | 102 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 29 deletions(-) diff --git a/ci/bin/lint.dart b/ci/bin/lint.dart index 321bd3d9989b3..695473265701a 100644 --- a/ci/bin/lint.dart +++ b/ci/bin/lint.dart @@ -17,7 +17,7 @@ import 'package:args/args.dart'; import 'package:path/path.dart' as path; import 'package:process_runner/process_runner.dart'; -String _linterOutputHeader = ''' +const String _linterOutputHeader = ''' ┌──────────────────────────┐ │ Engine Clang Tidy Linter │ └──────────────────────────┘ @@ -26,6 +26,8 @@ more information on addressing these issues please see: https://github.com/flutter/flutter/wiki/Engine-Clang-Tidy-Linter '''; +const String issueUrlPrefix = 'https://github.com/flutter/flutter/issues'; + class Command { Directory directory = Directory(''); String command = ''; @@ -109,23 +111,59 @@ File buildFileAsRepoFile(String buildFile, Directory repoPath) { return result; } -Future shouldIgnoreFile(File file) async { - if (path.split(file.path).contains('third_party')) { - return 'third_party'; - } else { - final RegExp exp = RegExp(r'//\s*FLUTTER_NOLINT'); - await for (String line - in file.openRead().transform(utf8.decoder).transform(const LineSplitter())) { - if (exp.hasMatch(line)) { - return 'FLUTTER_NOLINT'; - } else if (line.isNotEmpty && line[0] != '\n' && line[0] != '/') { - // Quick out once we find a line that isn't empty or a comment. The - // FLUTTER_NOLINT must show up before the first real code. - return ''; - } +/// Lint actions to apply to a file. +enum LintAction { + /// Run the linter over the file. + lint, + + /// Ignore files under third_party/. + skipThirdParty, + + /// Ignore due to a well-formed FLUTTER_NOLINT comment. + skipNoLint, + + /// Fail due to a malformed FLUTTER_NOLINT comment. + failMalformedNoLint, +} + +bool isThirdPartyFile(File file) { + return path.split(file.path).contains('third_party'); +} + +Future getLintAction(File file) async { + if (isThirdPartyFile(file)) { + return LintAction.skipThirdParty; + } + + // Check for FlUTTER_NOLINT at top of file. + final RegExp exp = RegExp('\/\/\\s*FLUTTER_NOLINT(: $issueUrlPrefix/\\d+)?'); + final Stream lines = file.openRead() + .transform(utf8.decoder) + .transform(const LineSplitter()); + await for (String line in lines) { + final RegExpMatch match = exp.firstMatch(line); + if (match != null) { + return match.group(1) != null + ? LintAction.skipNoLint + : LintAction.failMalformedNoLint; + } else if (line.isNotEmpty && line[0] != '\n' && line[0] != '/') { + // Quick out once we find a line that isn't empty or a comment. The + // FLUTTER_NOLINT must show up before the first real code. + return LintAction.lint; } - return ''; } + return LintAction.lint; +} + +WorkerJob createLintJob(Command command, String checks, String tidyPath) { + final String tidyArgs = calcTidyArgs(command); + final List args = [command.file.path, checks, '--']; + args.addAll(tidyArgs?.split(' ') ?? []); + return WorkerJob( + [tidyPath, ...args], + workingDirectory: command.directory, + name: 'clang-tidy on ${command.file.path}', + ); } void _usage(ArgParser parser, {int exitCode = 1}) { @@ -226,19 +264,23 @@ void main(List arguments) async { final List jobs = []; for (Command command in changedFileBuildCommands) { final String relativePath = path.relative(command.file.path, from: repoPath.parent.path); - final String ignoreReason = await shouldIgnoreFile(command.file); - if (ignoreReason.isEmpty) { - final String tidyArgs = calcTidyArgs(command); - final List args = [command.file.path, checks, '--']; - args.addAll(tidyArgs?.split(' ') ?? []); - print('🔶 linting $relativePath'); - jobs.add(WorkerJob( - [tidyPath, ...args], - workingDirectory: command.directory, - name: 'clang-tidy on ${command.file.path}', - )); - } else { - print('🔷 ignoring $relativePath ($ignoreReason)'); + final LintAction action = await getLintAction(command.file); + switch (action) { + case LintAction.skipNoLint: + print('🔷 ignoring $relativePath (FLUTTER_NOLINT)'); + break; + case LintAction.failMalformedNoLint: + print('❌ malformed opt-out $relativePath'); + print(' Required format: // FLUTTER_NOLINT: $issueUrlPrefix/ISSUE_ID'); + exitCode = 1; + break; + case LintAction.lint: + print('🔶 linting $relativePath'); + jobs.add(createLintJob(command, checks, tidyPath)); + break; + case LintAction.skipThirdParty: + print('🔷 ignoring $relativePath (third_party)'); + break; } } final ProcessPool pool = ProcessPool(); @@ -260,5 +302,7 @@ void main(List arguments) async { print('\n'); if (exitCode == 0) { print('No lint problems found.'); + } else { + print('Lint problems found.'); } } From f6b8a50ab70236c3be0ed37fffa93a65d8749f24 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 21:32:02 -0400 Subject: [PATCH 156/219] Roll Skia from f60a76e2ac01 to be8004d2fb6c (1 revision) (#21936) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index 19beae836e331..9cc4d3a4df5bd 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'f60a76e2ac019356c94f71682205d7f5a94c220e', + 'skia_revision': 'be8004d2fb6c8575d7da7c580811639654a9d255', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 396d8323d6071..54caee7b8e075 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 05f66a144cd90f4b3f945c832b2e6354 +Signature: 8a3d5e40f57c4443b0ec386868b3f2f2 UNUSED LICENSES: @@ -3902,7 +3902,6 @@ FILE: ../../../third_party/skia/gm/ycbcrimage.cpp FILE: ../../../third_party/skia/include/core/SkYUVAInfo.h FILE: ../../../third_party/skia/include/core/SkYUVAPixmaps.h FILE: ../../../third_party/skia/include/gpu/GrBackendSurfaceMutableState.h -FILE: ../../../third_party/skia/include/gpu/GrYUVABackendTextures.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DBackendContext.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DTypes.h FILE: ../../../third_party/skia/include/gpu/d3d/GrD3DTypesMinimal.h @@ -3941,7 +3940,6 @@ FILE: ../../../third_party/skia/src/gpu/GrUniformDataManager.cpp FILE: ../../../third_party/skia/src/gpu/GrUniformDataManager.h FILE: ../../../third_party/skia/src/gpu/GrUnrefDDLTask.h FILE: ../../../third_party/skia/src/gpu/GrUtil.cpp -FILE: ../../../third_party/skia/src/gpu/GrYUVABackendTextures.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.cpp FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAMDMemoryAllocator.h FILE: ../../../third_party/skia/src/gpu/d3d/GrD3DAttachment.cpp From 34832cb7829607235d68f3180544c867832d95e1 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 16 Oct 2020 18:39:25 -0700 Subject: [PATCH 157/219] Roll the process_runner package used by the formatter script (#21937) --- ci/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/pubspec.yaml b/ci/pubspec.yaml index 1ef6eaaa58cf1..b66758dd1ac34 100644 --- a/ci/pubspec.yaml +++ b/ci/pubspec.yaml @@ -8,7 +8,7 @@ dependencies: args: ^1.6.0 path: ^1.7.0 isolate: ^2.0.3 - process_runner: ^3.1.0 + process_runner: ^3.1.1 environment: sdk: '>=2.8.0 <3.0.0' From 2874fccbbbd8eff0839f8ba5209345252fe049be Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Fri, 16 Oct 2020 22:17:01 -0400 Subject: [PATCH 158/219] Roll Dart SDK from b58cfe5ab24e to aaab579579be (1 revision) (#21938) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 9cc4d3a4df5bd..c5e1089be51cb 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': 'b58cfe5ab24e7756d672c0257ed30888edfd3c27', + 'dart_revision': 'aaab579579bedd316956bec2e0853c4ad3c3fbb2', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 37c06213fc85f..e2265f218ca71 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: f220de2417f1bc266ae25ece48bf4216 +Signature: 32290c22f0ebd5322802b8512a00e00e UNUSED LICENSES: From cac04c4cfad16e9b8f54053290eec7b8122f1cf8 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 16 Oct 2020 20:47:43 -0700 Subject: [PATCH 159/219] Add FML_UNREACHABLE to declare points in code that should never be reached. (#21941) A version of this macro is present in most code-bases. The use of this macro must satisfy two requirements: 1: If reached, the process must be terminated in all runtime modes and at all optimization levels. 2: If the compiler requires a value to be returned from the function, encountering this macro should not make the compiler insist on a return value (since the process is about to die anyway). We used to have a version of this macro that wasn't widely used and didn't satisfy the two requirements. I have removed the same and another unused macro in fml/logging.h Fixes https://github.com/flutter/flutter/issues/68164. --- flow/layers/child_scene_layer.cc | 4 +--- fml/BUILD.gn | 1 + fml/logging.cc | 6 +++++- fml/logging.h | 11 +++++++---- fml/logging_unittests.cc | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 fml/logging_unittests.cc diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 9ba5eff3c5021..858328a140b22 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -22,9 +22,7 @@ void ChildSceneLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { CheckForChildLayerBelow(context); } -void ChildSceneLayer::Paint(PaintContext& context) const { - FML_NOTREACHED(); -} +void ChildSceneLayer::Paint(PaintContext& context) const {} void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "ChildSceneLayer::UpdateScene"); diff --git a/fml/BUILD.gn b/fml/BUILD.gn index 198d61c52c610..cf6af4e68a2e2 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -249,6 +249,7 @@ if (enable_unittests) { "command_line_unittest.cc", "file_unittest.cc", "hash_combine_unittests.cc", + "logging_unittests.cc", "memory/ref_counted_unittest.cc", "memory/task_runner_checker_unittest.cc", "memory/weak_ptr_unittest.cc", diff --git a/fml/logging.cc b/fml/logging.cc index d4273b9381da7..6530348e0ccac 100644 --- a/fml/logging.cc +++ b/fml/logging.cc @@ -93,7 +93,7 @@ LogMessage::~LogMessage() { #endif if (severity_ >= LOG_FATAL) { - abort(); + KillProcess(); } } @@ -105,4 +105,8 @@ bool ShouldCreateLogMessage(LogSeverity severity) { return severity >= GetMinLogLevel(); } +void KillProcess() { + abort(); +} + } // namespace fml diff --git a/fml/logging.h b/fml/logging.h index 20cb887cb20df..0b732ee818a0a 100644 --- a/fml/logging.h +++ b/fml/logging.h @@ -43,6 +43,8 @@ int GetVlogVerbosity(); // LOG_FATAL and above is always true. bool ShouldCreateLogMessage(LogSeverity severity); +[[noreturn]] void KillProcess(); + } // namespace fml #define FML_LOG_STREAM(severity) \ @@ -87,9 +89,10 @@ bool ShouldCreateLogMessage(LogSeverity severity); #define FML_DCHECK(condition) FML_EAT_STREAM_PARAMETERS(condition) #endif -#define FML_NOTREACHED() FML_DCHECK(false) - -#define FML_NOTIMPLEMENTED() \ - FML_LOG(ERROR) << "Not implemented in: " << __PRETTY_FUNCTION__ +#define FML_UNREACHABLE() \ + { \ + FML_LOG(ERROR) << "Reached unreachable code."; \ + ::fml::KillProcess(); \ + } #endif // FLUTTER_FML_LOGGING_H_ diff --git a/fml/logging_unittests.cc b/fml/logging_unittests.cc new file mode 100644 index 0000000000000..88b2ebee806df --- /dev/null +++ b/fml/logging_unittests.cc @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/logging.h" +#include "gtest/gtest.h" + +namespace fml { +namespace testing { + +int UnreachableScopeWithoutReturnDoesNotMakeCompilerMad() { + KillProcess(); + // return 0; <--- Missing but compiler is fine. +} + +int UnreachableScopeWithMacroWithoutReturnDoesNotMakeCompilerMad() { + FML_UNREACHABLE(); + // return 0; <--- Missing but compiler is fine. +} + +TEST(LoggingTest, UnreachableKillProcess) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH(KillProcess(), ""); +} + +TEST(LoggingTest, UnreachableKillProcessWithMacro) { + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + ASSERT_DEATH({ FML_UNREACHABLE(); }, ""); +} + +} // namespace testing +} // namespace fml From fabcc2de218b5491e50d089c19f6d70d21f389f4 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 17 Oct 2020 00:47:03 -0400 Subject: [PATCH 160/219] Roll Fuchsia Mac SDK from _FaRRt69Z... to XZSNobQCT... (#21944) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c5e1089be51cb..dad73e1d338f0 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '_FaRRt69ZlrCACiYukNB6u_zVpyECR3W1gPgS__sm-kC' + 'version': 'XZSNobQCTaaS8kszD0vwSYUk3rfYLRis_EDjQj-UaxoC' } ], 'condition': 'host_os == "mac"', From 20d3a0764a49369c159f5a11323047840480d35d Mon Sep 17 00:00:00 2001 From: godofredoc <54371434+godofredoc@users.noreply.github.com> Date: Fri, 16 Oct 2020 21:51:37 -0700 Subject: [PATCH 161/219] Collect logs in the background. (#21828) * Collect logs in the background. * Use fuchsia_ctl log_file option. * Delete the correct file. * Remove commented code. --- testing/fuchsia/run_tests.sh | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/testing/fuchsia/run_tests.sh b/testing/fuchsia/run_tests.sh index f39233dec6642..28fba45e1f8bf 100755 --- a/testing/fuchsia/run_tests.sh +++ b/testing/fuchsia/run_tests.sh @@ -45,20 +45,12 @@ fuchsia_ctl() { } reboot() { - fuchsia_ctl ssh \ - --timeout-seconds $ssh_timeout_seconds \ - --identity-file $pkey \ - -c "log_listener --dump_logs yes --file /tmp/log.txt" # As we are not using recipes we don't have a way to know the location # to upload the log to isolated. We are saving the log to a file to avoid dart # hanging when running the process and then just using printing the content to # the console. - fuchsia_ctl ssh \ - --timeout-seconds $ssh_timeout_seconds \ - --identity-file $pkey \ - -c "cat /tmp/log.txt" - - + kill -9 $PID + cat /tmp/log.txt echo "$(date) START:REBOOT ----------------------------------------" # note: this will set an exit code of 255, which we can ignore. fuchsia_ctl ssh \ @@ -82,6 +74,9 @@ for i in {1..10}; do --identity-file $pkey \ -c "echo up" && break || sleep 15; done +rm -rf /tmp/log.txt +fuchsia_ctl ssh --timeout-seconds 1800 --identity-file $pkey -c "log_listener" --log-file /tmp/log.txt & +PID=$! echo "$(date) END:WAIT_DEVICE_READY ---------------------------------" echo "$(date) START:EXTRACT_PACKAGES -------------------------------" From 815a1f3868580e86bdc3f185f980002d622e2e3a Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 17 Oct 2020 02:47:01 -0400 Subject: [PATCH 162/219] Roll Dart SDK from aaab579579be to 42a0bf548ea3 (1 revision) (#21946) --- DEPS | 4 ++-- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index dad73e1d338f0..a4423fc28889c 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': 'aaab579579bedd316956bec2e0853c4ad3c3fbb2', + 'dart_revision': '42a0bf548ea3a2d2c3926f357333d771c7bbe8cd', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -235,7 +235,7 @@ deps = { Var('dart_git') + '/logging.git@1590ba0b648a51e7eb3895c612e4b72f72623b6f', 'src/third_party/dart/third_party/pkg/markdown': - Var('dart_git') + '/markdown.git@dbeafd47759e7dd0a167602153bb9c49fb5e5fe7', + Var('dart_git') + '/markdown.git@6f89681d59541ddb1cf3a58efbdaa2304ffc3f51', 'src/third_party/dart/third_party/pkg/matcher': Var('dart_git') + '/matcher.git@9cae8faa7868bf3a88a7ba45eb0bd128e66ac515', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index e2265f218ca71..09a67386e85af 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 32290c22f0ebd5322802b8512a00e00e +Signature: 2bb9dcb314a53c20072e7759afee8c18 UNUSED LICENSES: From 890f0162077707b962e04bb2ab555e99e49d3652 Mon Sep 17 00:00:00 2001 From: Ferhat Date: Sat, 17 Oct 2020 00:09:41 -0700 Subject: [PATCH 163/219] [web] Implement ClipOp.difference (#21901) --- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 16 +++++-- lib/web_ui/lib/src/engine/canvas_pool.dart | 15 +++++-- lib/web_ui/lib/src/engine/dom_canvas.dart | 2 +- lib/web_ui/lib/src/engine/engine_canvas.dart | 4 +- .../lib/src/engine/html/recording_canvas.dart | 4 +- .../engine/canvas_golden_test.dart | 4 +- .../engine/clip_op_golden_test.dart | 42 ++++++++++++++++++ .../test/golden_tests/engine/screenshot.dart | 43 +++++++++++++++++++ lib/web_ui/test/mock_engine_canvas.dart | 2 +- 10 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/clip_op_golden_test.dart create mode 100644 lib/web_ui/test/golden_tests/engine/screenshot.dart diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 66a641aed621b..1f1b59c91e98d 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 672510dc52daa5b059081f6990582bccdb4ea48f +revision: 1556280d6f1d70fac9ddff9b38639757e105b4b0 diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 0f054559b18a0..b52588717183b 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -271,8 +271,18 @@ class BitmapCanvas extends EngineCanvas { } @override - void clipRect(ui.Rect rect) { - _canvasPool.clipRect(rect); + void clipRect(ui.Rect rect, ui.ClipOp op) { + if (op == ui.ClipOp.difference) { + // Create 2 rectangles inside each other that represents + // clip area difference using even-odd fill rule. + final SurfacePath path = new SurfacePath(); + path.fillType = ui.PathFillType.evenOdd; + path.addRect(ui.Rect.fromLTWH(0, 0, _bounds.width, _bounds.height)); + path.addRect(rect); + _canvasPool.clipPath(path); + } else { + _canvasPool.clipRect(rect); + } } @override @@ -466,7 +476,7 @@ class BitmapCanvas extends EngineCanvas { } else { if (requiresClipping) { save(); - clipRect(dst); + clipRect(dst, ui.ClipOp.intersect); } double targetLeft = dst.left; double targetTop = dst.top; diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index d56351124eed6..9a297398605c5 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -210,8 +210,13 @@ class _CanvasPool extends _SaveStackTracking { } else if (clipEntry.rrect != null) { _clipRRect(ctx, clipEntry.rrect!); } else if (clipEntry.path != null) { - _runPath(ctx, clipEntry.path as SurfacePath); - ctx.clip(); + final SurfacePath path = clipEntry.path as SurfacePath; + _runPath(ctx, path); + if (path.fillType == ui.PathFillType.nonZero) { + ctx.clip(); + } else { + ctx.clip('evenodd'); + } } } } @@ -443,7 +448,11 @@ class _CanvasPool extends _SaveStackTracking { if (_canvas != null) { html.CanvasRenderingContext2D ctx = context; _runPath(ctx, path as SurfacePath); - ctx.clip(); + if (path.fillType == ui.PathFillType.nonZero) { + ctx.clip(); + } else { + ctx.clip('evenodd'); + } } } diff --git a/lib/web_ui/lib/src/engine/dom_canvas.dart b/lib/web_ui/lib/src/engine/dom_canvas.dart index 0eabf6d8a4aac..11668eb2472fc 100644 --- a/lib/web_ui/lib/src/engine/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/dom_canvas.dart @@ -28,7 +28,7 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void clipRect(ui.Rect rect) { + void clipRect(ui.Rect rect, ui.ClipOp op) { throw UnimplementedError(); } diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index 5b0f7d31d8167..c197515900a67 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -33,7 +33,7 @@ abstract class EngineCanvas { void transform(Float32List matrix4); - void clipRect(ui.Rect rect); + void clipRect(ui.Rect rect, ui.ClipOp clipOp); void clipRRect(ui.RRect rrect); @@ -222,7 +222,7 @@ mixin SaveStackTracking on EngineCanvas { /// /// Classes that override this method must call `super.clipRect()`. @override - void clipRect(ui.Rect rect) { + void clipRect(ui.Rect rect, ui.ClipOp op) { _clipStack ??= <_SaveClipEntry>[]; _clipStack!.add(_SaveClipEntry.rect(rect, _currentTransform.clone())); } diff --git a/lib/web_ui/lib/src/engine/html/recording_canvas.dart b/lib/web_ui/lib/src/engine/html/recording_canvas.dart index cb6a1ed626fed..9f7e6b93bca92 100644 --- a/lib/web_ui/lib/src/engine/html/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/recording_canvas.dart @@ -275,7 +275,7 @@ class RecordingCanvas { void clipRect(ui.Rect rect, ui.ClipOp clipOp) { assert(!_recordingEnded); - final PaintClipRect command = PaintClipRect(rect, clipOp); + final DrawCommand command = PaintClipRect(rect, clipOp); switch (clipOp) { case ui.ClipOp.intersect: _paintBounds.clipRect(rect, command); @@ -810,7 +810,7 @@ class PaintClipRect extends DrawCommand { @override void apply(EngineCanvas canvas) { - canvas.clipRect(rect); + canvas.clipRect(rect, clipOp); } @override diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index a072d025974fd..dde35315d38bc 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -157,9 +157,9 @@ void testMain() async { canvas = BitmapCanvas(canvasSize); canvas.debugChildOverdraw = true; - canvas.clipRect(outerClip); + canvas.clipRect(outerClip, ClipOp.intersect); canvas.drawParagraph(paragraph, const Offset(8.5, 8.5)); - canvas.clipRect(innerClip); + canvas.clipRect(innerClip, ClipOp.intersect); canvas.drawParagraph(paragraph, Offset(8.5, 8.5 + innerClip.top)); expect( diff --git a/lib/web_ui/test/golden_tests/engine/clip_op_golden_test.dart b/lib/web_ui/test/golden_tests/engine/clip_op_golden_test.dart new file mode 100644 index 0000000000000..8e14c37caa308 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/clip_op_golden_test.dart @@ -0,0 +1,42 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart'; +import 'package:ui/src/engine.dart'; +import 'screenshot.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() async { + setUp(() async { + debugEmulateFlutterTesterEnvironment = true; + }); + + /// Regression test for https://github.com/flutter/flutter/issues/64734. + test('Clips using difference', () async { + final Rect region = const Rect.fromLTRB(0, 0, 400, 300); + final RecordingCanvas canvas = RecordingCanvas(region); + final Rect titleRect = Rect.fromLTWH(20, 0, 50, 20); + final Paint paint = Paint() + ..style = PaintingStyle.stroke + ..color = const Color(0xff000000) + ..strokeWidth = 1; + canvas.save(); + try { + final Rect borderRect = Rect.fromLTRB(0, 10, region.width, region.height); + canvas.clipRect(titleRect, ClipOp.difference); + canvas.drawRect(borderRect, paint); + } finally { + canvas.restore(); + } + canvas..drawRect(titleRect, paint); + await canvasScreenshot(canvas, 'clip_op_difference', + region: const Rect.fromLTRB(0, 0, 420, 360)); + }); +} diff --git a/lib/web_ui/test/golden_tests/engine/screenshot.dart b/lib/web_ui/test/golden_tests/engine/screenshot.dart new file mode 100644 index 0000000000000..81cae453c2a56 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/screenshot.dart @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'dart:html' as html; +import 'package:ui/ui.dart' as ui; +import 'package:ui/src/engine.dart'; +import 'package:web_engine_tester/golden_tester.dart'; +import 'package:test/test.dart'; + +/// Commit a recording canvas to a bitmap, and compare with the expected. +Future canvasScreenshot(RecordingCanvas rc, String fileName, + {ui.Rect region = const ui.Rect.fromLTWH(0, 0, 600, 800), + double maxDiffRatePercent = 0.0, bool write: false}) async { + final EngineCanvas engineCanvas = BitmapCanvas(region); + + rc.endRecording(); + rc.apply(engineCanvas, region); + + // Wrap in so that our CSS selectors kick in. + final html.Element sceneElement = html.Element.tag('flt-scene'); + try { + sceneElement.append(engineCanvas.rootElement); + html.document.body.append(sceneElement); + await matchGoldenFile('$fileName.png', + region: region, maxDiffRatePercent: maxDiffRatePercent, write: write); + } finally { + // The page is reused across tests, so remove the element after taking the + // Scuba screenshot. + sceneElement.remove(); + } +} + +/// Configures the test to use bundled Roboto and Ahem fonts to avoid golden +/// screenshot differences due to differences in the preinstalled system fonts. +void setUpStableTestFonts() { + setUp(() async { + await ui.webOnlyInitializePlatform(); + ui.webOnlyFontCollection.debugRegisterTestFonts(); + await ui.webOnlyFontCollection.ensureFontsLoaded(); + }); +} diff --git a/lib/web_ui/test/mock_engine_canvas.dart b/lib/web_ui/test/mock_engine_canvas.dart index a88dccc0b12ca..91b59ceb8e320 100644 --- a/lib/web_ui/test/mock_engine_canvas.dart +++ b/lib/web_ui/test/mock_engine_canvas.dart @@ -99,7 +99,7 @@ class MockEngineCanvas implements EngineCanvas { } @override - void clipRect(Rect rect) { + void clipRect(Rect rect, ClipOp op) { _called('clipRect', arguments: rect); } From 3ea02776e88f1a383cb65b4200f1c6f477e94e2b Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 17 Oct 2020 07:17:01 -0400 Subject: [PATCH 164/219] Roll Dart SDK from 42a0bf548ea3 to 675c7165c071 (1 revision) (#21948) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index a4423fc28889c..4051a9bf99bdb 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '42a0bf548ea3a2d2c3926f357333d771c7bbe8cd', + 'dart_revision': '675c7165c071c405d6496c8f6cf411721ad7794a', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 09a67386e85af..1dcd14c0b17cb 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 2bb9dcb314a53c20072e7759afee8c18 +Signature: 9969595ae8de55891d17927142faa8eb UNUSED LICENSES: From 56d3da33b220bc38cb9212d4b24e8aac83176133 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 17 Oct 2020 13:52:01 -0400 Subject: [PATCH 165/219] Roll Fuchsia Mac SDK from XZSNobQCT... to 9mMCqUXkF... (#21950) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4051a9bf99bdb..bebf8d6d48f8f 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'XZSNobQCTaaS8kszD0vwSYUk3rfYLRis_EDjQj-UaxoC' + 'version': '9mMCqUXkFzP6Ok4lqE3rEKDu3-Yqzbw2SBdTRpcwegAC' } ], 'condition': 'host_os == "mac"', From 1c35cc6416ed50e11fbc75534f5ad2dba9348463 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Sat, 17 Oct 2020 11:02:19 -0700 Subject: [PATCH 166/219] [null-safety] fix type declaration of Picutre._toImage (#21942) Fixes flutter/flutter#68377 Should be a nullable string. Looks like --null-assertions does not cover the native binding code. --- lib/ui/painting.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index a8ebd7a632004..1ba835c509a0b 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -4689,7 +4689,7 @@ class Picture extends NativeFieldWrapperClass2 { ); } - String _toImage(int width, int height, _Callback<_Image> callback) native 'Picture_toImage'; + String? _toImage(int width, int height, _Callback<_Image> callback) native 'Picture_toImage'; /// Release the resources used by this object. The object is no longer usable /// after this method is called. From e9a43c15b7116890254af06dfa8a3bf9f69c08c0 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sat, 17 Oct 2020 18:32:01 -0400 Subject: [PATCH 167/219] Roll Dart SDK from 675c7165c071 to 5c59a47beda7 (1 revision) (#21952) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index bebf8d6d48f8f..477b3d0d4ed39 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '675c7165c071c405d6496c8f6cf411721ad7794a', + 'dart_revision': '5c59a47beda7af5114881b23a8238aeb99338654', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 499a70f5e21b2d2ee2fd360f2f58579fc29e0c55 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Sat, 17 Oct 2020 20:33:13 -0700 Subject: [PATCH 168/219] Restore missing call to RuntimeDelegate.OnRootIsolateCreated (#21953) Fixes https://github.com/flutter/flutter/issues/68411 --- runtime/runtime_controller.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index c0874db466c7c..a2ea4a00d24df 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -397,6 +397,9 @@ bool RuntimeController::LaunchRootIsolate( } FML_DCHECK(Dart_CurrentIsolate() == nullptr); + + client_.OnRootIsolateCreated(); + return true; } From f7da9d687d37e814fce71e768d11a5536182b052 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Sun, 18 Oct 2020 02:57:01 -0400 Subject: [PATCH 169/219] Roll Fuchsia Mac SDK from 9mMCqUXkF... to MR_bRfe8I... (#21955) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 477b3d0d4ed39..8b0ae797e8391 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '9mMCqUXkFzP6Ok4lqE3rEKDu3-Yqzbw2SBdTRpcwegAC' + 'version': 'MR_bRfe8Iqs-oit_vOBInpGrAehp3akKfqVWQjQcSEEC' } ], 'condition': 'host_os == "mac"', From e66e3f5352774d6a2b78383a693bfd2f40b83e9d Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 19 Oct 2020 12:42:02 -0400 Subject: [PATCH 170/219] Roll Skia from be8004d2fb6c to 27f7fe32f49b (1 revision) (#21956) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 8b0ae797e8391..1a77f6a76eccd 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'be8004d2fb6c8575d7da7c580811639654a9d255', + 'skia_revision': '27f7fe32f49baab9e1b176bf5d68a41aa4ef94d9', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 54caee7b8e075..ac55ac5c5ee75 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 8a3d5e40f57c4443b0ec386868b3f2f2 +Signature: 77782200a533f205decafbf6638866e7 UNUSED LICENSES: From d4ac8e6cb246af9a7e0f223a4c84a685a667c154 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Mon, 19 Oct 2020 11:51:34 -0700 Subject: [PATCH 171/219] Temporarily disabled tests that were using latin and arabic characters (#21971) while we fix them. --- third_party/txt/src/utils/LinuxUtils.h | 3 +++ third_party/txt/tests/paragraph_unittests.cc | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/third_party/txt/src/utils/LinuxUtils.h b/third_party/txt/src/utils/LinuxUtils.h index da3e7772c9aa6..ade76ada9b38b 100644 --- a/third_party/txt/src/utils/LinuxUtils.h +++ b/third_party/txt/src/utils/LinuxUtils.h @@ -38,4 +38,7 @@ #define FRIEND_TEST_LINUX_ONLY(SUITE, TEST_NAME) \ FRIEND_TEST_LINUX_ONLY_EXPANDED(SUITE, DISABLE_TEST_LINUX(TEST_NAME)) #endif // defined(__linux__) + +#define LINUX_ONLY_DISABLED(TEST_NAME) DISABLED_##TEST_NAME + #endif // LINUX_UTILS_H diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 8f94d548e2a73..a7ac55f5b4edc 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -3973,7 +3973,10 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeCenterParagraph)) { ASSERT_TRUE(Snapshot()); } -TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineLeftAlign)) { +// 10/19/20 Temporarily disabled tests while being they are being fixed. +// https://github.com/flutter/flutter/issues/68493 +TEST_F(ParagraphTest, + LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineLeftAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), @@ -4070,7 +4073,8 @@ TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineLeftAlign)) { ASSERT_TRUE(Snapshot()); } -TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineRightAlign)) { +TEST_F(ParagraphTest, + LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRightAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), @@ -4168,7 +4172,7 @@ TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineRightAlign)) { } TEST_F(ParagraphTest, - LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineCentered)) { + LINUX_ONLY_DISABLED(GetRectsForRangeCenterParagraphNewlineCentered)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), @@ -4264,8 +4268,9 @@ TEST_F(ParagraphTest, ASSERT_TRUE(Snapshot()); } + TEST_F(ParagraphTest, - LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLLeftAlign)) { + LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRTLLeftAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), @@ -4363,7 +4368,7 @@ TEST_F(ParagraphTest, } TEST_F(ParagraphTest, - LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLRightAlign)) { + LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRTLRightAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), @@ -4461,7 +4466,7 @@ TEST_F(ParagraphTest, } TEST_F(ParagraphTest, - LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineRTLCentered)) { + LINUX_ONLY_DISABLED(GetRectsForRangeCenterParagraphNewlineRTLCentered)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), From f854cbbfa59056f7ae169e27e8cec5c569791ed8 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 19 Oct 2020 13:51:39 -0700 Subject: [PATCH 172/219] Specify the Noto Naskh Arabic font to get consistent results in tests using Arabic characters (#21974) See https://github.com/flutter/flutter/issues/68493 --- third_party/txt/src/utils/LinuxUtils.h | 2 - third_party/txt/tests/paragraph_unittests.cc | 90 ++++++++++---------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/third_party/txt/src/utils/LinuxUtils.h b/third_party/txt/src/utils/LinuxUtils.h index ade76ada9b38b..212aa9f8bc5af 100644 --- a/third_party/txt/src/utils/LinuxUtils.h +++ b/third_party/txt/src/utils/LinuxUtils.h @@ -39,6 +39,4 @@ FRIEND_TEST_LINUX_ONLY_EXPANDED(SUITE, DISABLE_TEST_LINUX(TEST_NAME)) #endif // defined(__linux__) -#define LINUX_ONLY_DISABLED(TEST_NAME) DISABLED_##TEST_NAME - #endif // LINUX_UTILS_H diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index a7ac55f5b4edc..1113372fd02de 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -3973,23 +3973,21 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeCenterParagraph)) { ASSERT_TRUE(Snapshot()); } -// 10/19/20 Temporarily disabled tests while being they are being fixed. -// https://github.com/flutter/flutter/issues/68493 -TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineLeftAlign)) { +TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineLeftAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::ltr; paragraph_style.text_align = TextAlign::left; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4056,9 +4054,9 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 77); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 77); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 85); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 85); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4068,26 +4066,26 @@ TEST_F(ParagraphTest, EXPECT_EQ(boxes.size(), 1ull); EXPECT_FLOAT_EQ(boxes[0].rect.left(), 27); EXPECT_FLOAT_EQ(boxes[0].rect.right(), 27); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } -TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRightAlign)) { +TEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineRightAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::ltr; paragraph_style.text_align = TextAlign::right; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4156,7 +4154,7 @@ TEST_F(ParagraphTest, EXPECT_EQ(boxes.size(), 1ull); EXPECT_FLOAT_EQ(boxes[0].rect.left(), 550); EXPECT_FLOAT_EQ(boxes[0].rect.right(), 550); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4164,28 +4162,29 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 483); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 483); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 478); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 478); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeCenterParagraphNewlineCentered)) { + LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineCentered)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::ltr; paragraph_style.text_align = TextAlign::center; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4252,9 +4251,9 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 313); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 313); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 317); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 317); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4262,28 +4261,29 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 255); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 255); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 252); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 252); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRTLLeftAlign)) { + LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLLeftAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::rtl; paragraph_style.text_align = TextAlign::left; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4352,7 +4352,7 @@ TEST_F(ParagraphTest, EXPECT_EQ(boxes.size(), 1ull); EXPECT_FLOAT_EQ(boxes[0].rect.left(), 55); EXPECT_FLOAT_EQ(boxes[0].rect.right(), 55); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4362,26 +4362,27 @@ TEST_F(ParagraphTest, EXPECT_EQ(boxes.size(), 1ull); EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0); EXPECT_FLOAT_EQ(boxes[0].rect.right(), 0); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeParagraphNewlineRTLRightAlign)) { + LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLRightAlign)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::rtl; paragraph_style.text_align = TextAlign::right; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4448,9 +4449,9 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 527); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 527); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 519); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 519); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4458,28 +4459,29 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 456); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 456); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 451); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 451); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } TEST_F(ParagraphTest, - LINUX_ONLY_DISABLED(GetRectsForRangeCenterParagraphNewlineRTLCentered)) { + LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineRTLCentered)) { const char* text = "01234\n\nعab\naعلی\n"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); txt::ParagraphStyle paragraph_style; + paragraph_style.font_family = "Roboto"; paragraph_style.max_lines = 10; paragraph_style.text_direction = TextDirection::rtl; paragraph_style.text_align = TextAlign::center; txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); txt::TextStyle text_style; - text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families = {"Roboto", "Noto Naskh Arabic"}; text_style.font_size = 50; text_style.letter_spacing = 0; text_style.font_weight = FontWeight::w500; @@ -4546,9 +4548,9 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 291); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 291); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 134); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 287); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 287); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160); boxes = paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style); @@ -4556,9 +4558,9 @@ TEST_F(ParagraphTest, GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 228); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 228); - EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 193); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 225); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 225); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245); ASSERT_TRUE(Snapshot()); } From 43b618e4bf957d58c90fadcb0857d6016844fc03 Mon Sep 17 00:00:00 2001 From: puelo Date: Tue, 20 Oct 2020 01:22:02 +0200 Subject: [PATCH 173/219] Added keyEvent support for iOS 13.4+ (#20972) --- .../ios/framework/Headers/FlutterEngine.h | 8 + .../ios/framework/Source/FlutterEngine.mm | 10 ++ .../framework/Source/FlutterViewController.mm | 52 +++++++ .../Source/FlutterViewControllerTest.mm | 147 ++++++++++++++++++ 4 files changed, 217 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index 98ccb8354896e..7583a8d7aa04d 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -322,6 +322,14 @@ FLUTTER_EXPORT */ @property(nonatomic, readonly) FlutterBasicMessageChannel* settingsChannel; +/** + * The `FlutterBasicMessageChannel` used for communicating key events + * from physical keyboards + * + * Can be nil after `destroyContext` is called. + */ +@property(nonatomic, readonly) FlutterBasicMessageChannel* keyEventChannel; + /** * The `NSURL` of the observatory for the service isolate. * diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 2c55925a2c4d5..d6d34cda5364b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -81,6 +81,7 @@ @implementation FlutterEngine { fml::scoped_nsobject _lifecycleChannel; fml::scoped_nsobject _systemChannel; fml::scoped_nsobject _settingsChannel; + fml::scoped_nsobject _keyEventChannel; int64_t _nextTextureId; @@ -350,6 +351,9 @@ - (FlutterBasicMessageChannel*)systemChannel { - (FlutterBasicMessageChannel*)settingsChannel { return _settingsChannel.get(); } +- (FlutterBasicMessageChannel*)keyEventChannel { + return _keyEventChannel.get(); +} - (NSURL*)observatoryUrl { return [_publisher.get() url]; @@ -364,6 +368,7 @@ - (void)resetChannels { _lifecycleChannel.reset(); _systemChannel.reset(); _settingsChannel.reset(); + _keyEventChannel.reset(); } - (void)startProfiler:(NSString*)threadLabel { @@ -436,6 +441,11 @@ - (void)setupChannels { binaryMessenger:self.binaryMessenger codec:[FlutterJSONMessageCodec sharedInstance]]); + _keyEventChannel.reset([[FlutterBasicMessageChannel alloc] + initWithName:@"flutter/keyevent" + binaryMessenger:self.binaryMessenger + codec:[FlutterJSONMessageCodec sharedInstance]]); + _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); _textInputPlugin.get().textInputDelegate = self; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ed7e1d79fd56d..afd4db122dcbd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1024,6 +1024,58 @@ - (void)keyboardWillBeHidden:(NSNotification*)notification { [self updateViewportMetrics]; } +- (void)dispatchPresses:(NSSet*)presses API_AVAILABLE(ios(13.4)) { + if (@available(iOS 13.4, *)) { + for (UIPress* press in presses) { + if (press.key == nil || press.phase == UIPressPhaseStationary || + press.phase == UIPressPhaseChanged) { + continue; + } + NSMutableDictionary* keyMessage = [@{ + @"keymap" : @"ios", + @"type" : @"unknown", + @"keyCode" : @(press.key.keyCode), + @"modifiers" : @(press.key.modifierFlags), + @"characters" : press.key.characters, + @"charactersIgnoringModifiers" : press.key.charactersIgnoringModifiers + } mutableCopy]; + + if (press.phase == UIPressPhaseBegan) { + keyMessage[@"type"] = @"keydown"; + } else if (press.phase == UIPressPhaseEnded || press.phase == UIPressPhaseCancelled) { + keyMessage[@"type"] = @"keyup"; + } + + [[_engine.get() keyEventChannel] sendMessage:keyMessage]; + } + } +} + +- (void)pressesBegan:(NSSet*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) { + if (@available(iOS 13.4, *)) { + [self dispatchPresses:presses]; + } +} + +- (void)pressesChanged:(NSSet*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) { + if (@available(iOS 13.4, *)) { + [self dispatchPresses:presses]; + } +} + +- (void)pressesEnded:(NSSet*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) { + if (@available(iOS 13.4, *)) { + [self dispatchPresses:presses]; + } +} + +- (void)pressesCancelled:(NSSet*)presses + withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) { + if (@available(iOS 13.4, *)) { + [self dispatchPresses:presses]; + } +} + #pragma mark - Orientation updates - (void)onOrientationPreferencesUpdated:(NSNotification*)notification { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index f15ea37666319..9f4348dd6812f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -62,6 +62,7 @@ - (UIAccessibilityContrast)accessibilityContrast; @interface FlutterViewController (Tests) - (void)surfaceUpdated:(BOOL)appeared; - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences; +- (void)dispatchPresses:(NSSet*)presses; @end @implementation FlutterViewControllerTest @@ -549,4 +550,150 @@ - (void)testNotifyLowMemory { OCMVerify([engine notifyLowMemory]); } +- (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) { + if (@available(iOS 13.4, *)) { + // noop + } else { + return; + } + + id engine = OCMClassMock([FlutterEngine class]); + + id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine keyEventChannel]).andReturn(keyEventChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + + id testSet = [self fakeUiPressSetForPhase:UIPressPhaseBegan + keyCode:UIKeyboardHIDUsageKeyboardA + modifierFlags:UIKeyModifierShift + characters:@"a" + charactersIgnoringModifiers:@"A"]; + + // Exercise behavior under test. + [vc dispatchPresses:testSet]; + + // Verify behavior. + OCMVerify([keyEventChannel + sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { + return [message[@"keymap"] isEqualToString:@"ios"] && + [message[@"type"] isEqualToString:@"keydown"] && + [message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] && + [message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] && + [message[@"characters"] isEqualToString:@"a"] && + [message[@"charactersIgnoringModifiers"] isEqualToString:@"A"]; + }]]); + + // Clean up mocks + [engine stopMocking]; + [keyEventChannel stopMocking]; +} + +- (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) { + if (@available(iOS 13.4, *)) { + // noop + } else { + return; + } + + id engine = OCMClassMock([FlutterEngine class]); + + id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine keyEventChannel]).andReturn(keyEventChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + + id testSet = [self fakeUiPressSetForPhase:UIPressPhaseEnded + keyCode:UIKeyboardHIDUsageKeyboardA + modifierFlags:UIKeyModifierShift + characters:@"a" + charactersIgnoringModifiers:@"A"]; + + // Exercise behavior under test. + [vc dispatchPresses:testSet]; + + // Verify behavior. + OCMVerify([keyEventChannel + sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { + return [message[@"keymap"] isEqualToString:@"ios"] && + [message[@"type"] isEqualToString:@"keyup"] && + [message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] && + [message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] && + [message[@"characters"] isEqualToString:@"a"] && + [message[@"charactersIgnoringModifiers"] isEqualToString:@"A"]; + }]]); + + // Clean up mocks + [engine stopMocking]; + [keyEventChannel stopMocking]; +} + +- (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) { + if (@available(iOS 13.4, *)) { + // noop + } else { + return; + } + + id engine = OCMClassMock([FlutterEngine class]); + + id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); + OCMStub([engine keyEventChannel]).andReturn(keyEventChannel); + + FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + + id emptySet = [NSSet set]; + id ignoredSet = [self fakeUiPressSetForPhase:UIPressPhaseStationary + keyCode:UIKeyboardHIDUsageKeyboardA + modifierFlags:UIKeyModifierShift + characters:@"a" + charactersIgnoringModifiers:@"A"]; + + id mockUiPress = OCMClassMock([UIPress class]); + OCMStub([mockUiPress phase]).andReturn(UIPressPhaseBegan); + id emptyKeySet = [NSSet setWithArray:@[ mockUiPress ]]; + // Exercise behavior under test. + [vc dispatchPresses:emptySet]; + [vc dispatchPresses:ignoredSet]; + [vc dispatchPresses:emptyKeySet]; + + // Verify behavior. + OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]); + + // Clean up mocks + [engine stopMocking]; + [keyEventChannel stopMocking]; +} + +- (NSSet*)fakeUiPressSetForPhase:(UIPressPhase)phase + keyCode:(UIKeyboardHIDUsage)keyCode + modifierFlags:(UIKeyModifierFlags)modifierFlags + characters:(NSString*)characters + charactersIgnoringModifiers:(NSString*)charactersIgnoringModifiers + API_AVAILABLE(ios(13.4)) { + if (@available(iOS 13.4, *)) { + // noop + } else { + return [NSSet set]; + } + id mockUiPress = OCMClassMock([UIPress class]); + OCMStub([mockUiPress phase]).andReturn(phase); + + id mockUiKey = OCMClassMock([UIKey class]); + OCMStub([mockUiKey keyCode]).andReturn(keyCode); + OCMStub([mockUiKey modifierFlags]).andReturn(modifierFlags); + OCMStub([mockUiKey characters]).andReturn(characters); + OCMStub([mockUiKey charactersIgnoringModifiers]).andReturn(charactersIgnoringModifiers); + + OCMStub([mockUiPress key]).andReturn(mockUiKey); + + return [NSSet setWithArray:@[ mockUiPress ]]; +} + @end From a6857802b716e9ad38e612d5b74b1c77b3d25265 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 19 Oct 2020 16:22:36 -0700 Subject: [PATCH 174/219] Fix the initialization of AndroidSurfaceFactoryImpl (#21977) Fixes https://github.com/flutter/flutter/issues/68446 --- .../platform/android/platform_view_android.cc | 20 ++++++++++--------- .../platform/android/platform_view_android.h | 6 ++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 0fc9af5c59b51..8ebac2aa929b9 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -30,13 +30,16 @@ namespace flutter { AndroidSurfaceFactoryImpl::AndroidSurfaceFactoryImpl( std::shared_ptr context, - std::shared_ptr jni_facade, - std::weak_ptr external_view_embedder) - : android_context_(context), jni_facade_(jni_facade), - external_view_embedder_(external_view_embedder) {} + std::shared_ptr jni_facade) + : android_context_(context), jni_facade_(jni_facade) {} AndroidSurfaceFactoryImpl::~AndroidSurfaceFactoryImpl() = default; +void AndroidSurfaceFactoryImpl::SetExternalViewEmbedder( + std::shared_ptr external_view_embedder) { + external_view_embedder_ = external_view_embedder; +} + std::unique_ptr AndroidSurfaceFactoryImpl::CreateSurface() { std::shared_ptr external_view_embedder = external_view_embedder_.lock(); @@ -84,12 +87,11 @@ PlatformViewAndroid::PlatformViewAndroid( FML_CHECK(android_context && android_context->IsValid()) << "Could not create an Android context."; - external_view_embedder_ = - std::make_shared(android_context, jni_facade, - surface_factory_); surface_factory_ = - std::make_shared(android_context, jni_facade, - external_view_embedder_); + std::make_shared(android_context, jni_facade); + external_view_embedder_ = std::make_shared( + android_context, jni_facade, surface_factory_); + surface_factory_->SetExternalViewEmbedder(external_view_embedder_); android_surface_ = surface_factory_->CreateSurface(); FML_CHECK(android_surface_ && android_surface_->IsValid()) diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 2ae51464df3c2..b1bf8194976f5 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -25,13 +25,15 @@ namespace flutter { class AndroidSurfaceFactoryImpl : public AndroidSurfaceFactory { public: AndroidSurfaceFactoryImpl(std::shared_ptr context, - std::shared_ptr jni_facade, - std::weak_ptr external_view_embedder); + std::shared_ptr jni_facade); ~AndroidSurfaceFactoryImpl() override; std::unique_ptr CreateSurface() override; + void SetExternalViewEmbedder( + std::shared_ptr external_view_embedder); + private: std::shared_ptr android_context_; std::shared_ptr jni_facade_; From 895940081c054f6ce8d5673cb5abf5f2fd83d851 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Mon, 19 Oct 2020 16:22:40 -0700 Subject: [PATCH 175/219] [null-safety] fix types of layer code (#21959) --- lib/ui/compositing.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index f484f0e53c521..d29e5f1a8e57d 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -538,7 +538,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { return layer; } - EngineLayer _pushShaderMask( + void _pushShaderMask( EngineLayer engineLayer, Shader shader, double maskRectLeft, @@ -587,7 +587,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { return layer; } - EngineLayer _pushPhysicalShape(EngineLayer outEngineLayer, Path path, double elevation, int color, int shadowColor, + void _pushPhysicalShape(EngineLayer outEngineLayer, Path path, double elevation, int color, int shadowColor, int clipBehavior) native 'SceneBuilder_pushPhysicalShape'; /// Ends the effect of the most recently pushed operation. From 9946be45a926adb6b3f7659367406638358c7d10 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 20 Oct 2020 01:23:10 +0200 Subject: [PATCH 176/219] FlTextInputPlugin: fix memory leaks (#21879) This PR fixes a few small memory leaks in FlTextInputPlugin. All three cases are creating temporary FlValue instances for lookups and comparison without ever releasing them. --- shell/platform/linux/fl_text_input_plugin.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index 11dc8489f1a21..b63e00c9b85bb 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -183,12 +183,13 @@ static FlMethodResponse* set_client(FlTextInputPlugin* self, FlValue* args) { // Clear the multiline flag, then set it only if the field is multiline. self->input_multiline = FALSE; FlValue* input_type_value = - fl_value_lookup(config_value, fl_value_new_string(kTextInputTypeKey)); + fl_value_lookup_string(config_value, kTextInputTypeKey); if (fl_value_get_type(input_type_value) == FL_VALUE_TYPE_MAP) { - FlValue* input_type_name = fl_value_lookup( - input_type_value, fl_value_new_string(kTextInputTypeNameKey)); - if (fl_value_equal(input_type_name, - fl_value_new_string(kMultilineInputType))) { + FlValue* input_type_name = + fl_value_lookup_string(input_type_value, kTextInputTypeNameKey); + if (fl_value_get_type(input_type_name) == FL_VALUE_TYPE_STRING && + g_strcmp0(fl_value_get_string(input_type_name), kMultilineInputType) == + 0) { self->input_multiline = TRUE; } } From bcc557f1ba423c18f17a1d763a25c557e4119532 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 19 Oct 2020 16:25:56 -0700 Subject: [PATCH 177/219] Add multi-step input method support for Linux (#21897) This implements the Gtk hooks required to support multi-step input methods on Linux. This builds on the support for composing regions (preedit region in Gtk terminology) added to TextInputModel in https://github.com/flutter/engine/pull/21682. Specifically, the following changes are included: 1. Add handler for TextInput.setMarkedTextRegion framework messages: On any change to the EditableText in the framework, this message is sent which provides an updated rect (in the local co-ordinates of the EditableText) for the composing region. If not in composing mode, the cursor rect is sent. 2. Add handler for TextInput.setEditableSizeAndTransform framework messages: On any change to the RenderObject underlying the EditableText, an updated size for the full EditableText widget, as well as an affine transform matrix from local co-ordinates to Flutter root co-ordinates is sent. 3. On either of the above messages, we use the transformed composing rect to compute the cursor position in Gtk window co-ordinates and inform Gtk, so that it can position any system IM composing window correctly for on-the-spot composing, such as is used when inputting Japanese text. 4. Adds handlers for preedit-start, preedit-changed, and preedit-end signals from Gtk. These are passed on to the TextInputModel. 5. Updates the preedit-commit handler to commit the composing region to the text or, if not composing, insert new text at the cursor. 6. Updates the handler for TextInput.setEditingState framework messages to extract the composing range base and extent and pass these on to TextInputModel. 7. Updates update_editing_state function to set composing base and extent on text input state updates sent to the framework. --- shell/platform/linux/fl_text_input_plugin.cc | 178 ++++++++++++++++++- shell/platform/linux/fl_text_input_plugin.h | 5 +- shell/platform/linux/fl_view.cc | 2 +- 3 files changed, 180 insertions(+), 5 deletions(-) diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index b63e00c9b85bb..b89e184238c9d 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -22,6 +22,9 @@ static constexpr char kHideMethod[] = "TextInput.hide"; static constexpr char kUpdateEditingStateMethod[] = "TextInputClient.updateEditingState"; static constexpr char kPerformActionMethod[] = "TextInputClient.performAction"; +static constexpr char kSetEditableSizeAndTransform[] = + "TextInput.setEditableSizeAndTransform"; +static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect"; static constexpr char kInputActionKey[] = "inputAction"; static constexpr char kTextInputTypeKey[] = "inputType"; @@ -34,6 +37,8 @@ static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional"; static constexpr char kComposingBaseKey[] = "composingBase"; static constexpr char kComposingExtentKey[] = "composingExtent"; +static constexpr char kTransform[] = "transform"; + static constexpr char kTextAffinityDownstream[] = "TextAffinity.downstream"; static constexpr char kMultilineInputType[] = "TextInputType.multiline"; @@ -57,6 +62,19 @@ struct _FlTextInputPlugin { GtkIMContext* im_context; flutter::TextInputModel* text_model; + + // The owning Flutter view. + FlView* view; + + // A 4x4 matrix that maps from `EditableText` local coordinates to the + // coordinate system of `PipelineOwner.rootNode`. + double editabletext_transform[4][4]; + + // The smallest rect, in local coordinates, of the text in the composing + // range, or of the caret in the case where there is no current composing + // range. This value is updated via `TextInput.setMarkedTextRect` messages + // over the text input channel. + GdkRectangle composing_rect; }; G_DEFINE_TYPE(FlTextInputPlugin, fl_text_input_plugin, G_TYPE_OBJECT) @@ -99,13 +117,22 @@ static void update_editing_state(FlTextInputPlugin* self) { fl_value_set_string_take(value, kSelectionExtentKey, fl_value_new_int(selection.extent())); + int composing_base = self->text_model->composing() + ? self->text_model->composing_range().base() + : -1; + int composing_extent = self->text_model->composing() + ? self->text_model->composing_range().extent() + : -1; + fl_value_set_string_take(value, kComposingBaseKey, + fl_value_new_int(composing_base)); + fl_value_set_string_take(value, kComposingExtentKey, + fl_value_new_int(composing_extent)); + // The following keys are not implemented and set to default values. fl_value_set_string_take(value, kSelectionAffinityKey, fl_value_new_string(kTextAffinityDownstream)); fl_value_set_string_take(value, kSelectionIsDirectionalKey, fl_value_new_bool(FALSE)); - fl_value_set_string_take(value, kComposingBaseKey, fl_value_new_int(-1)); - fl_value_set_string_take(value, kComposingExtentKey, fl_value_new_int(-1)); fl_value_append(args, value); @@ -138,9 +165,41 @@ static void perform_action(FlTextInputPlugin* self) { nullptr, perform_action_response_cb, self); } +// Signal handler for GtkIMContext::preedit-start +static void im_preedit_start_cb(FlTextInputPlugin* self) { + self->text_model->BeginComposing(); + + // Set the top-level window used for system input method windows. + GdkWindow* window = + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self->view))); + gtk_im_context_set_client_window(self->im_context, window); +} + +// Signal handler for GtkIMContext::preedit-changed +static void im_preedit_changed_cb(FlTextInputPlugin* self) { + g_autofree gchar* buf = nullptr; + gint cursor_offset = 0; + gtk_im_context_get_preedit_string(self->im_context, &buf, nullptr, + &cursor_offset); + cursor_offset += self->text_model->composing_range().base(); + self->text_model->UpdateComposingText(buf); + self->text_model->SetSelection(TextRange(cursor_offset, cursor_offset)); + + update_editing_state(self); +} + // Signal handler for GtkIMContext::commit static void im_commit_cb(FlTextInputPlugin* self, const gchar* text) { self->text_model->AddText(text); + if (self->text_model->composing()) { + self->text_model->CommitComposing(); + } + update_editing_state(self); +} + +// Signal handler for GtkIMContext::preedit-end +static void im_preedit_end_cb(FlTextInputPlugin* self) { + self->text_model->EndComposing(); update_editing_state(self); } @@ -209,6 +268,8 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, FlValue* args) { const gchar* text = fl_value_get_string(fl_value_lookup_string(args, kTextKey)); + self->text_model->SetText(text); + int64_t selection_base = fl_value_get_int(fl_value_lookup_string(args, kSelectionBaseKey)); int64_t selection_extent = @@ -221,6 +282,19 @@ static FlMethodResponse* set_editing_state(FlTextInputPlugin* self, self->text_model->SetText(text); self->text_model->SetSelection(TextRange(selection_base, selection_extent)); + int64_t composing_base = + fl_value_get_int(fl_value_lookup_string(args, kComposingBaseKey)); + int64_t composing_extent = + fl_value_get_int(fl_value_lookup_string(args, kComposingExtentKey)); + if (composing_base == -1 && composing_extent == -1) { + self->text_model->EndComposing(); + } else { + size_t composing_start = std::min(composing_base, composing_extent); + size_t cursor_offset = selection_base - composing_start; + self->text_model->SetComposingRange( + TextRange(composing_base, composing_extent), cursor_offset); + } + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } @@ -238,6 +312,83 @@ static FlMethodResponse* hide(FlTextInputPlugin* self) { return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } +// Update the IM cursor position. +// +// As text is input by the user, the framework sends two streams of updates +// over the text input channel: updates to the composing rect (cursor rect when +// not in IME composing mode) and updates to the matrix transform from local +// coordinates to Flutter root coordinates. This function is called after each +// of these updates. It transforms the composing rect to GTK window coordinates +// and notifies GTK of the updated cursor position. +static void update_im_cursor_position(FlTextInputPlugin* self) { + // Skip update if not composing to avoid setting to position 0. + if (!self->text_model->composing()) { + return; + } + + // Transform the x, y positions of the cursor from local coordinates to + // Flutter view coordinates. + gint x = self->composing_rect.x * self->editabletext_transform[0][0] + + self->composing_rect.y * self->editabletext_transform[1][0] + + self->editabletext_transform[3][0] + self->composing_rect.width; + gint y = self->composing_rect.x * self->editabletext_transform[0][1] + + self->composing_rect.y * self->editabletext_transform[1][1] + + self->editabletext_transform[3][1] + self->composing_rect.height; + + // Transform from Flutter view coordinates to GTK window coordinates. + GdkRectangle preedit_rect; + gtk_widget_translate_coordinates( + GTK_WIDGET(self->view), gtk_widget_get_toplevel(GTK_WIDGET(self->view)), + x, y, &preedit_rect.x, &preedit_rect.y); + + // Set the cursor location in window coordinates so that GTK can position any + // system input method windows. + gtk_im_context_set_cursor_location(self->im_context, &preedit_rect); +} + +// Handles updates to the EditableText size and position from the framework. +// +// On changes to the size or position of the RenderObject underlying the +// EditableText, this update may be triggered. It provides an updated size and +// transform from the local coordinate system of the EditableText to root +// Flutter coordinate system. +static FlMethodResponse* set_editable_size_and_transform( + FlTextInputPlugin* self, + FlValue* args) { + FlValue* transform = fl_value_lookup_string(args, kTransform); + size_t transform_len = fl_value_get_length(transform); + g_warn_if_fail(transform_len == 16); + + for (size_t i = 0; i < transform_len; ++i) { + double val = fl_value_get_float(fl_value_get_list_value(transform, i)); + self->editabletext_transform[i / 4][i % 4] = val; + } + update_im_cursor_position(self); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Handles updates to the composing rect from the framework. +// +// On changes to the state of the EditableText in the framework, this update +// may be triggered. It provides an updated rect for the composing region in +// local coordinates of the EditableText. In the case where there is no +// composing region, the cursor rect is sent. +static FlMethodResponse* set_marked_text_rect(FlTextInputPlugin* self, + FlValue* args) { + self->composing_rect.x = + fl_value_get_float(fl_value_lookup_string(args, "x")); + self->composing_rect.y = + fl_value_get_float(fl_value_lookup_string(args, "y")); + self->composing_rect.width = + fl_value_get_float(fl_value_lookup_string(args, "width")); + self->composing_rect.height = + fl_value_get_float(fl_value_lookup_string(args, "height")); + update_im_cursor_position(self); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + // Called when a method call is received from Flutter. static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, @@ -258,6 +409,10 @@ static void method_call_cb(FlMethodChannel* channel, response = clear_client(self); } else if (strcmp(method, kHideMethod) == 0) { response = hide(self); + } else if (strcmp(method, kSetEditableSizeAndTransform) == 0) { + response = set_editable_size_and_transform(self, args); + } else if (strcmp(method, kSetMarkedTextRect) == 0) { + response = set_marked_text_rect(self, args); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } @@ -268,6 +423,11 @@ static void method_call_cb(FlMethodChannel* channel, } } +static void view_weak_notify_cb(gpointer user_data, GObject* object) { + FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object); + self->view = nullptr; +} + static void fl_text_input_plugin_dispose(GObject* object) { FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN(object); @@ -290,6 +450,15 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) { self->client_id = kClientIdUnset; self->im_context = gtk_im_multicontext_new(); self->input_multiline = FALSE; + g_signal_connect_object(self->im_context, "preedit-start", + G_CALLBACK(im_preedit_start_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "preedit-end", + G_CALLBACK(im_preedit_end_cb), self, + G_CONNECT_SWAPPED); + g_signal_connect_object(self->im_context, "preedit-changed", + G_CALLBACK(im_preedit_changed_cb), self, + G_CONNECT_SWAPPED); g_signal_connect_object(self->im_context, "commit", G_CALLBACK(im_commit_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object(self->im_context, "retrieve-surrounding", @@ -301,7 +470,8 @@ static void fl_text_input_plugin_init(FlTextInputPlugin* self) { self->text_model = new flutter::TextInputModel(); } -FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) { +FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, + FlView* view) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN( @@ -312,6 +482,8 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger) { fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, nullptr); + self->view = view; + g_object_weak_ref(G_OBJECT(view), view_weak_notify_cb, self); return self; } diff --git a/shell/platform/linux/fl_text_input_plugin.h b/shell/platform/linux/fl_text_input_plugin.h index 14ab5cfc04417..d68f5903c17a6 100644 --- a/shell/platform/linux/fl_text_input_plugin.h +++ b/shell/platform/linux/fl_text_input_plugin.h @@ -8,6 +8,7 @@ #include #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" G_BEGIN_DECLS @@ -27,13 +28,15 @@ G_DECLARE_FINAL_TYPE(FlTextInputPlugin, /** * fl_text_input_plugin_new: * @messenger: an #FlBinaryMessenger. + * @view: the #FlView with which the text input plugin is associated. * * Creates a new plugin that implements SystemChannels.textInput from the * Flutter services library. * * Returns: a new #FlTextInputPlugin. */ -FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger); +FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, + FlView* view); /** * fl_text_input_plugin_filter_keypress diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 01711d125e3b8..b65e3da7d4297 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -161,7 +161,7 @@ static void fl_view_constructed(GObject* object) { self->key_event_plugin = fl_key_event_plugin_new(messenger); self->mouse_cursor_plugin = fl_mouse_cursor_plugin_new(messenger, self); self->platform_plugin = fl_platform_plugin_new(messenger); - self->text_input_plugin = fl_text_input_plugin_new(messenger); + self->text_input_plugin = fl_text_input_plugin_new(messenger, self); } static void fl_view_set_property(GObject* object, From 6fdcdd99ccdcf8f648e0619721393a0ee5f8dd42 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 19 Oct 2020 16:27:03 -0700 Subject: [PATCH 178/219] Forward font collection APIs to the SkParagraph font collection (#21734) --- third_party/txt/src/txt/font_collection.cc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/third_party/txt/src/txt/font_collection.cc b/third_party/txt/src/txt/font_collection.cc index 5f895ef41afee..6820f14264eeb 100644 --- a/third_party/txt/src/txt/font_collection.cc +++ b/third_party/txt/src/txt/font_collection.cc @@ -85,6 +85,12 @@ FontCollection::FontCollection() : enable_font_fallback_(true) {} FontCollection::~FontCollection() { minikin::Layout::purgeCaches(); + +#if FLUTTER_ENABLE_SKSHAPER + if (skt_collection_) { + skt_collection_->clearCaches(); + } +#endif } size_t FontCollection::GetFontManagersCount() const { @@ -127,6 +133,12 @@ std::vector> FontCollection::GetFontManagerOrder() const { void FontCollection::DisableFontFallback() { enable_font_fallback_ = false; + +#if FLUTTER_ENABLE_SKSHAPER + if (skt_collection_) { + skt_collection_->disableFontFallback(); + } +#endif } std::shared_ptr @@ -346,8 +358,13 @@ FontCollection::GetFallbackFontFamily(const sk_sp& manager, void FontCollection::ClearFontFamilyCache() { font_collections_cache_.clear(); -} +#if FLUTTER_ENABLE_SKSHAPER + if (skt_collection_) { + skt_collection_->clearCaches(); + } +#endif +} #if FLUTTER_ENABLE_SKSHAPER sk_sp From 25d8fa5a79cb0228e639601822598ada49695ff6 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 19 Oct 2020 16:31:07 -0700 Subject: [PATCH 179/219] Define SK_VULKAN for clang-tidy runs (#21927) When linting flutter/vulkan/vulkan_window.cc, the call to GrDirectContext::MakeVulkan is undefined when SK_VULKAN is not defined, triggering a lint error. Bug: https://github.com/flutter/flutter/issues/68331 --- ci/bin/lint.dart | 6 ++++++ vulkan/vulkan_window.cc | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ci/bin/lint.dart b/ci/bin/lint.dart index 695473265701a..2f2f98abf2c8c 100644 --- a/ci/bin/lint.dart +++ b/ci/bin/lint.dart @@ -28,6 +28,11 @@ https://github.com/flutter/flutter/wiki/Engine-Clang-Tidy-Linter const String issueUrlPrefix = 'https://github.com/flutter/flutter/issues'; +/// Symbol definitions passed to clang-tidy. +const List clangTidyDefineArgs = [ + "-DSK_VULKAN", // See: https://github.com/flutter/flutter/issues/68331 +]; + class Command { Directory directory = Directory(''); String command = ''; @@ -46,6 +51,7 @@ String calcTidyArgs(Command command) { String result = command.command; result = result.replaceAll(RegExp(r'\S*clang/bin/clang'), ''); result = result.replaceAll(RegExp(r'-MF \S*'), ''); + result += ' ' + clangTidyDefineArgs.join(' '); return result; } diff --git a/vulkan/vulkan_window.cc b/vulkan/vulkan_window.cc index 2c4618f06e796..49f8e5d606bcd 100644 --- a/vulkan/vulkan_window.cc +++ b/vulkan/vulkan_window.cc @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/68331 - #include "vulkan_window.h" #include From 0c7c477245041d02220ae322b3d601eb3cc05bf9 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 19 Oct 2020 16:32:03 -0700 Subject: [PATCH 180/219] Revert "[ios] Refactor IOSSurface factory and unify surface creation (#21877)" (#21970) --- ci/licenses_golden/licenses_flutter | 2 - shell/platform/darwin/ios/BUILD.gn | 2 - .../ios/framework/Source/FlutterEngine.mm | 26 ++++-------- .../Source/FlutterEnginePlatformViewTest.mm | 1 - .../ios/framework/Source/FlutterOverlayView.h | 2 + .../framework/Source/FlutterOverlayView.mm | 8 ++++ .../framework/Source/FlutterPlatformViews.mm | 9 ++-- .../Source/FlutterPlatformViewsTest.mm | 42 ++++--------------- .../Source/FlutterPlatformViews_Internal.h | 9 +--- .../Source/FlutterPlatformViews_Internal.mm | 5 +-- .../darwin/ios/framework/Source/FlutterView.h | 1 + .../ios/framework/Source/FlutterView.mm | 9 ++++ .../Source/accessibility_bridge_test.mm | 17 +------- .../platform/darwin/ios/ios_surface_factory.h | 39 ----------------- .../darwin/ios/ios_surface_factory.mm | 30 ------------- shell/platform/darwin/ios/platform_view_ios.h | 3 -- .../platform/darwin/ios/platform_view_ios.mm | 8 +--- 17 files changed, 46 insertions(+), 167 deletions(-) delete mode 100644 shell/platform/darwin/ios/ios_surface_factory.h delete mode 100644 shell/platform/darwin/ios/ios_surface_factory.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a9d8d5cf71974..b1018822b05e9 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1010,8 +1010,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.mm -FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_factory.h -FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_factory.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.h diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 05c39560e265b..793c97be93701 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -99,8 +99,6 @@ source_set("flutter_framework_source") { "ios_render_target_gl.mm", "ios_surface.h", "ios_surface.mm", - "ios_surface_factory.h", - "ios_surface_factory.mm", "ios_surface_gl.h", "ios_surface_gl.mm", "ios_surface_software.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index d6d34cda5364b..cbd93e978fd6a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -28,9 +28,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" -#import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" -#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" #include "flutter/shell/profiling/sampling_profiler.h" @@ -65,7 +63,6 @@ @implementation FlutterEngine { fml::WeakPtr _viewController; fml::scoped_nsobject _publisher; - std::shared_ptr _surfaceFactory; std::unique_ptr _platformViewsController; std::unique_ptr _profiler_metrics; std::unique_ptr _profiler; @@ -131,7 +128,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix _pluginPublications = [NSMutableDictionary new]; _registrars = [[NSMutableDictionary alloc] init]; - [self ensurePlatformViewController]; + _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); _binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self]; _connections.reset(new flutter::ConnectionCollection()); @@ -165,16 +162,6 @@ - (instancetype)initWithName:(NSString*)labelPrefix return self; } -- (void)ensurePlatformViewController { - if (!_platformViewsController) { - auto renderingApi = flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering); - _surfaceFactory = flutter::IOSSurfaceFactory::Create(renderingApi); - auto pvc = new flutter::FlutterPlatformViewsController(_surfaceFactory); - _surfaceFactory->SetPlatformViewsController(pvc); - _platformViewsController.reset(pvc); - } -} - - (void)dealloc { /// Notify plugins of dealloc. This should happen first in dealloc since the /// plugins may be talking to things like the binaryMessenger. @@ -533,13 +520,13 @@ - (BOOL)createShell:(NSString*)entrypoint threadHostType}; // Lambda captures by pointers to ObjC objects are fine here because the - // create call is synchronous. + // create call is + // synchronous. flutter::Shell::CreateCallback on_create_platform_view = - [self](flutter::Shell& shell) { - [self ensurePlatformViewController]; + [](flutter::Shell& shell) { return std::make_unique( shell, flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering), - self->_surfaceFactory, shell.GetTaskRunners()); + shell.GetTaskRunners()); }; flutter::Shell::CreateCallback on_create_rasterizer = @@ -567,6 +554,9 @@ - (BOOL)createShell:(NSString*)entrypoint [self setupChannels]; [self onLocaleUpdated:nil]; [self initializeDisplays]; + if (!_platformViewsController) { + _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); + } _publisher.reset([[FlutterObservatoryPublisher alloc] initWithEnableObservatoryPublication:settings.enable_observatory_publication]); [self maybeSetupPlatformViewChannels]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 3e002e2aa78ee..a202eba4d8d8d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -67,7 +67,6 @@ - (void)testCallsNotifyLowMemory { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id project = OCMClassMock([FlutterDartProject class]); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h index b77ac7181853f..cb56163c6c5c2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h @@ -35,6 +35,8 @@ - (instancetype)init NS_DESIGNATED_INITIALIZER; - (instancetype)initWithContentsScale:(CGFloat)contentsScale; +- (std::unique_ptr)createSurface: + (std::shared_ptr)ios_context; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm index 345d3a36798cb..f6a0cd8239756 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm @@ -62,6 +62,14 @@ + (Class)layerClass { return [FlutterView layerClass]; } +- (std::unique_ptr)createSurface: + (std::shared_ptr)ios_context { + return flutter::IOSSurface::Create(std::move(ios_context), // context + fml::scoped_nsobject{[self.layer retain]}, // layer + nullptr // platform views controller + ); +} + // TODO(amirh): implement drawLayer to support snapshotting. @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 240134dc4d5b8..c0c185c6ee4b7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -16,7 +16,6 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" -#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" namespace flutter { @@ -33,8 +32,8 @@ overlay_view.reset([[FlutterOverlayView alloc] init]); overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); - auto ca_layer = fml::scoped_nsobject{[[overlay_view.get() layer] retain]}; - std::unique_ptr ios_surface = ios_surface_factory_->CreateSurface(ca_layer); + std::unique_ptr ios_surface = + [overlay_view.get() createSurface:std::move(ios_context)]; std::unique_ptr surface = ios_surface->CreateGPUSurface(); layer = std::make_shared( @@ -45,8 +44,8 @@ overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); - auto ca_layer = fml::scoped_nsobject{[[overlay_view.get() layer] retain]}; - std::unique_ptr ios_surface = ios_surface_factory_->CreateSurface(ca_layer); + std::unique_ptr ios_surface = + [overlay_view.get() createSurface:std::move(ios_context)]; std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); layer = std::make_shared( diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 0a890b7e92882..283a86cf2ae46 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -121,16 +121,12 @@ - (void)testCanCreatePlatformViewWithoutFlutterView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -179,16 +175,12 @@ - (void)testCompositePlatformView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -238,16 +230,12 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -313,16 +301,12 @@ - (void)testClipRect { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -389,16 +373,12 @@ - (void)testClipRRect { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -465,16 +445,12 @@ - (void)testClipPath { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; @@ -542,16 +518,12 @@ - (void)testSetFlutterViewControllerAfterCreateCanStillDispatchTouchEvents { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - auto surface_factory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surface_factory, /*task_runners=*/runners); - auto flutterPlatformViewsController = - std::make_unique(surface_factory); - surface_factory->SetPlatformViewsController(flutterPlatformViewsController.get()); + auto flutterPlatformViewsController = std::make_unique(); FlutterPlatformViewsTestMockFlutterPlatformFactory* factory = [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index d9638d75b6b15..4fcaaf3874212 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -61,7 +61,6 @@ void ResetAnchor(CALayer* layer); class IOSContextGL; class IOSSurface; -class IOSSurfaceFactory; struct FlutterPlatformViewLayer { FlutterPlatformViewLayer(fml::scoped_nsobject overlay_view, @@ -88,9 +87,7 @@ struct FlutterPlatformViewLayer { // This class isn't thread safe. class FlutterPlatformViewLayerPool { public: - FlutterPlatformViewLayerPool(std::shared_ptr ios_surface_factory) - : ios_surface_factory_(ios_surface_factory) {} - + FlutterPlatformViewLayerPool() = default; ~FlutterPlatformViewLayerPool() = default; // Gets a layer from the pool if available, or allocates a new one. @@ -121,14 +118,12 @@ class FlutterPlatformViewLayerPool { size_t available_layer_index_ = 0; std::vector> layers_; - const std::shared_ptr ios_surface_factory_; - FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); }; class FlutterPlatformViewsController { public: - FlutterPlatformViewsController(std::shared_ptr surface_factory); + FlutterPlatformViewsController(); ~FlutterPlatformViewsController(); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index a46684ccb1064..a6d3d03653b17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -23,9 +23,8 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; -FlutterPlatformViewsController::FlutterPlatformViewsController( - std::shared_ptr surface_factory) - : layer_pool_(std::make_unique(surface_factory)), +FlutterPlatformViewsController::FlutterPlatformViewsController() + : layer_pool_(std::make_unique()), weak_factory_(std::make_unique>(this)){}; FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.h b/shell/platform/darwin/ios/framework/Source/FlutterView.h index 13d7b6ec27cee..3a9c138de02aa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -32,6 +32,7 @@ - (instancetype)initWithDelegate:(id)delegate opaque:(BOOL)opaque NS_DESIGNATED_INITIALIZER; +- (std::unique_ptr)createSurface:(std::shared_ptr)context; // Set by FlutterEngine or FlutterViewController to override software rendering. @property(class, nonatomic) BOOL forceSoftwareRendering; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 73a9b8b529088..3f6b19decdac3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -83,6 +83,15 @@ + (Class)layerClass { flutter::GetRenderingAPIForProcess(FlutterView.forceSoftwareRendering)); } +- (std::unique_ptr)createSurface: + (std::shared_ptr)ios_context { + return flutter::IOSSurface::Create( + std::move(ios_context), // context + fml::scoped_nsobject{[self.layer retain]}, // layer + [_delegate platformViewsController] // platform views controller + ); +} + - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context { TRACE_EVENT0("flutter", "SnapshotFlutterView"); diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 754495cc527f4..41970e67c8f73 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -137,7 +137,6 @@ - (void)testCreate { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); auto bridge = std::make_unique(/*view=*/nil, @@ -157,7 +156,6 @@ - (void)testUpdateSemanticsEmpty { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -184,7 +182,6 @@ - (void)testUpdateSemanticsOneNode { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -227,12 +224,9 @@ - (void)testSemanticsDeallocated { /*raster=*/thread_task_runner, /*ui=*/thread_task_runner, /*io=*/thread_task_runner); - - auto surfaceFactory = flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware); auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - /*ios_surface_factory=*/surfaceFactory, /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -240,9 +234,8 @@ - (void)testSemanticsDeallocated { std::string label = "some label"; auto flutterPlatformViewsController = - std::make_unique(surfaceFactory); + std::make_unique(); flutterPlatformViewsController->SetFlutterView(mockFlutterView); - surfaceFactory->SetPlatformViewsController(flutterPlatformViewsController.get()); MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory new] autorelease]; flutterPlatformViewsController->RegisterViewFactory( @@ -286,7 +279,6 @@ - (void)testAnnouncesRouteChanges { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -352,7 +344,6 @@ - (void)testAnnouncesRouteChangesWhenNoNamesRoute { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -420,7 +411,6 @@ - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -487,7 +477,6 @@ - (void)testAnnouncesLayoutChangeWithLastFocused { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -560,7 +549,6 @@ - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -635,7 +623,6 @@ - (void)testAnnouncesScrollChangeWithLastFocused { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); id mockFlutterView = OCMClassMock([FlutterView class]); @@ -706,7 +693,6 @@ - (void)testAnnouncesIgnoresRouteChangesWhenModal { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); id mockFlutterView = OCMClassMock([FlutterView class]); id mockFlutterViewController = OCMClassMock([FlutterViewController class]); @@ -774,7 +760,6 @@ - (void)testAccessibilityMessageAfterDeletion { auto platform_view = std::make_unique( /*delegate=*/mock_delegate, /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, - flutter::IOSSurfaceFactory::Create(flutter::IOSRenderingAPI::kSoftware), /*task_runners=*/runners); fml::AutoResetWaitableEvent latch; thread_task_runner->PostTask([&] { diff --git a/shell/platform/darwin/ios/ios_surface_factory.h b/shell/platform/darwin/ios/ios_surface_factory.h deleted file mode 100644 index a692c56754887..0000000000000 --- a/shell/platform/darwin/ios/ios_surface_factory.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ -#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ - -#include - -#import "flutter/shell/platform/darwin/ios/ios_surface.h" -#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" - -namespace flutter { - -class IOSSurfaceFactory { - public: - static std::shared_ptr Create( - IOSRenderingAPI rendering_api); - - explicit IOSSurfaceFactory(std::shared_ptr ios_context); - - ~IOSSurfaceFactory(); - - void SetPlatformViewsController( - FlutterPlatformViewsController* platform_views_controller); - - std::unique_ptr CreateSurface( - fml::scoped_nsobject ca_layer); - - private: - FlutterPlatformViewsController* platform_views_controller_; - std::shared_ptr ios_context_; - - FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceFactory); -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS__SURFACE_FACTORY_H_ diff --git a/shell/platform/darwin/ios/ios_surface_factory.mm b/shell/platform/darwin/ios/ios_surface_factory.mm deleted file mode 100644 index 4ed7d4c0b96a9..0000000000000 --- a/shell/platform/darwin/ios/ios_surface_factory.mm +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" -#import "flutter/shell/platform/darwin/ios/ios_context.h" - -namespace flutter { - -IOSSurfaceFactory::IOSSurfaceFactory(std::shared_ptr ios_context) - : ios_context_(ios_context) {} - -std::shared_ptr IOSSurfaceFactory::Create(IOSRenderingAPI rendering_api) { - std::shared_ptr ios_context = IOSContext::Create(rendering_api); - return std::make_shared(ios_context); -} - -IOSSurfaceFactory::~IOSSurfaceFactory() = default; - -void IOSSurfaceFactory::SetPlatformViewsController( - FlutterPlatformViewsController* platform_views_controller) { - platform_views_controller_ = platform_views_controller; -} - -std::unique_ptr IOSSurfaceFactory::CreateSurface( - fml::scoped_nsobject ca_layer) { - return flutter::IOSSurface::Create(ios_context_, ca_layer, platform_views_controller_); -} - -} // namespace flutter diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 661d5e57f5b85..8e63bbda2f132 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -19,7 +19,6 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" -#import "flutter/shell/platform/darwin/ios/ios_surface_factory.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @class FlutterViewController; @@ -42,7 +41,6 @@ class PlatformViewIOS final : public PlatformView { public: explicit PlatformViewIOS(PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - std::shared_ptr surface_factory, flutter::TaskRunners task_runners); ~PlatformViewIOS() override; @@ -126,7 +124,6 @@ class PlatformViewIOS final : public PlatformView { std::mutex ios_surface_mutex_; std::unique_ptr ios_surface_; std::shared_ptr ios_context_; - std::shared_ptr ios_surface_factory_; PlatformMessageRouter platform_message_router_; AccessibilityBridgePtr accessibility_bridge_; fml::scoped_nsprotocol text_input_plugin_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index 43543c1e2fcde..faf764be7cb0c 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -3,7 +3,6 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" -#include #include @@ -47,11 +46,9 @@ PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate, IOSRenderingAPI rendering_api, - std::shared_ptr surface_factory, flutter::TaskRunners task_runners) : PlatformView(delegate, std::move(task_runners)), ios_context_(IOSContext::Create(rendering_api)), - ios_surface_factory_(surface_factory), accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }) {} PlatformViewIOS::~PlatformViewIOS() = default; @@ -105,9 +102,8 @@ FML_DCHECK(owner_controller_.get().isViewLoaded) << "FlutterViewController's view should be loaded " "before attaching to PlatformViewIOS."; - auto flutter_view = static_cast(owner_controller_.get().view); - auto ca_layer = fml::scoped_nsobject{[[flutter_view layer] retain]}; - ios_surface_ = ios_surface_factory_->CreateSurface(ca_layer); + ios_surface_ = + [static_cast(owner_controller_.get().view) createSurface:ios_context_]; FML_DCHECK(ios_surface_ != nullptr); if (accessibility_bridge_) { From ea2aea1c610242ffdf57d0b813f5281ff17fdd16 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 19 Oct 2020 19:37:02 -0400 Subject: [PATCH 181/219] Roll Fuchsia Linux SDK from ZJHmp3INU... to dcMRY8S12... (#21976) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 1a77f6a76eccd..7b54bdfd35c18 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC' + 'version': 'dcMRY8S12r8ReGSI4Sp3g9_nUTw34r2v0Kh_I08HfOsC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 9b55043c23ff9..9d63e52376a0b 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: e794e6ce4652a9fdd105bddcb6799529 +Signature: a23537a6ee6308a39262185cec6c15f2 UNUSED LICENSES: @@ -926,6 +926,7 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/scheduler.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/lib/Scrt1.o FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/lib/libc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/lib/libdl.so @@ -1133,6 +1134,7 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/internal/cdecls.inc FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/scheduler.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/utc.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/lib/Scrt1.o FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/lib/libc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/lib/libdl.so From 3edc16ca4746b7ff8b6403255ae201c4fb9135d5 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 19 Oct 2020 20:17:02 -0400 Subject: [PATCH 182/219] Roll Dart SDK from 5c59a47beda7 to 902538ea56d5 (2 revisions) (#21978) --- DEPS | 4 ++-- ci/licenses_golden/licenses_third_party | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 7b54bdfd35c18..9b5ee9ae1f2dc 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': '5c59a47beda7af5114881b23a8238aeb99338654', + 'dart_revision': '902538ea56d5058c901a872415e148e644c46c40', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -52,7 +52,7 @@ vars = { 'dart_pub_rev': 'f0c7771b38155d3829a60d60b5dba2784b100811', 'dart_pub_semver_tag': 'v1.4.4', 'dart_quiver-dart_tag': '246e754fe45cecb6aa5f3f13b4ed61037ff0d784', - 'dart_resource_rev': 'f8e37558a1c4f54550aa463b88a6a831e3e33cd6', + 'dart_resource_rev': '6b79867d0becf5395e5819a75720963b8298e9a7', 'dart_root_certificates_rev': '7e5ec82c99677a2e5b95ce296c4d68b0d3378ed8', 'dart_shelf_packages_handler_tag': '2.0.0', 'dart_shelf_proxy_tag': '0.1.0+7', diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 1dcd14c0b17cb..8579d5c205e4d 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 9969595ae8de55891d17927142faa8eb +Signature: b015e7ec07ad3548442fc45952411228 UNUSED LICENSES: From 0747f2f4b1167a6fdfca0d04a8e4f0b862e7f130 Mon Sep 17 00:00:00 2001 From: Ferhat Date: Mon, 19 Oct 2020 17:27:27 -0700 Subject: [PATCH 183/219] [web] Fix 3d transforms for html backend (#21499) * Workaround for canvas element lacking support for 3d setTransform * update golden test * Add webkit workaround * Implement DOM rendering for perspective * cleanup * update goldens lock * Add check for shader and filtermask for dom use * Fix svg viewBox. Move zIndex check to bitmap canvas * Fix null check warning * Fix scene_builder zIndex=-1 test to force canvas usage * Add blendmode handling for DOM mode * Update maxdiff and golden locks * Remove unused import * Add drawcolor/drawpaint test. Fix bounds for drawColor/drawPaint * update golden locks * adjust drawColor for dpr * Update test to use canvas * Fix toDataUrl NNBD * Update Picture.toImage to use canvas to obstain image data * Remove write:true from golden calls * Add fill-rule for _pathToSvgElement * Update golden locks * Fix sceneBuilder pushClip / add missing clipBehaviour * Fix test now that clipping works correctly * move overflow handling for tests into DOMClip.addOverflow * Add clipRect to test to keep render inside bitmap canvas area * Update compositing test, fix drawColor coordinates * update golden locks * Skip test for matchGolden infra fail * update golden lock * merge * update maxdiff for text over canvas * update golden diff * update paint spread bounds maxdiff * update paint spread maxDiff --- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 306 ++++++++++++++---- lib/web_ui/lib/src/engine/canvas_pool.dart | 17 +- lib/web_ui/lib/src/engine/dom_canvas.dart | 176 ++++++---- lib/web_ui/lib/src/engine/html/clip.dart | 43 ++- .../lib/src/engine/html/scene_builder.dart | 3 +- lib/web_ui/lib/src/engine/picture.dart | 2 +- .../engine/surface/scene_builder_test.dart | 35 +- .../engine/canvas_blend_golden_test.dart | 11 +- .../engine/canvas_draw_color_test.dart | 90 ++++++ .../engine/canvas_draw_image_golden_test.dart | 165 +++++++++- .../engine/canvas_golden_test.dart | 19 +- .../engine/compositing_golden_test.dart | 5 +- .../engine/recording_canvas_golden_test.dart | 2 +- 14 files changed, 703 insertions(+), 173 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/canvas_draw_color_test.dart diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 1f1b59c91e98d..f558ffe268551 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 1556280d6f1d70fac9ddff9b38639757e105b4b0 +revision: 67f22ef933be27ba2be8b27df1b71b2c69eb86e5 diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index b52588717183b..afe4a6e07844a 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -94,6 +94,16 @@ class BitmapCanvas extends EngineCanvas { _childOverdraw = value; } + /// Indicates bitmap canvas contains a 3d transform. + /// WebKit fails to preserve paint order when this happens and therefore + /// requires insertion of

to be + /// used for each child to force correct rendering order. + bool _contains3dTransform = false; + + /// Indicates that contents should be rendered into canvas so a dataUrl + /// can be constructed from contents. + bool _preserveImageData = false; + /// Allocates a canvas with enough memory to paint a picture within the given /// [bounds]. /// @@ -117,6 +127,13 @@ class BitmapCanvas extends EngineCanvas { _setupInitialTransform(); } + /// Constructs bitmap canvas to capture image data. + factory BitmapCanvas.imageData(ui.Rect bounds) { + BitmapCanvas bitmapCanvas = BitmapCanvas(bounds); + bitmapCanvas._preserveImageData = true; + return bitmapCanvas; + } + /// Setup cache for reusing DOM elements across frames. void setElementCache(CrossFrameCache cache) { _elementCache = cache; @@ -139,8 +156,9 @@ class BitmapCanvas extends EngineCanvas { final double canvasPositionCorrectionX = _bounds.left - BitmapCanvas.kPaddingPixels - _canvasPositionX!.toDouble(); - final double canvasPositionCorrectionY = - _bounds.top - BitmapCanvas.kPaddingPixels - _canvasPositionY!.toDouble(); + final double canvasPositionCorrectionY = _bounds.top - + BitmapCanvas.kPaddingPixels - + _canvasPositionY!.toDouble(); // This compensates for the translate on the `rootElement`. _canvasPool.initialTransform = ui.Offset( -_bounds.left + canvasPositionCorrectionX + BitmapCanvas.kPaddingPixels, @@ -175,6 +193,7 @@ class BitmapCanvas extends EngineCanvas { /// Prepare to reuse this canvas by clearing it's current contents. @override void clear() { + _contains3dTransform = false; _canvasPool.clear(); final int len = _children.length; for (int i = 0; i < len; i++) { @@ -267,6 +286,10 @@ class BitmapCanvas extends EngineCanvas { @override void transform(Float32List matrix4) { + TransformKind transformKind = transformKindOf(matrix4); + if (transformKind == TransformKind.complex) { + _contains3dTransform = true; + } _canvasPool.transform(matrix4); } @@ -295,37 +318,115 @@ class BitmapCanvas extends EngineCanvas { _canvasPool.clipPath(path); } + /// Whether drawing operation should use DOM node instead of Canvas. + /// + /// - Perspective transforms are not supported by canvas and require + /// DOM to render correctly. + /// - Pictures typically have large rect/rounded rectangles as background + /// prefer DOM if canvas has not been allocated yet. + bool _useDomForRendering(SurfacePaintData paint) => + _preserveImageData == false && ( + _contains3dTransform || + (_canvasPool._canvas == null && + paint.maskFilter == null && + paint.shader == null && + paint.style != ui.PaintingStyle.stroke)); + @override void drawColor(ui.Color color, ui.BlendMode blendMode) { - _canvasPool.drawColor(color, blendMode); + final SurfacePaintData paintData = SurfacePaintData() + ..color = color + ..blendMode = blendMode; + if (_useDomForRendering(paintData)) { + drawRect(_computeScreenBounds(_canvasPool._currentTransform), paintData); + } else { + _canvasPool.drawColor(color, blendMode); + } } @override void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.strokeLine(p1, p2); - _tearDownPaint(); + if (_useDomForRendering(paint)) { + final SurfacePath path = SurfacePath() + ..moveTo(p1.dx, p1.dy) + ..lineTo(p2.dx, p2.dy); + drawPath(path, paint); + } else { + _setUpPaint(paint); + _canvasPool.strokeLine(p1, p2); + _tearDownPaint(); + } } @override void drawPaint(SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.fill(); - _tearDownPaint(); + if (_useDomForRendering(paint)) { + drawRect(_computeScreenBounds(_canvasPool._currentTransform), paint); + } else { + _setUpPaint(paint); + _canvasPool.fill(); + _tearDownPaint(); + } } @override void drawRect(ui.Rect rect, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.drawRect(rect, paint.style); - _tearDownPaint(); + if (_useDomForRendering(paint)) { + html.HtmlElement element = _buildDrawRectElement( + rect, paint, 'draw-rect', _canvasPool._currentTransform); + _drawElement( + element, + ui.Offset( + math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)), + paint); + } else { + _setUpPaint(paint); + _canvasPool.drawRect(rect, paint.style); + _tearDownPaint(); + } + } + + /// Inserts a dom element at [offset] creating stack of divs for clipping + /// if required. + void _drawElement( + html.Element element, ui.Offset offset, SurfacePaintData paint) { + if (_canvasPool.isClipped) { + final List clipElements = _clipContent( + _canvasPool._clipStack!, + element, + ui.Offset.zero, + transformWithOffset(_canvasPool._currentTransform, offset)); + for (html.Element clipElement in clipElements) { + rootElement.append(clipElement); + _children.add(clipElement); + } + } else { + rootElement.append(element); + _children.add(element); + } + ui.BlendMode? blendMode = paint.blendMode; + if (blendMode != null) { + element.style.mixBlendMode = _stringForBlendMode(blendMode) ?? ''; + } } @override void drawRRect(ui.RRect rrect, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.drawRRect(rrect, paint.style); - _tearDownPaint(); + final ui.Rect rect = rrect.outerRect; + if (_useDomForRendering(paint)) { + html.HtmlElement element = _buildDrawRectElement( + rect, paint, 'draw-rrect', _canvasPool._currentTransform); + _applyRRectBorderRadius(element.style, rrect); + _drawElement( + element, + ui.Offset( + math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)), + paint); + } else { + _setUpPaint(paint); + _canvasPool.drawRRect(rrect, paint.style); + _tearDownPaint(); + } } @override @@ -337,23 +438,62 @@ class BitmapCanvas extends EngineCanvas { @override void drawOval(ui.Rect rect, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.drawOval(rect, paint.style); - _tearDownPaint(); + if (_useDomForRendering(paint)) { + html.HtmlElement element = _buildDrawRectElement( + rect, paint, 'draw-oval', _canvasPool._currentTransform); + _drawElement( + element, + ui.Offset( + math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)), + paint); + element.style.borderRadius = + '${(rect.width / 2.0)}px / ${(rect.height / 2.0)}px'; + } else { + _setUpPaint(paint); + _canvasPool.drawOval(rect, paint.style); + _tearDownPaint(); + } } @override void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.drawCircle(c, radius, paint.style); - _tearDownPaint(); + ui.Rect rect = ui.Rect.fromCircle(center: c, radius: radius); + if (_useDomForRendering(paint)) { + html.HtmlElement element = _buildDrawRectElement( + rect, paint, 'draw-circle', _canvasPool._currentTransform); + _drawElement( + element, + ui.Offset( + math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)), + paint); + element.style.borderRadius = '50%'; + } else { + _setUpPaint(paint); + _canvasPool.drawCircle(c, radius, paint.style); + _tearDownPaint(); + } } @override void drawPath(ui.Path path, SurfacePaintData paint) { - _setUpPaint(paint); - _canvasPool.drawPath(path, paint.style); - _tearDownPaint(); + if (_useDomForRendering(paint)) { + final Matrix4 transform = _canvasPool._currentTransform; + final SurfacePath surfacePath = path as SurfacePath; + final ui.Rect pathBounds = surfacePath.getBounds(); + html.Element svgElm = _pathToSvgElement( + surfacePath, paint, '${pathBounds.right}', '${pathBounds.bottom}'); + if (!_canvasPool.isClipped) { + svgElm.style + ..transform = matrix4ToCssTransform(transform) + ..transformOrigin = '0 0 0' + ..position = 'absolute'; + } + _drawElement(svgElm, ui.Offset(0, 0), paint); + } else { + _setUpPaint(paint); + _canvasPool.drawPath(path, paint.style); + _tearDownPaint(); + } } @override @@ -366,8 +506,8 @@ class BitmapCanvas extends EngineCanvas { void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { final html.HtmlElement imageElement = _drawImage(image, p, paint); if (paint.colorFilter != null) { - _applyTargetSize(imageElement, image.width.toDouble(), - image.height.toDouble()); + _applyTargetSize( + imageElement, image.width.toDouble(), image.height.toDouble()); } _childOverdraw = true; _canvasPool.closeCurrentCanvas(); @@ -377,7 +517,8 @@ class BitmapCanvas extends EngineCanvas { html.ImageElement _reuseOrCreateImage(HtmlImage htmlImage) { final String cacheKey = htmlImage.imgElement.src!; if (_elementCache != null) { - html.ImageElement? imageElement = _elementCache!.reuse(cacheKey) as html.ImageElement?; + html.ImageElement? imageElement = + _elementCache!.reuse(cacheKey) as html.ImageElement?; if (imageElement != null) { return imageElement; } @@ -398,7 +539,8 @@ class BitmapCanvas extends EngineCanvas { ui.Image image, ui.Offset p, SurfacePaintData paint) { final HtmlImage htmlImage = image as HtmlImage; final ui.BlendMode? blendMode = paint.blendMode; - final EngineColorFilter? colorFilter = paint.colorFilter as EngineColorFilter?; + final EngineColorFilter? colorFilter = + paint.colorFilter as EngineColorFilter?; final ui.BlendMode? colorFilterBlendMode = colorFilter?._blendMode; html.HtmlElement imgElement; if (colorFilterBlendMode == null) { @@ -419,21 +561,19 @@ class BitmapCanvas extends EngineCanvas { case ui.BlendMode.color: case ui.BlendMode.luminosity: case ui.BlendMode.xor: - imgElement = _createImageElementWithSvgFilter(image, - colorFilter!._color, colorFilterBlendMode, paint); + imgElement = _createImageElementWithSvgFilter( + image, colorFilter!._color, colorFilterBlendMode, paint); break; default: - imgElement = _createBackgroundImageWithBlend(image, - colorFilter!._color, colorFilterBlendMode, paint); + imgElement = _createBackgroundImageWithBlend( + image, colorFilter!._color, colorFilterBlendMode, paint); break; } } imgElement.style.mixBlendMode = _stringForBlendMode(blendMode) ?? ''; if (_canvasPool.isClipped) { // Reset width/height since they may have been previously set. - imgElement.style - ..removeProperty('width') - ..removeProperty('height'); + imgElement.style..removeProperty('width')..removeProperty('height'); final List clipElements = _clipContent( _canvasPool._clipStack!, imgElement, p, _canvasPool.currentTransform); for (html.Element clipElement in clipElements) { @@ -503,7 +643,8 @@ class BitmapCanvas extends EngineCanvas { targetWidth *= image.width / src.width; targetHeight *= image.height / src.height; } - _applyTargetSize(imgElement as html.HtmlElement, targetWidth, targetHeight); + _applyTargetSize( + imgElement as html.HtmlElement, targetWidth, targetHeight); if (requiresClipping) { restore(); } @@ -511,8 +652,8 @@ class BitmapCanvas extends EngineCanvas { _closeCurrentCanvas(); } - void _applyTargetSize(html.HtmlElement imageElement, double targetWidth, - double targetHeight) { + void _applyTargetSize( + html.HtmlElement imageElement, double targetWidth, double targetHeight) { final html.CssStyleDeclaration imageStyle = imageElement.style; final String widthPx = '${targetWidth.toStringAsFixed(2)}px'; final String heightPx = '${targetHeight.toStringAsFixed(2)}px'; @@ -542,8 +683,10 @@ class BitmapCanvas extends EngineCanvas { // For clear,dstOut it generates a blank element. // For src,srcOver it only sets background-color attribute. // For dst,dstIn , it only sets source not background color. - html.HtmlElement _createBackgroundImageWithBlend(HtmlImage image, - ui.Color? filterColor, ui.BlendMode colorFilterBlendMode, + html.HtmlElement _createBackgroundImageWithBlend( + HtmlImage image, + ui.Color? filterColor, + ui.BlendMode colorFilterBlendMode, SurfacePaintData paint) { // When blending with color we can't use an image element. // Instead use a div element with background image, color and @@ -558,8 +701,8 @@ class BitmapCanvas extends EngineCanvas { case ui.BlendMode.src: case ui.BlendMode.srcOver: style - ..position = 'absolute' - ..backgroundColor = colorToCssString(filterColor); + ..position = 'absolute' + ..backgroundColor = colorToCssString(filterColor); break; case ui.BlendMode.dst: case ui.BlendMode.dstIn: @@ -571,7 +714,8 @@ class BitmapCanvas extends EngineCanvas { style ..position = 'absolute' ..backgroundImage = "url('${image.imgElement.src}')" - ..backgroundBlendMode = _stringForBlendMode(colorFilterBlendMode) ?? '' + ..backgroundBlendMode = + _stringForBlendMode(colorFilterBlendMode) ?? '' ..backgroundColor = colorToCssString(filterColor); break; } @@ -579,12 +723,14 @@ class BitmapCanvas extends EngineCanvas { } // Creates an image element and an svg filter to apply on the element. - html.HtmlElement _createImageElementWithSvgFilter(HtmlImage image, - ui.Color? filterColor, ui.BlendMode colorFilterBlendMode, + html.HtmlElement _createImageElementWithSvgFilter( + HtmlImage image, + ui.Color? filterColor, + ui.BlendMode colorFilterBlendMode, SurfacePaintData paint) { // For srcIn blendMode, we use an svg filter to apply to image element. - String? svgFilter = svgFilterFromBlendMode(filterColor, - colorFilterBlendMode); + String? svgFilter = + svgFilterFromBlendMode(filterColor, colorFilterBlendMode); final html.Element filterElement = html.Element.html(svgFilter, treeSanitizer: _NullTreeSanitizer()); rootElement.append(filterElement); @@ -651,9 +797,11 @@ class BitmapCanvas extends EngineCanvas { if (paragraph._drawOnCanvas && _childOverdraw == false) { // !Do not move this assignment above this if clause since, accessing // context will generate extra tags. - final List lines = paragraph._measurementResult!.lines!; + final List lines = + paragraph._measurementResult!.lines!; - final SurfacePaintData? backgroundPaint = paragraph._background?.paintData; + final SurfacePaintData? backgroundPaint = + paragraph._background?.paintData; if (backgroundPaint != null) { final ui.Rect rect = ui.Rect.fromLTWH( offset.dx, offset.dy, paragraph.width, paragraph.height); @@ -723,8 +871,8 @@ class BitmapCanvas extends EngineCanvas { /// If colors is specified, convert colors to premultiplied (alpha) colors /// and use a SkTriColorShader to render. @override - void drawVertices( - SurfaceVertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) { + void drawVertices(SurfaceVertices vertices, ui.BlendMode blendMode, + SurfacePaintData paint) { // TODO(flutter_web): Implement shaders for [Paint.shader] and // blendMode. https://github.com/flutter/flutter/issues/40096 // Move rendering to OffscreenCanvas so that transform is preserved @@ -761,7 +909,8 @@ class BitmapCanvas extends EngineCanvas { ..blendMode = ui.BlendMode.srcOver; @override - void drawPoints(ui.PointMode pointMode, Float32List points, SurfacePaintData paint) { + void drawPoints( + ui.PointMode pointMode, Float32List points, SurfacePaintData paint) { if (pointMode == ui.PointMode.points) { _drawPointsPaint.style = ui.PaintingStyle.stroke; } else { @@ -780,6 +929,45 @@ class BitmapCanvas extends EngineCanvas { void endOfPaint() { _canvasPool.endOfPaint(); _elementCache?.commitFrame(); + // Wrap all elements in translate3d (workaround for webkit paint order bug). + if (_contains3dTransform && browserEngine == BrowserEngine.webkit) { + for (html.Element element in rootElement.children) { + html.DivElement paintOrderElement = html.DivElement() + ..style.transform = 'translate3d(0,0,0)'; + paintOrderElement.append(element); + rootElement.append(paintOrderElement); + _children.add(paintOrderElement); + } + } + if (rootElement.firstChild is html.HtmlElement && + (rootElement.firstChild as html.HtmlElement).tagName.toLowerCase() == + 'canvas') { + (rootElement.firstChild as html.HtmlElement).style.zIndex = '-1'; + } + } + + /// Computes paint bounds given [targetTransform] to completely cover window + /// viewport. + ui.Rect _computeScreenBounds(Matrix4 targetTransform) { + final Matrix4 inverted = targetTransform.clone()..invert(); + final double dpr = ui.window.devicePixelRatio; + final double width = ui.window.physicalSize.width * dpr; + final double height = ui.window.physicalSize.height * dpr; + Vector3 topLeft = inverted.perspectiveTransform(Vector3(0, 0, 0)); + Vector3 topRight = inverted.perspectiveTransform(Vector3(width, 0, 0)); + Vector3 bottomRight = + inverted.perspectiveTransform(Vector3(width, height, 0)); + Vector3 bottomLeft = inverted.perspectiveTransform(Vector3(0, height, 0)); + return ui.Rect.fromLTRB( + math.min(topLeft.x, + math.min(topRight.x, math.min(bottomRight.x, bottomLeft.x))), + math.min(topLeft.y, + math.min(topRight.y, math.min(bottomRight.y, bottomLeft.y))), + math.max(topLeft.x, + math.max(topRight.x, math.max(bottomRight.x, bottomLeft.x))), + math.max(topLeft.y, + math.max(topRight.y, math.max(bottomRight.y, bottomLeft.y))), + ); } } @@ -885,7 +1073,7 @@ String _stringForStrokeJoin(ui.StrokeJoin strokeJoin) { /// it's contents. The clipping rectangles are nested and returned together /// with a list of svg elements that provide clip-paths. List _clipContent(List<_SaveClipEntry> clipStack, - html.HtmlElement content, ui.Offset offset, Matrix4 currentTransform) { + html.Element content, ui.Offset offset, Matrix4 currentTransform) { html.Element? root, curElement; final List clipDefs = []; final int len = clipStack.length; @@ -902,6 +1090,9 @@ List _clipContent(List<_SaveClipEntry> clipStack, curElement = newElement; final ui.Rect? rect = entry.rect; Matrix4 newClipTransform = entry.currentTransform; + final TransformKind transformKind = + transformKindOf(newClipTransform.storage); + bool requiresTransformStyle = transformKind == TransformKind.complex; if (rect != null) { final double clipOffsetX = rect.left; final double clipOffsetY = rect.top; @@ -931,7 +1122,8 @@ List _clipContent(List<_SaveClipEntry> clipStack, curElement.style ..transform = matrix4ToCssTransform(newClipTransform) ..transformOrigin = '0 0 0'; - String svgClipPath = createSvgClipDef(curElement as html.HtmlElement, entry.path!); + String svgClipPath = + createSvgClipDef(curElement as html.HtmlElement, entry.path!); final html.Element clipElement = html.Element.html(svgClipPath, treeSanitizer: _NullTreeSanitizer()); clipDefs.add(clipElement); @@ -946,6 +1138,11 @@ List _clipContent(List<_SaveClipEntry> clipStack, reverseTransformDiv, (newClipTransform.clone()..invert()).storage, ); + if (requiresTransformStyle) { + // Instead of flattening matrix3d, preserve so it can be reversed. + curElement.style.transformStyle = 'preserve-3d'; + reverseTransformDiv.style.transformStyle = 'preserve-3d'; + } curElement.append(reverseTransformDiv); curElement = reverseTransformDiv; } @@ -975,4 +1172,3 @@ String _maskFilterToCanvasFilter(ui.MaskFilter? maskFilter) { return 'none'; } } - diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index 9a297398605c5..a645fc2aaaa55 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -33,8 +33,6 @@ class _CanvasPool extends _SaveStackTracking { html.HtmlElement? _rootElement; int _saveContextCount = 0; - // Number of elements that have been added to flt-canvas. - int _activeElementCount = 0; _CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels); @@ -76,7 +74,6 @@ class _CanvasPool extends _SaveStackTracking { _context = null; _contextHandle = null; } - _activeElementCount++; } void allocateCanvas(html.HtmlElement rootElement) { @@ -134,15 +131,12 @@ class _CanvasPool extends _SaveStackTracking { _rootElement!.append(canvas); } - if (_activeElementCount == 0) { - canvas.style.zIndex = '-1'; - } else if (reused) { - // If a canvas is the first element we set z-index = -1 to workaround - // blink compositing bug. To make sure this does not leak when reused - // reset z-index. + if (reused) { + // If a canvas is the first element we set z-index = -1 in [BitmapCanvas] + // endOfPaint to workaround blink compositing bug. To make sure this + // does not leak when reused reset z-index. canvas.style.removeProperty('z-index'); } - ++_activeElementCount; final html.CanvasRenderingContext2D context = _context = canvas.context2D; _contextHandle = ContextStateHandle(this, context); @@ -270,7 +264,6 @@ class _CanvasPool extends _SaveStackTracking { _canvas = null; _context = null; _contextHandle = null; - _activeElementCount = 0; } void endOfPaint() { @@ -326,7 +319,7 @@ class _CanvasPool extends _SaveStackTracking { // Returns a "data://" URI containing a representation of the image in this // canvas in PNG format. - String toDataUrl() => _canvas!.toDataUrl(); + String toDataUrl() => _canvas?.toDataUrl() ?? ''; @override void save() { diff --git a/lib/web_ui/lib/src/engine/dom_canvas.dart b/lib/web_ui/lib/src/engine/dom_canvas.dart index 11668eb2472fc..c6d32a02e8f70 100644 --- a/lib/web_ui/lib/src/engine/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/dom_canvas.dart @@ -68,75 +68,16 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { @override void drawRect(ui.Rect rect, SurfacePaintData paint) { - _drawRect(rect, paint, 'draw-rect'); - } - - html.Element _drawRect(ui.Rect rect, SurfacePaintData paint, String tagName) { - assert(paint.shader == null); - final html.Element rectangle = html.Element.tag(tagName); - assert(() { - rectangle.setAttribute('flt-rect', '$rect'); - rectangle.setAttribute('flt-paint', '$paint'); - return true; - }()); - String effectiveTransform; - final bool isStroke = paint.style == ui.PaintingStyle.stroke; - final double strokeWidth = paint.strokeWidth ?? 0.0; - final double left = math.min(rect.left, rect.right); - final double right = math.max(rect.left, rect.right); - final double top = math.min(rect.top, rect.bottom); - final double bottom = math.max(rect.top, rect.bottom); - if (currentTransform.isIdentity()) { - if (isStroke) { - effectiveTransform = - 'translate(${left - (strokeWidth / 2.0)}px, ${top - (strokeWidth / 2.0)}px)'; - } else { - effectiveTransform = 'translate(${left}px, ${top}px)'; - } - } else { - // Clone to avoid mutating _transform. - final Matrix4 translated = currentTransform.clone(); - if (isStroke) { - translated.translate( - left - (strokeWidth / 2.0), top - (strokeWidth / 2.0)); - } else { - translated.translate(left, top); - } - effectiveTransform = matrix4ToCssTransform(translated); - } - final html.CssStyleDeclaration style = rectangle.style; - style - ..position = 'absolute' - ..transformOrigin = '0 0 0' - ..transform = effectiveTransform; - - final String cssColor = - paint.color == null ? '#000000' : colorToCssString(paint.color)!; - - if (paint.maskFilter != null) { - style.filter = 'blur(${paint.maskFilter!.webOnlySigma}px)'; - } - - if (isStroke) { - style - ..width = '${right - left - strokeWidth}px' - ..height = '${bottom - top - strokeWidth}px' - ..border = '${strokeWidth}px solid $cssColor'; - } else { - style - ..width = '${right - left}px' - ..height = '${bottom - top}px' - ..backgroundColor = cssColor; - } - - currentElement.append(rectangle); - return rectangle; + currentElement.append(_buildDrawRectElement(rect, paint, 'draw-rect', + currentTransform)); } @override void drawRRect(ui.RRect rrect, SurfacePaintData paint) { - html.Element element = _drawRect(rrect.outerRect, paint, 'draw-rrect'); - element.style.borderRadius = '${rrect.blRadiusX.toStringAsFixed(3)}px'; + html.Element element = _buildDrawRectElement(rrect.outerRect, + paint, 'draw-rrect', currentTransform); + _applyRRectBorderRadius(element.style, rrect); + currentElement.append(element); } @override @@ -199,3 +140,108 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { // No reuse of elements yet to handle here. Noop. } } + +html.HtmlElement _buildDrawRectElement(ui.Rect rect, SurfacePaintData paint, String tagName, + Matrix4 transform) { + assert(paint.shader == null); + final html.HtmlElement rectangle = html.Element.tag(tagName) as html.HtmlElement; + assert(() { + rectangle.setAttribute('flt-rect', '$rect'); + rectangle.setAttribute('flt-paint', '$paint'); + return true; + }()); + String effectiveTransform; + final bool isStroke = paint.style == ui.PaintingStyle.stroke; + final double strokeWidth = paint.strokeWidth ?? 0.0; + final double left = math.min(rect.left, rect.right); + final double right = math.max(rect.left, rect.right); + final double top = math.min(rect.top, rect.bottom); + final double bottom = math.max(rect.top, rect.bottom); + if (transform.isIdentity()) { + if (isStroke) { + effectiveTransform = + 'translate(${left - (strokeWidth / 2.0)}px, ${top - (strokeWidth / 2.0)}px)'; + } else { + effectiveTransform = 'translate(${left}px, ${top}px)'; + } + } else { + // Clone to avoid mutating _transform. + final Matrix4 translated = transform.clone(); + if (isStroke) { + translated.translate( + left - (strokeWidth / 2.0), top - (strokeWidth / 2.0)); + } else { + translated.translate(left, top); + } + effectiveTransform = matrix4ToCssTransform(translated); + } + final html.CssStyleDeclaration style = rectangle.style; + style + ..position = 'absolute' + ..transformOrigin = '0 0 0' + ..transform = effectiveTransform; + + final String cssColor = + paint.color == null ? '#000000' : colorToCssString(paint.color)!; + + if (paint.maskFilter != null) { + style.filter = 'blur(${paint.maskFilter!.webOnlySigma}px)'; + } + + if (isStroke) { + style + ..width = '${right - left - strokeWidth}px' + ..height = '${bottom - top - strokeWidth}px' + ..border = '${strokeWidth}px solid $cssColor'; + } else { + style + ..width = '${right - left}px' + ..height = '${bottom - top}px' + ..backgroundColor = cssColor; + } + return rectangle; +} + +void _applyRRectBorderRadius(html.CssStyleDeclaration style, ui.RRect rrect) { + if (rrect.tlRadiusX == rrect.trRadiusX && + rrect.tlRadiusX == rrect.blRadiusX && + rrect.tlRadiusX == rrect.brRadiusX && + rrect.tlRadiusX == rrect.tlRadiusY && + rrect.trRadiusX == rrect.trRadiusY && + rrect.blRadiusX == rrect.blRadiusY && + rrect.brRadiusX == rrect.brRadiusY) { + style.borderRadius = '${rrect.blRadiusX.toStringAsFixed(3)}px'; + return; + } + // Non-uniform. Apply each corner radius. + style.borderTopLeftRadius = '${rrect.tlRadiusX.toStringAsFixed(3)}px ' + '${rrect.tlRadiusY.toStringAsFixed(3)}px'; + style.borderTopRightRadius = '${rrect.trRadiusX.toStringAsFixed(3)}px ' + '${rrect.trRadiusY.toStringAsFixed(3)}px'; + style.borderBottomLeftRadius = '${rrect.blRadiusX.toStringAsFixed(3)}px ' + '${rrect.blRadiusY.toStringAsFixed(3)}px'; + style.borderBottomRightRadius = '${rrect.brRadiusX.toStringAsFixed(3)}px ' + '${rrect.brRadiusY.toStringAsFixed(3)}px'; +} + +html.Element _pathToSvgElement(SurfacePath path, SurfacePaintData paint, + String width, String height) { + final StringBuffer sb = StringBuffer(); + sb.write( + ''); + sb.write(''); + sb.write(''); + return html.Element.html(sb.toString(), treeSanitizer: _NullTreeSanitizer()); +} diff --git a/lib/web_ui/lib/src/engine/html/clip.dart b/lib/web_ui/lib/src/engine/html/clip.dart index 0cab237946277..bc8cbc456b5ed 100644 --- a/lib/web_ui/lib/src/engine/html/clip.dart +++ b/lib/web_ui/lib/src/engine/html/clip.dart @@ -25,18 +25,6 @@ mixin _DomClip on PersistedContainerSurface { @override html.Element createElement() { final html.Element element = defaultCreateElement('flt-clip'); - if (!debugShowClipLayers) { - // Hide overflow in production mode. When debugging we want to see the - // clipped picture in full. - element.style - ..overflow = 'hidden' - ..zIndex = '0'; - } else { - // Display the outline of the clipping region. When debugShowClipLayers is - // `true` we don't hide clip overflow (see above). This outline helps - // visualizing clip areas. - element.style.boxShadow = 'inset 0 0 10px green'; - } _childContainer = html.Element.tag('flt-clip-interior'); if (_debugExplainSurfaceStats) { // This creates an additional interior element. Count it too. @@ -57,14 +45,32 @@ mixin _DomClip on PersistedContainerSurface { // together. _childContainer = null; } + + void applyOverflow(html.Element element, ui.Clip? clipBehaviour) { + if (!debugShowClipLayers) { + // Hide overflow in production mode. When debugging we want to see the + // clipped picture in full. + if (clipBehaviour != ui.Clip.none) { + element.style + ..overflow = 'hidden' + ..zIndex = '0'; + } + } else { + // Display the outline of the clipping region. When debugShowClipLayers is + // `true` we don't hide clip overflow (see above). This outline helps + // visualizing clip areas. + element.style.boxShadow = 'inset 0 0 10px green'; + } + } } /// A surface that creates a rectangular clip. class PersistedClipRect extends PersistedContainerSurface with _DomClip implements ui.ClipRectEngineLayer { - PersistedClipRect(PersistedClipRect? oldLayer, this.rect) : super(oldLayer); - + PersistedClipRect(PersistedClipRect? oldLayer, this.rect, this.clipBehavior) + : super(oldLayer); + final ui.Clip? clipBehavior; final ui.Rect rect; @override @@ -87,6 +93,7 @@ class PersistedClipRect extends PersistedContainerSurface ..top = '${rect.top}px' ..width = '${rect.right - rect.left}px' ..height = '${rect.bottom - rect.top}px'; + applyOverflow(rootElement!, clipBehavior); // Translate the child container in the opposite direction to compensate for // the shift in the coordinate system introduced by the translation of the @@ -99,7 +106,7 @@ class PersistedClipRect extends PersistedContainerSurface @override void update(PersistedClipRect oldSurface) { super.update(oldSurface); - if (rect != oldSurface.rect) { + if (rect != oldSurface.rect || clipBehavior != oldSurface.clipBehavior) { apply(); } } @@ -134,7 +141,8 @@ class PersistedClipRRect extends PersistedContainerSurface @override void apply() { - rootElement!.style + html.CssStyleDeclaration style = rootElement!.style; + style ..left = '${rrect.left}px' ..top = '${rrect.top}px' ..width = '${rrect.width}px' @@ -143,6 +151,7 @@ class PersistedClipRRect extends PersistedContainerSurface ..borderTopRightRadius = '${rrect.trRadiusX}px' ..borderBottomRightRadius = '${rrect.brRadiusX}px' ..borderBottomLeftRadius = '${rrect.blRadiusX}px'; + applyOverflow(rootElement!, clipBehavior); // Translate the child container in the opposite direction to compensate for // the shift in the coordinate system introduced by the translation of the @@ -155,7 +164,7 @@ class PersistedClipRRect extends PersistedContainerSurface @override void update(PersistedClipRRect oldSurface) { super.update(oldSurface); - if (rrect != oldSurface.rrect) { + if (rrect != oldSurface.rrect || clipBehavior != oldSurface.clipBehavior) { apply(); } } diff --git a/lib/web_ui/lib/src/engine/html/scene_builder.dart b/lib/web_ui/lib/src/engine/html/scene_builder.dart index b9dd6f790aad9..705b3aa4bc589 100644 --- a/lib/web_ui/lib/src/engine/html/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/html/scene_builder.dart @@ -113,7 +113,8 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { }) { assert(clipBehavior != null); // ignore: unnecessary_null_comparison assert(clipBehavior != ui.Clip.none); - return _pushSurface(PersistedClipRect(oldLayer as PersistedClipRect?, rect)) as ui.ClipRectEngineLayer; + return _pushSurface(PersistedClipRect(oldLayer as PersistedClipRect?, rect, clipBehavior)) + as ui.ClipRectEngineLayer; } /// Pushes a rounded-rectangular clip operation onto the operation stack. diff --git a/lib/web_ui/lib/src/engine/picture.dart b/lib/web_ui/lib/src/engine/picture.dart index 946f348a66a3e..42304da062c9d 100644 --- a/lib/web_ui/lib/src/engine/picture.dart +++ b/lib/web_ui/lib/src/engine/picture.dart @@ -47,7 +47,7 @@ class EnginePicture implements ui.Picture { @override Future toImage(int width, int height) async { final ui.Rect imageRect = ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble()); - final BitmapCanvas canvas = BitmapCanvas(imageRect); + final BitmapCanvas canvas = BitmapCanvas.imageData(imageRect); recordingCanvas!.apply(canvas, imageRect); final String imageDataUrl = canvas.toDataUrl(); final html.ImageElement imageElement = html.ImageElement() diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index da2bdb275215e..89b358e35f7f6 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -133,7 +133,8 @@ void testMain() { () { final PersistedScene scene1 = PersistedScene(null); final PersistedClipRect clip1 = - PersistedClipRect(null, const Rect.fromLTRB(10, 10, 20, 20)); + PersistedClipRect(null, const Rect.fromLTRB(10, 10, 20, 20), + Clip.antiAlias); final PersistedOpacity opacity = PersistedOpacity(null, 100, Offset.zero); final MockPersistedPicture picture = MockPersistedPicture(); @@ -158,7 +159,8 @@ void testMain() { // because the clip didn't change no repaints should happen. final PersistedScene scene2 = PersistedScene(scene1); final PersistedClipRect clip2 = - PersistedClipRect(clip1, const Rect.fromLTRB(10, 10, 20, 20)); + PersistedClipRect(clip1, const Rect.fromLTRB(10, 10, 20, 20), + Clip.antiAlias); clip1.state = PersistedSurfaceState.pendingUpdate; scene2.appendChild(clip2); opacity.state = PersistedSurfaceState.pendingRetention; @@ -176,7 +178,8 @@ void testMain() { // This should cause the picture to repaint despite being retained. final PersistedScene scene3 = PersistedScene(scene2); final PersistedClipRect clip3 = - PersistedClipRect(clip2, const Rect.fromLTRB(10, 10, 50, 50)); + PersistedClipRect(clip2, const Rect.fromLTRB(10, 10, 50, 50), + Clip.antiAlias); clip2.state = PersistedSurfaceState.pendingUpdate; scene3.appendChild(clip3); opacity.state = PersistedSurfaceState.pendingRetention; @@ -234,6 +237,7 @@ void testMain() { builder.pop(); html.HtmlElement content = builder.build().webOnlyRootElement; + html.document.body.append(content); expect(content.querySelector('canvas').style.zIndex, '-1'); // Force update to scene which will utilize reuse code path. @@ -627,8 +631,16 @@ Picture _drawPicture() { final EnginePictureRecorder recorder = PictureRecorder(); final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400)); + Shader gradient = Gradient.radial( + Offset(100, 100), 50, [ + const Color.fromARGB(255, 0, 0, 0), + const Color.fromARGB(255, 0, 0, 255) + ]); canvas.drawCircle( - Offset(offsetX + 10, offsetY + 10), 10, Paint()..style = PaintingStyle.fill); + Offset(offsetX + 10, offsetY + 10), 10, + Paint() + ..style = PaintingStyle.fill + ..shader = gradient); canvas.drawCircle( Offset(offsetX + 60, offsetY + 10), 10, @@ -656,8 +668,16 @@ Picture _drawPathImagePath() { final EnginePictureRecorder recorder = PictureRecorder(); final RecordingCanvas canvas = recorder.beginRecording(const Rect.fromLTRB(0, 0, 400, 400)); + Shader gradient = Gradient.radial( + Offset(100, 100), 50, [ + const Color.fromARGB(255, 0, 0, 0), + const Color.fromARGB(255, 0, 0, 255) + ]); canvas.drawCircle( - Offset(offsetX + 10, offsetY + 10), 10, Paint()..style = PaintingStyle.fill); + Offset(offsetX + 10, offsetY + 10), 10, + Paint() + ..style = PaintingStyle.fill + ..shader = gradient); canvas.drawCircle( Offset(offsetX + 60, offsetY + 10), 10, @@ -671,6 +691,11 @@ Picture _drawPathImagePath() { ..style = PaintingStyle.fill ..color = const Color.fromRGBO(0, 255, 0, 1)); canvas.drawImage(createTestImage(), Offset(0, 0), Paint()); + canvas.drawCircle( + Offset(offsetX + 10, offsetY + 10), 10, + Paint() + ..style = PaintingStyle.fill + ..shader = gradient); canvas.drawCircle( Offset(offsetX + 60, offsetY + 60), 10, diff --git a/lib/web_ui/test/golden_tests/engine/canvas_blend_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_blend_golden_test.dart index 39cfcb4b27feb..2996be4e1be01 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_blend_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_blend_golden_test.dart @@ -24,7 +24,7 @@ void testMain() async { // Commit a recording canvas to a bitmap, and compare with the expected Future _checkScreenshot(RecordingCanvas rc, String fileName, {Rect region = const Rect.fromLTWH(0, 0, 500, 500), - double maxDiffRatePercent = 0.0}) async { + double maxDiffRatePercent = 0.0, bool write = false}) async { final EngineCanvas engineCanvas = BitmapCanvas(screenRect); rc.endRecording(); @@ -35,7 +35,8 @@ void testMain() async { try { sceneElement.append(engineCanvas.rootElement); html.document.body.append(sceneElement); - await matchGoldenFile('$fileName.png', region: region, maxDiffRatePercent: maxDiffRatePercent); + await matchGoldenFile('$fileName.png', region: region, + maxDiffRatePercent: maxDiffRatePercent, write: write); } finally { // The page is reused across tests, so remove the element after taking the // Scuba screenshot. @@ -83,7 +84,8 @@ void testMain() async { ..color = const Color.fromARGB(128, 255, 0, 0)); rc.restore(); await _checkScreenshot(rc, 'canvas_blend_circle_diff_color', - maxDiffRatePercent: operatingSystem == OperatingSystem.macOs ? 2.95 : 0); + maxDiffRatePercent: operatingSystem == OperatingSystem.macOs ? 2.95 : + operatingSystem == OperatingSystem.iOs ? 1.0 : 0); }); test('Blend circle and text with multiply', () async { @@ -120,7 +122,8 @@ void testMain() async { Paint()..blendMode = BlendMode.multiply); rc.restore(); await _checkScreenshot(rc, 'canvas_blend_image_multiply', - maxDiffRatePercent: operatingSystem == OperatingSystem.macOs ? 2.95 : 0); + maxDiffRatePercent: operatingSystem == OperatingSystem.macOs ? 2.95 : + operatingSystem == OperatingSystem.iOs ? 2.0 : 0); }); } diff --git a/lib/web_ui/test/golden_tests/engine/canvas_draw_color_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_draw_color_test.dart new file mode 100644 index 0000000000000..112bffb5d58f7 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/canvas_draw_color_test.dart @@ -0,0 +1,90 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'dart:html' as html; + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart'; +import 'package:ui/src/engine.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() async { + setUp(() async { + debugShowClipLayers = true; + SurfaceSceneBuilder.debugForgetFrameScene(); + for (html.Node scene in html.document.querySelectorAll('flt-scene')) { + scene.remove(); + } + + await webOnlyInitializePlatform(); + webOnlyFontCollection.debugRegisterTestFonts(); + await webOnlyFontCollection.ensureFontsLoaded(); + }); + + test('drawColor should cover entire viewport', () async { + final Rect region = Rect.fromLTWH(0, 0, 400, 400); + + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + final Picture testPicture = _drawTestPicture(region, useColor: true); + builder.addPicture(Offset.zero, testPicture); + + html.document.body.append(builder + .build() + .webOnlyRootElement); + + await matchGoldenFile('canvas_draw_color.png', region: region); + }, skip: true); // TODO: matchGolden fails when a div covers viewport. + + test('drawPaint should cover entire viewport', () async { + final Rect region = Rect.fromLTWH(0, 0, 400, 400); + + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + final Picture testPicture = _drawTestPicture(region, useColor: false); + builder.addPicture(Offset.zero, testPicture); + + html.document.body.append(builder + .build() + .webOnlyRootElement); + + await matchGoldenFile('canvas_draw_paint.png', region: region); + }, skip: true); // TODO: matchGolden fails when a div covers viewport.); +} + +Picture _drawTestPicture(Rect region, {bool useColor = false}) { + final EnginePictureRecorder recorder = PictureRecorder(); + final Rect r = Rect.fromLTWH(0, 0, 200, 200); + final RecordingCanvas canvas = recorder.beginRecording(r); + + canvas.drawRect( + region.deflate(8.0), + Paint() + ..style = PaintingStyle.fill + ..color = Color(0xFFE0E0E0) + ); + + canvas.transform(Matrix4.translationValues(50, 50, 0).storage); + + if (useColor) { + canvas.drawColor(const Color.fromRGBO(0, 255, 0, 1), BlendMode.srcOver); + } else { + canvas.drawPaint(Paint() + ..style = PaintingStyle.fill + ..color = const Color.fromRGBO(0, 0, 255, 1)); + } + + canvas.drawCircle( + Offset(r.width/2, r.height/2), r.width/2, + Paint() + ..style = PaintingStyle.fill + ..color = const Color.fromRGBO(255, 0, 0, 1)); + + return recorder.endRecording(); +} diff --git a/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart index 799ab41050a2e..4a61124d2578a 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart @@ -28,7 +28,8 @@ void testMain() async { // Commit a recording canvas to a bitmap, and compare with the expected Future _checkScreenshot(RecordingCanvas rc, String fileName, {Rect region = const Rect.fromLTWH(0, 0, 500, 500), - double maxDiffRatePercent = 0.0}) async { + double maxDiffRatePercent = 0.0, bool setupPerspective = false, + bool write = false}) async { final EngineCanvas engineCanvas = BitmapCanvas(screenRect); rc.endRecording(); @@ -37,10 +38,18 @@ void testMain() async { // Wrap in so that our CSS selectors kick in. final html.Element sceneElement = html.Element.tag('flt-scene'); try { + if (setupPerspective) { + // iFrame disables perspective, set it explicitly for test. + engineCanvas.rootElement.style.perspective = '400px'; + for (html.Element element in engineCanvas.rootElement.querySelectorAll( + 'div')) { + element.style.perspective = '400px'; + } + } sceneElement.append(engineCanvas.rootElement); html.document.body.append(sceneElement); await matchGoldenFile('$fileName.png', - region: region, maxDiffRatePercent: maxDiffRatePercent); + region: region, maxDiffRatePercent: maxDiffRatePercent, write: write); } finally { // The page is reused across tests, so remove the element after taking the // Scuba screenshot. @@ -400,6 +409,158 @@ void testMain() async { await _checkScreenshot(canvas, 'draw_clipped_and_transformed_image', region: region, maxDiffRatePercent: 1.0); }); + + /// Regression test for https://github.com/flutter/flutter/issues/61245 + test('Should render image with perspective', () async { + final Rect region = const Rect.fromLTRB(0, 0, 200, 200); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.translate(10, 10); + canvas.drawImage(createTestImage(), Offset(0, 0), new Paint()); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.0005); // perspective + canvas.transform(transform.storage); + canvas.drawImage(createTestImage(), Offset(0, 100), new Paint()); + await _checkScreenshot(canvas, 'draw_3d_image', + region: region, + maxDiffRatePercent: 6.0, + setupPerspective: true); + }); + + /// Regression test for https://github.com/flutter/flutter/issues/61245 + test('Should render image with perspective inside clip area', () async { + final Rect region = const Rect.fromLTRB(0, 0, 200, 200); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.drawRect(region, Paint()..color = Color(0xFFE0E0E0)); + canvas.translate(10, 10); + canvas.drawImage(createTestImage(), Offset(0, 0), new Paint()); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.0005); // perspective + canvas.transform(transform.storage); + canvas.clipRect(region, ClipOp.intersect); + canvas.drawRect(Rect.fromLTWH(0, 0, 100, 200), Paint()..color = Color(0x801080E0)); + canvas.drawImage(createTestImage(), Offset(0, 100), new Paint()); + canvas.drawRect(Rect.fromLTWH(50, 150, 50, 20), Paint()..color = Color(0x80000000)); + await _checkScreenshot(canvas, 'draw_3d_image_clipped', + region: region, + maxDiffRatePercent: 5.0, + setupPerspective: true); + }); + + test('Should render rect with perspective transform', () async { + final Rect region = const Rect.fromLTRB(0, 0, 400, 400); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.drawRect(region, Paint()..color = Color(0xFFE0E0E0)); + canvas.translate(20, 20); + canvas.drawRect(Rect.fromLTWH(0, 0, 100, 40), + Paint()..color = Color(0xFF000000)); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.001); // perspective + canvas.transform(transform.storage); + canvas.clipRect(region, ClipOp.intersect); + canvas.drawRect(Rect.fromLTWH(0, 60, 120, 40), Paint()..color = Color(0x801080E0)); + canvas.drawRect(Rect.fromLTWH(300, 250, 120, 40), Paint()..color = Color(0x80E010E0)); + canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(0, 120, 160, 40), Radius.circular(5)), + Paint()..color = Color(0x801080E0)); + canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(300, 320, 90, 40), Radius.circular(20)), + Paint()..color = Color(0x80E010E0)); + await _checkScreenshot(canvas, 'draw_3d_rect_clipped', + region: region, + maxDiffRatePercent: 1.0, + setupPerspective: true); + }); + + test('Should render color and ovals with perspective transform', () async { + final Rect region = const Rect.fromLTRB(0, 0, 400, 400); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.drawRect(region, Paint()..color = Color(0xFFFF0000)); + canvas.drawColor(Color(0xFFE0E0E0), BlendMode.src); + canvas.translate(20, 20); + canvas.drawRect(Rect.fromLTWH(0, 0, 100, 40), + Paint()..color = Color(0xFF000000)); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.001); // perspective + canvas.transform(transform.storage); + canvas.clipRect(region, ClipOp.intersect); + canvas.drawOval(Rect.fromLTWH(0, 120, 130, 40), + Paint()..color = Color(0x801080E0)); + canvas.drawOval(Rect.fromLTWH(300, 290, 90, 40), + Paint()..color = Color(0x80E010E0)); + canvas.drawCircle(Offset(60, 240), 50, Paint()..color = Color(0x801080E0)); + canvas.drawCircle(Offset(360, 370), 30, Paint()..color = Color(0x80E010E0)); + await _checkScreenshot(canvas, 'draw_3d_oval_clipped', + region: region, + maxDiffRatePercent: 1.0, + setupPerspective: true); + }); + + test('Should render path with perspective transform', () async { + final Rect region = const Rect.fromLTRB(0, 0, 400, 400); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.drawRect(region, Paint()..color = Color(0xFFFF0000)); + canvas.drawColor(Color(0xFFE0E0E0), BlendMode.src); + canvas.translate(20, 20); + canvas.drawRect(Rect.fromLTWH(0, 0, 100, 20), + Paint()..color = Color(0xFF000000)); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.001); // perspective + canvas.transform(transform.storage); + canvas.drawRect(Rect.fromLTWH(0, 120, 130, 40), + Paint()..color = Color(0x801080E0)); + canvas.drawOval(Rect.fromLTWH(300, 290, 90, 40), + Paint()..color = Color(0x80E010E0)); + Path path = Path(); + path.moveTo(50, 50); + path.lineTo(100, 50); + path.lineTo(100, 100); + path.close(); + canvas.drawPath(path, Paint()..color = Color(0x801080E0)); + + canvas.drawCircle(Offset(50, 50), 4, Paint()..color = Color(0xFF000000)); + canvas.drawCircle(Offset(100, 100), 4, Paint()..color = Color(0xFF000000)); + canvas.drawCircle(Offset(100, 50), 4, Paint()..color = Color(0xFF000000)); + await _checkScreenshot(canvas, 'draw_3d_path', + region: region, + maxDiffRatePercent: 1.0, + setupPerspective: true); + }); + + test('Should render path with perspective transform', () async { + final Rect region = const Rect.fromLTRB(0, 0, 400, 400); + final RecordingCanvas canvas = RecordingCanvas(region); + canvas.drawRect(region, Paint()..color = Color(0xFFFF0000)); + canvas.drawColor(Color(0xFFE0E0E0), BlendMode.src); + canvas.translate(20, 20); + canvas.drawRect(Rect.fromLTWH(0, 0, 100, 20), + Paint()..color = Color(0xFF000000)); + Matrix4 transform = Matrix4.identity() + ..setRotationY(0.8) + ..setEntry(3, 2, 0.001); // perspective + canvas.transform(transform.storage); + //canvas.clipRect(region, ClipOp.intersect); + canvas.drawRect(Rect.fromLTWH(0, 120, 130, 40), + Paint()..color = Color(0x801080E0)); + canvas.drawOval(Rect.fromLTWH(300, 290, 90, 40), + Paint()..color = Color(0x80E010E0)); + Path path = Path(); + path.moveTo(50, 50); + path.lineTo(100, 50); + path.lineTo(100, 100); + path.close(); + canvas.drawPath(path, Paint()..color = Color(0x801080E0)); + + canvas.drawCircle(Offset(50, 50), 4, Paint()..color = Color(0xFF000000)); + canvas.drawCircle(Offset(100, 100), 4, Paint()..color = Color(0xFF000000)); + canvas.drawCircle(Offset(100, 50), 4, Paint()..color = Color(0xFF000000)); + await _checkScreenshot(canvas, 'draw_3d_path_clipped', + region: region, + maxDiffRatePercent: 1.0, + setupPerspective: true); + }); } // 9 slice test image that has a shiny/glass look. diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index dde35315d38bc..0590725f909fc 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -93,28 +93,31 @@ void testMain() async { // compensate by shifting the contents of the canvas in the opposite // direction. canvas = BitmapCanvas(const Rect.fromLTWH(0.5, 0.5, 60, 60)); - + canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect); drawMisalignedLines(canvas); appendToScene(); - await matchGoldenFile('misaligned_canvas_test.png', region: region); + await matchGoldenFile('misaligned_canvas_test.png', region: region, + maxDiffRatePercent: 1.0); }); test('fill the whole canvas with color even when transformed', () async { canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 50, 50)); - + canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect); canvas.translate(25, 25); canvas.drawColor(const Color.fromRGBO(0, 255, 0, 1.0), BlendMode.src); appendToScene(); - await matchGoldenFile('bitmap_canvas_fills_color_when_transformed.png', region: region); + await matchGoldenFile('bitmap_canvas_fills_color_when_transformed.png', + region: region, + maxDiffRatePercent: 5.0); }); test('fill the whole canvas with paint even when transformed', () async { canvas = BitmapCanvas(const Rect.fromLTWH(0, 0, 50, 50)); - + canvas.clipRect(const Rect.fromLTWH(0, 0, 50, 50), ClipOp.intersect); canvas.translate(25, 25); canvas.drawPaint(SurfacePaintData() ..color = const Color.fromRGBO(0, 255, 0, 1.0) @@ -122,7 +125,9 @@ void testMain() async { appendToScene(); - await matchGoldenFile('bitmap_canvas_fills_paint_when_transformed.png', region: region); + await matchGoldenFile('bitmap_canvas_fills_paint_when_transformed.png', + region: region, + maxDiffRatePercent: 5.0); }); // This test reproduces text blurriness when two pieces of text appear inside @@ -245,7 +250,7 @@ void testMain() async { await matchGoldenFile( 'bitmap_canvas_draws_text_on_top_of_canvas.png', region: canvasSize, - maxDiffRatePercent: 0.0, + maxDiffRatePercent: 1.0, pixelComparison: PixelComparison.precise, ); }); diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 04dce71d0b986..8c2784c7ba03a 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -22,7 +22,9 @@ void main() { void testMain() async { setUp(() async { - debugShowClipLayers = true; + // To debug test failures uncomment the following to visualize clipping + // layers: + // debugShowClipLayers = true; SurfaceSceneBuilder.debugForgetFrameScene(); for (html.Node scene in html.document.querySelectorAll('flt-scene')) { scene.remove(); @@ -545,7 +547,6 @@ void _testCullRectComputation() { 'renders clipped text with high quality', () async { // To reproduce blurriness we need real clipping. - debugShowClipLayers = false; final Paragraph paragraph = (ParagraphBuilder(ParagraphStyle(fontFamily: 'Roboto'))..addText('Am I blurry?')).build(); paragraph.layout(const ParagraphConstraints(width: 1000)); diff --git a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart index c7854c961a798..e7397545124d9 100644 --- a/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/recording_canvas_golden_test.dart @@ -678,7 +678,7 @@ void testMain() async { await matchGoldenFile( 'paint_spread_bounds.png', region: const Rect.fromLTRB(0, 0, 250, 600), - maxDiffRatePercent: 0.01, + maxDiffRatePercent: 0.2, pixelComparison: PixelComparison.precise, ); } finally { From fa11ee6e2af5eb7a615fb072e2c9a92fe0c24e44 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 19 Oct 2020 19:42:01 -0700 Subject: [PATCH 184/219] Fix native constructor of list of zircon handles and remove unused list factory specializations. (#21980) --- lib/ui/text/text_box.h | 14 -------------- .../fuchsia/dart-pkg/zircon/sdk_ext/system.cc | 3 ++- third_party/tonic/dart_wrappable.h | 12 ------------ 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/lib/ui/text/text_box.h b/lib/ui/text/text_box.h index c3eea00aa8999..27f60670f232f 100644 --- a/lib/ui/text/text_box.h +++ b/lib/ui/text/text_box.h @@ -25,18 +25,4 @@ struct TextBox { } // namespace flutter -namespace tonic { - -template <> -struct DartConverter { - static Dart_Handle ToDart(const flutter::TextBox& val); -}; - -template <> -struct DartListFactory { - static Dart_Handle NewList(intptr_t length); -}; - -} // namespace tonic - #endif // FLUTTER_LIB_UI_TEXT_TEXT_BOX_H_ diff --git a/shell/platform/fuchsia/dart-pkg/zircon/sdk_ext/system.cc b/shell/platform/fuchsia/dart-pkg/zircon/sdk_ext/system.cc index 1c3d642a2c987..9482c453d50ca 100644 --- a/shell/platform/fuchsia/dart-pkg/zircon/sdk_ext/system.cc +++ b/shell/platform/fuchsia/dart-pkg/zircon/sdk_ext/system.cc @@ -98,7 +98,8 @@ Dart_Handle MakeHandleList(const std::vector& in_handles) { tonic::DartClassLibrary& class_library = tonic::DartState::Current()->class_library(); Dart_Handle handle_type = class_library.GetClass("zircon", "Handle"); - Dart_Handle list = Dart_NewListOfType(handle_type, in_handles.size()); + Dart_Handle list = Dart_NewListOfTypeFilled( + handle_type, Handle::CreateInvalid(), in_handles.size()); if (Dart_IsError(list)) return list; for (size_t i = 0; i < in_handles.size(); i++) { diff --git a/third_party/tonic/dart_wrappable.h b/third_party/tonic/dart_wrappable.h index 49b0a2c40baf3..a036abb854e7c 100644 --- a/third_party/tonic/dart_wrappable.h +++ b/third_party/tonic/dart_wrappable.h @@ -166,18 +166,6 @@ struct DartConverter> { } }; -template

41`eR`3l$Vv=zMNJq(73Y|ij<8OWC=ZbCZutR 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 MmDnSJgEZ}b0mzqS2><{9 literal 0 HcmV?d00001 diff --git a/e2etests/web/regular_integration_tests/fonts/RobotoMono-Regular.ttf b/e2etests/web/regular_integration_tests/fonts/RobotoMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7c4ce36a442d14eeb12444ad707c2afa19422fd8 GIT binary patch literal 86908 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 main() async => test.integrationDriver(); +/// ``` +/// +/// this method is able to take screenshot. +/// +/// It provides an `onScreenshot` callback to the `integrationDriver` method. +/// It also includes options for updating the golden files. +Future runTestWithScreenshots( + {double diffRateFailure = kMaxDiffRateFailure, + int browserWidth = _kScreenshotWidth, + int browserHeight = _kScreenshotHeight}) async { + final WebFlutterDriver driver = + await FlutterDriver.connect() as WebFlutterDriver; + + // Learn the browser in use from the webDriver. + final String browser = driver.webDriver.capabilities['browserName'] as String; + + final Window window = await driver.webDriver.window; + window.setSize(Rectangle(0, 0, browserWidth, browserHeight)); + + bool updateGoldens = false; + // We are using an environment variable instead of an argument, since + // this code is not invoked from the shell but from the `flutter drive` + // tool itself. Therefore we do not have control on the command line + // arguments. + // Please read the README, further info on how to update the goldens. + final String updateGoldensFlag = io.Platform.environment['UPDATE_GOLDENS']; + // Validate if the environment variable is set correctly. + if (updateGoldensFlag != null && + !(updateGoldensFlag.toLowerCase() == 'true' || + updateGoldensFlag.toLowerCase() == 'false')) { + throw StateError( + 'UPDATE_GOLDENS environment variable is not set correctly'); + } + if (updateGoldensFlag != null && updateGoldensFlag.toLowerCase() == 'true') { + updateGoldens = true; + } + + test.integrationDriver( + driver: driver, + onScreenshot: (String screenshotName, List screenshotBytes) async { + if (browser == 'chrome') { + final Image screenshot = decodePng(screenshotBytes); + final String result = compareImage( + screenshot, + updateGoldens, + '$screenshotName-$browser.png', + PixelComparison.fuzzy, + diffRateFailure, + forIntegrationTests: true, + write: updateGoldens, + ); + if (result == 'OK') { + return true; + } else { + io.stderr.writeln('ERROR: $result'); + return false; + } + } else { + return true; + } + }, + ); +} diff --git a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart index f1aefa7ef1967..d36c0c7ae9f6c 100644 --- a/e2etests/web/regular_integration_tests/lib/text_editing_main.dart +++ b/e2etests/web/regular_integration_tests/lib/text_editing_main.dart @@ -11,6 +11,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( key: const Key('mainapp'), + theme: ThemeData(fontFamily: 'RobotoMono'), title: 'Integration Test App', home: MyHomePage(title: 'Integration Test App'), ); @@ -56,6 +57,7 @@ class _MyHomePageState extends State { enabled: true, controller: _emptyController, decoration: const InputDecoration( + contentPadding: EdgeInsets.all(10.0), labelText: 'Empty Input Field:', ), ), @@ -67,6 +69,7 @@ class _MyHomePageState extends State { enabled: true, controller: _controller, decoration: const InputDecoration( + contentPadding: EdgeInsets.all(10.0), labelText: 'Text Input Field:', ), ), @@ -78,6 +81,7 @@ class _MyHomePageState extends State { enabled: true, controller: _controller2, decoration: const InputDecoration( + contentPadding: EdgeInsets.all(10.0), labelText: 'Text Input Field 2:', ), onFieldSubmitted: (String str) { @@ -94,7 +98,7 @@ class _MyHomePageState extends State { child: SelectableText( 'Lorem ipsum dolor sit amet', key: Key('selectable'), - style: TextStyle(fontFamily: 'Roboto', fontSize: 20.0), + style: TextStyle(fontFamily: 'RobotoMono', fontSize: 20.0), ), ), ], diff --git a/e2etests/web/regular_integration_tests/pubspec.yaml b/e2etests/web/regular_integration_tests/pubspec.yaml index 26ee22f810be2..407980bc02496 100644 --- a/e2etests/web/regular_integration_tests/pubspec.yaml +++ b/e2etests/web/regular_integration_tests/pubspec.yaml @@ -15,8 +15,14 @@ dev_dependencies: sdk: flutter integration_test: 0.9.0 http: 0.12.0+2 - test: any + web_test_utils: + path: ../../../web_sdk/web_test_utils flutter: assets: - assets/images/ + fonts: + - family: RobotoMono + fonts: + - asset: fonts/RobotoMono-Bold.ttf + - asset: fonts/RobotoMono-Regular.ttf diff --git a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart index af1b8d6b00a0f..30acf5a3efb2c 100644 --- a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart +++ b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart @@ -13,7 +13,7 @@ import 'package:flutter/material.dart'; import 'package:integration_test/integration_test.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final IntegrationTestWidgetsFlutterBinding binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; testWidgets('Focused text field creates a native input element', (WidgetTester tester) async { @@ -41,6 +41,8 @@ void main() { textFormField.controller.text = 'New Value'; // DOM element's value also changes. expect(input.value, 'New Value'); + + await binding.takeScreenshot('focused_text_field'); }); testWidgets('Input field with no initial value works', diff --git a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration_test.dart b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration_test.dart index 96b5ad0bf52a4..9c2c0fdcadc77 100644 --- a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration_test.dart +++ b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:integration_test/integration_test_driver.dart' as test; +import 'package:regular_integration_tests/screenshot_support.dart' as test; -Future main() async => test.integrationDriver(); +Future main() async { + await test.runTestWithScreenshots(); +} diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 067577a642f6f..579e6c5086add 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 1a4722227af42c3f51450266016b1a07ae459e73 +revision: da3fef0c0eb849dfbb14b09a088c5f7916677482 diff --git a/lib/web_ui/dev/integration_tests_manager.dart b/lib/web_ui/dev/integration_tests_manager.dart index e01a632f219cd..ac6c2b50a6183 100644 --- a/lib/web_ui/dev/integration_tests_manager.dart +++ b/lib/web_ui/dev/integration_tests_manager.dart @@ -24,7 +24,10 @@ class IntegrationTestsManager { final DriverManager _driverManager; - IntegrationTestsManager(this._browser, this._useSystemFlutter) + final bool _doUpdateScreenshotGoldens; + + IntegrationTestsManager( + this._browser, this._useSystemFlutter, this._doUpdateScreenshotGoldens) : _driverManager = DriverManager.chooseDriver(_browser); Future runTests() async { @@ -159,14 +162,19 @@ class IntegrationTestsManager { Future _runTestsInProfileMode( io.Directory directory, String testName) async { - final String executable = + String executable = _useSystemFlutter ? 'flutter' : environment.flutterCommand.path; + Map enviroment = Map(); + if (_doUpdateScreenshotGoldens) { + enviroment['UPDATE_GOLDENS'] = 'true'; + } final IntegrationArguments arguments = IntegrationArguments.fromBrowser(_browser); final int exitCode = await runProcess( executable, arguments.getTestArguments(testName, 'profile'), workingDirectory: directory.path, + environment: enviroment, ); if (exitCode != 0) { @@ -334,7 +342,7 @@ class ChromeIntegrationArguments extends IntegrationArguments { '--$mode', '--browser-name=chrome', if (isLuci) '--chrome-binary=${preinstalledChromeExecutable()}', - if (isLuci) '--headless', + '--headless', '--local-engine=host_debug_unopt', ]; } diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 18dd92cf1c45e..330457c23b403 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -22,6 +22,8 @@ import 'package:shelf_web_socket/shelf_web_socket.dart'; import 'package:shelf_packages_handler/shelf_packages_handler.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:web_test_utils/goldens.dart'; +import 'package:web_test_utils/image_compare.dart'; import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports @@ -39,7 +41,6 @@ import 'package:test_core/src/runner/configuration.dart'; // ignore: implementat import 'browser.dart'; import 'common.dart'; import 'environment.dart' as env; -import 'goldens.dart'; import 'screenshot_manager.dart'; import 'supported_browsers.dart'; @@ -197,17 +198,6 @@ class BrowserPlatform extends PlatformPlugin { 'golden_files', ); } else { - // On LUCI MacOS bots the goldens are fetched by the recipe code. - // Fetch the goldens if: - // - Tests are running on a local machine. - // - Tests are running on an OS other than macOS. - if (!isLuci || !Platform.isMacOS) { - await fetchGoldens(); - } else { - if (!env.environment.webUiGoldensRepositoryDirectory.existsSync()) { - throw Exception('The goldens directory must have been copied'); - } - } goldensDirectory = p.join( env.environment.webUiGoldensRepositoryDirectory.path, 'engine', @@ -215,19 +205,6 @@ class BrowserPlatform extends PlatformPlugin { ); } - // Bail out fast if golden doesn't exist, and user doesn't want to create it. - final File file = File(p.join( - goldensDirectory, - filename, - )); - if (!file.existsSync() && !write) { - return ''' -Golden file $filename does not exist on path ${file.absolute.path} - -To automatically create this file call matchGoldenFile('$filename', write: true). -'''; - } - final Rectangle regionAsRectange = Rectangle( region['x'] as num, region['y'] as num, @@ -238,115 +215,14 @@ To automatically create this file call matchGoldenFile('$filename', write: true) // Take screenshot. final Image screenshot = await _screenshotManager.capture(regionAsRectange); - if (write) { - // Don't even bother with the comparison, just write and return - print('Updating screenshot golden: $file'); - file.writeAsBytesSync(encodePng(screenshot), flush: true); - if (doUpdateScreenshotGoldens) { - // Do not fail tests when bulk-updating screenshot goldens. - return 'OK'; - } else { - return 'Golden file $filename was updated. You can remove "write: true" in the call to matchGoldenFile.'; - } - } - - // Compare screenshots. - ImageDiff diff = ImageDiff( - golden: decodeNamedImage(file.readAsBytesSync(), filename), - other: screenshot, - pixelComparison: pixelComparison, - ); - - if (diff.rate > 0) { - final String testResultsPath = - env.environment.webUiTestResultsDirectory.path; - final String basename = p.basenameWithoutExtension(file.path); - - final File actualFile = - File(p.join(testResultsPath, '$basename.actual.png')); - actualFile.writeAsBytesSync(encodePng(screenshot), flush: true); - - final File diffFile = File(p.join(testResultsPath, '$basename.diff.png')); - diffFile.writeAsBytesSync(encodePng(diff.diff), flush: true); - - final File expectedFile = - File(p.join(testResultsPath, '$basename.expected.png')); - file.copySync(expectedFile.path); - - final File reportFile = - File(p.join(testResultsPath, '$basename.report.html')); - reportFile.writeAsStringSync(''' -Golden file $filename did not match the image generated by the test. - - - - - - - - - - - - -
ExpectedDiffActual
- - - - - -
-'''); - - final StringBuffer message = StringBuffer(); - message.writeln( - 'Golden file $filename did not match the image generated by the test.'); - message.writeln(getPrintableDiffFilesInfo(diff.rate, maxDiffRateFailure)); - message - .writeln('You can view the test report in your browser by opening:'); - - // Cirrus cannot serve HTML pages generated by build jobs, so we - // archive all the files so that they can be downloaded and inspected - // locally. - if (isCirrus) { - final String taskId = Platform.environment['CIRRUS_TASK_ID']; - final String baseArtifactsUrl = - 'https://api.cirrus-ci.com/v1/artifact/task/$taskId/web_engine_test/test_results'; - final String cirrusReportUrl = '$baseArtifactsUrl/$basename.report.zip'; - message.writeln(cirrusReportUrl); - - await Process.run( - 'zip', - [ - '$basename.report.zip', - '$basename.report.html', - '$basename.expected.png', - '$basename.diff.png', - '$basename.actual.png', - ], - workingDirectory: testResultsPath, - ); - } else { - final String localReportPath = '$testResultsPath/$basename.report.html'; - message.writeln(localReportPath); - } - - message.writeln( - 'To update the golden file call matchGoldenFile(\'$filename\', write: true).'); - message.writeln('Golden file: ${expectedFile.path}'); - message.writeln('Actual file: ${actualFile.path}'); - - if (diff.rate < maxDiffRateFailure) { - // Issue a warning but do not fail the test. - print('WARNING:'); - print(message); - return 'OK'; - } else { - // Fail test - return '$message'; - } - } - return 'OK'; + return compareImage( + screenshot, + doUpdateScreenshotGoldens, + filename, + pixelComparison, + maxDiffRateFailure, + goldensDirectory: goldensDirectory, + write: write); } /// A handler that serves wrapper files used to bootstrap tests. diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index 5ad1b3b53dfc3..74c8de6d89248 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -15,6 +15,7 @@ import 'package:test_core/src/runner/hack_register_platform.dart' import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports +import 'package:web_test_utils/goldens.dart'; import 'common.dart'; import 'environment.dart'; @@ -157,7 +158,10 @@ class TestCommand extends Command with ArgUtils { } Future runIntegrationTests() async { - return IntegrationTestsManager(browser, useSystemFlutter).runTests(); + await _prepare(); + return IntegrationTestsManager( + browser, useSystemFlutter, doUpdateScreenshotGoldens) + .runTests(); } Future runUnitTests() async { @@ -189,6 +193,15 @@ class TestCommand extends Command with ArgUtils { } environment.webUiTestResultsDirectory.createSync(recursive: true); + // If screenshot tests are available, fetch the screenshot goldens. + if (isScreenshotTestsAvailable) { + print('screenshot tests available'); + final GoldensRepoFetcher goldensRepoFetcher = GoldensRepoFetcher( + environment.webUiGoldensRepositoryDirectory, + path.join(environment.webUiDevDir.path, 'goldens_lock.yaml')); + await goldensRepoFetcher.fetch(); + } + // In order to run iOS Safari unit tests we need to make sure iOS Simulator // is booted. if (isSafariIOS) { @@ -371,6 +384,15 @@ class TestCommand extends Command with ArgUtils { isFirefoxIntegrationTestAvailable || isSafariIntegrationTestAvailable; + // Whether the tests will do screenshot testing. + bool get isScreenshotTestsAvailable => + isIntegrationTestsAvailable || isUnitTestsScreenshotsAvailable; + + // For unit tests screenshot tests and smoke tests only run on: + // "Chrome/iOS" for LUCI/local. + bool get isUnitTestsScreenshotsAvailable => + isChrome && (io.Platform.isLinux || !isLuci) || isSafariIOS; + /// Use system flutter instead of cloning the repository. /// /// Read the flag help for more details. Uses PATH to locate flutter. @@ -397,13 +419,7 @@ class TestCommand extends Command with ArgUtils { 'test', )); - // Screenshot tests and smoke tests only run on: "Chrome/iOS Safari" - // locally and on LUCI. They are not available on Windows bots: - // TODO: https://github.com/flutter/flutter/issues/63710 - if ((isChrome && isLuci && io.Platform.isLinux) || - ((isChrome || isSafariIOS) && !isLuci) || - (isSafariIOS && isLuci)) { - print('INFO: Also running the screenshot tests.'); + if (isUnitTestsScreenshotsAvailable) { // Separate screenshot tests from unit-tests. Screenshot tests must run // one at a time. Otherwise, they will end up screenshotting each other. // This is not an issue for unit-tests. @@ -621,7 +637,8 @@ class TestCommand extends Command with ArgUtils { /// Runs a batch of tests. /// - /// Unless [expectFailure] is set to false, sets [io.exitCode] to a non-zero value if any tests fail. + /// Unless [expectFailure] is set to false, sets [io.exitCode] to a non-zero + /// value if any tests fail. Future _runTestBatch( List testFiles, { @required int concurrency, @@ -644,7 +661,8 @@ class TestCommand extends Command with ArgUtils { return BrowserPlatform.start( browser, root: io.Directory.current.path, - // It doesn't make sense to update a screenshot for a test that is expected to fail. + // It doesn't make sense to update a screenshot for a test that is + // expected to fail. doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens, ); }); diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 35f5af4d3fea5..c761816991502 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -14,7 +14,7 @@ dev_dependencies: image: 2.1.13 js: 0.6.1+1 mockito: 4.1.1 - path: 1.7.0 + path: 1.8.0-nullsafety.1 test: 1.14.3 quiver: 2.1.3 build_resolvers: 1.3.10 @@ -23,6 +23,8 @@ dev_dependencies: build_web_compilers: 2.11.0 yaml: 2.2.1 watcher: 0.9.7+15 + web_test_utils: + path: ../../web_sdk/web_test_utils web_engine_tester: path: ../../web_sdk/web_engine_tester simulators: diff --git a/web_sdk/web_test_utils/lib/environment.dart b/web_sdk/web_test_utils/lib/environment.dart new file mode 100644 index 0000000000000..b56fd552713de --- /dev/null +++ b/web_sdk/web_test_utils/lib/environment.dart @@ -0,0 +1,214 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 +import 'dart:io' as io; +import 'package:path/path.dart' as pathlib; + +import 'exceptions.dart'; + +/// Contains various environment variables, such as common file paths and command-line options. +Environment get environment { + _environment ??= Environment(); + return _environment; +} + +Environment _environment; + +/// Contains various environment variables, such as common file paths and command-line options. +class Environment { + factory Environment() { + final io.File self = io.File.fromUri(io.Platform.script); + final io.Directory engineSrcDir = self.parent.parent.parent.parent.parent; + return _prepareEnvironmentFromEngineDir(self, engineSrcDir); + } + + factory Environment.forIntegrationTests() { + final io.File self = io.File.fromUri(io.Platform.script); + final io.Directory engineSrcDir = + self.parent.parent.parent.parent.parent.parent; + return _prepareEnvironmentFromEngineDir(self, engineSrcDir); + } + + static Environment _prepareEnvironmentFromEngineDir( + io.File self, io.Directory engineSrcDir) { + final io.Directory engineToolsDir = + io.Directory(pathlib.join(engineSrcDir.path, 'flutter', 'tools')); + final io.Directory outDir = + io.Directory(pathlib.join(engineSrcDir.path, 'out')); + final io.Directory hostDebugUnoptDir = + io.Directory(pathlib.join(outDir.path, 'host_debug_unopt')); + final io.Directory dartSdkDir = + io.Directory(pathlib.join(hostDebugUnoptDir.path, 'dart-sdk')); + final io.Directory webUiRootDir = io.Directory( + pathlib.join(engineSrcDir.path, 'flutter', 'lib', 'web_ui')); + final io.Directory integrationTestsDir = io.Directory( + pathlib.join(engineSrcDir.path, 'flutter', 'e2etests', 'web')); + + for (io.Directory expectedDirectory in [ + engineSrcDir, + outDir, + hostDebugUnoptDir, + dartSdkDir, + webUiRootDir + ]) { + if (!expectedDirectory.existsSync()) { + throw ToolException('$expectedDirectory does not exist.'); + } + } + + return Environment._( + self: self, + webUiRootDir: webUiRootDir, + engineSrcDir: engineSrcDir, + engineToolsDir: engineToolsDir, + integrationTestsDir: integrationTestsDir, + outDir: outDir, + hostDebugUnoptDir: hostDebugUnoptDir, + dartSdkDir: dartSdkDir, + ); + } + + Environment._({ + this.self, + this.webUiRootDir, + this.engineSrcDir, + this.engineToolsDir, + this.integrationTestsDir, + this.outDir, + this.hostDebugUnoptDir, + this.dartSdkDir, + }); + + /// The Dart script that's currently running. + final io.File self; + + /// Path to the "web_ui" package sources. + final io.Directory webUiRootDir; + + /// Path to the engine's "src" directory. + final io.Directory engineSrcDir; + + /// Path to the engine's "tools" directory. + final io.Directory engineToolsDir; + + /// Path to the web integration tests. + final io.Directory integrationTestsDir; + + /// Path to the engine's "out" directory. + /// + /// This is where you'll find the ninja output, such as the Dart SDK. + final io.Directory outDir; + + /// The "host_debug_unopt" build of the Dart SDK. + final io.Directory hostDebugUnoptDir; + + /// The root of the Dart SDK. + final io.Directory dartSdkDir; + + /// The "dart" executable file. + String get dartExecutable => pathlib.join(dartSdkDir.path, 'bin', 'dart'); + + /// The "pub" executable file. + String get pubExecutable => pathlib.join(dartSdkDir.path, 'bin', 'pub'); + + /// The "dart2js" executable file. + String get dart2jsExecutable => + pathlib.join(dartSdkDir.path, 'bin', 'dart2js'); + + /// Path to where github.com/flutter/engine is checked out inside the engine workspace. + io.Directory get flutterDirectory => + io.Directory(pathlib.join(engineSrcDir.path, 'flutter')); + io.Directory get webSdkRootDir => io.Directory(pathlib.join( + flutterDirectory.path, + 'web_sdk', + )); + + /// Path to the "web_engine_tester" package. + io.Directory get webEngineTesterRootDir => io.Directory(pathlib.join( + webSdkRootDir.path, + 'web_engine_tester', + )); + + /// Path to the "build" directory, generated by "package:build_runner". + /// + /// This is where compiled output goes. + io.Directory get webUiBuildDir => io.Directory(pathlib.join( + webUiRootDir.path, + 'build', + )); + + /// Path to the ".dart_tool" directory, generated by various Dart tools. + io.Directory get webUiDartToolDir => io.Directory(pathlib.join( + webUiRootDir.path, + '.dart_tool', + )); + + /// Path to the ".dart_tool" directory living under `engine/src/flutter`. + /// + /// This is a designated area for tool downloads which can be used by + /// multiple platforms. For exampe: Flutter repo for e2e tests. + io.Directory get engineDartToolDir => io.Directory(pathlib.join( + engineSrcDir.path, + 'flutter', + '.dart_tool', + )); + + /// Path to the "dev" directory containing engine developer tools and + /// configuration files. + io.Directory get webUiDevDir => io.Directory(pathlib.join( + webUiRootDir.path, + 'dev', + )); + + /// Path to the "test" directory containing web engine tests. + io.Directory get webUiTestDir => io.Directory(pathlib.join( + webUiRootDir.path, + 'test', + )); + + /// Path to the "lib" directory containing web engine code. + io.Directory get webUiLibDir => io.Directory(pathlib.join( + webUiRootDir.path, + 'lib', + )); + + /// Path to the clone of the flutter/goldens repository. + io.Directory get webUiGoldensRepositoryDirectory => io.Directory(pathlib.join( + webUiDartToolDir.path, + 'goldens', + )); + + /// Directory to add test results which would later be uploaded to a gcs + /// bucket by LUCI. + io.Directory get webUiTestResultsDirectory => io.Directory(pathlib.join( + webUiDartToolDir.path, + 'test_results', + )); + + /// Path to the screenshots taken by iOS simulator. + io.Directory get webUiSimulatorScreenshotsDirectory => + io.Directory(pathlib.join( + webUiDartToolDir.path, + 'ios_screenshots', + )); + + /// Path to the script that clones the Flutter repo. + io.File get cloneFlutterScript => io.File(pathlib.join( + engineToolsDir.path, + 'clone_flutter.sh', + )); + + /// Path to flutter. + /// + /// For example, this can be used to run `flutter pub get`. + /// + /// Only use [cloneFlutterScript] to clone flutter to the engine build. + io.File get flutterCommand => io.File(pathlib.join( + engineDartToolDir.path, + 'flutter', + 'bin', + 'flutter', + )); +} diff --git a/web_sdk/web_test_utils/lib/exceptions.dart b/web_sdk/web_test_utils/lib/exceptions.dart new file mode 100644 index 0000000000000..167d1734e655f --- /dev/null +++ b/web_sdk/web_test_utils/lib/exceptions.dart @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class BrowserInstallerException implements Exception { + BrowserInstallerException(this.message); + + final String message; + + @override + String toString() => message; +} + +class DriverException implements Exception { + DriverException(this.message); + + final String message; + + @override + String toString() => message; +} + +class ToolException implements Exception { + ToolException(this.message); + + final String message; + + @override + String toString() => message; +} diff --git a/lib/web_ui/dev/goldens.dart b/web_sdk/web_test_utils/lib/goldens.dart similarity index 68% rename from lib/web_ui/dev/goldens.dart rename to web_sdk/web_test_utils/lib/goldens.dart index fef621c724385..480575dc0c998 100644 --- a/lib/web_ui/dev/goldens.dart +++ b/web_sdk/web_test_utils/lib/goldens.dart @@ -6,10 +6,8 @@ import 'dart:io' as io; import 'package:image/image.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; -import 'package:yaml/yaml.dart'; -import 'environment.dart'; -import 'utils.dart'; +import 'package:yaml/yaml.dart'; /// How to compares pixels within the image. /// @@ -30,14 +28,14 @@ void main(List args) { final io.File fileB = io.File(args[1]); final Image imageA = decodeNamedImage(fileA.readAsBytesSync(), 'a.png'); final Image imageB = decodeNamedImage(fileB.readAsBytesSync(), 'b.png'); - final ImageDiff diff = ImageDiff(golden: imageA, other: imageB, pixelComparison: PixelComparison.fuzzy); + final ImageDiff diff = ImageDiff( + golden: imageA, other: imageB, pixelComparison: PixelComparison.fuzzy); print('Diff: ${(diff.rate * 100).toStringAsFixed(4)}%'); } /// This class encapsulates visually diffing an Image with any other. /// Both images need to be the exact same size. class ImageDiff { - /// The image to match final Image golden; @@ -59,6 +57,8 @@ class ImageDiff { /// This gets set to 1 (100% difference) when golden and other aren't the same size. double get rate => _wrongPixels / _pixelCount; + /// Image diff constructor which requires two [Image]s to compare and + /// [PixelComparison] algorithm. ImageDiff({ @required this.golden, @required this.other, @@ -72,8 +72,8 @@ class ImageDiff { /// That would be the distance between black and white. static final double _maxTheoreticalColorDistance = Color.distance( - [255, 255, 255], // white - [0, 0, 0], // black + [255, 255, 255], // white + [0, 0, 0], // black false, ).toDouble(); @@ -121,11 +121,9 @@ class ImageDiff { _reflectedPixel(image, x - 1, y - 1), _reflectedPixel(image, x - 1, y), _reflectedPixel(image, x - 1, y + 1), - _reflectedPixel(image, x, y - 1), _reflectedPixel(image, x, y), _reflectedPixel(image, x, y + 1), - _reflectedPixel(image, x + 1, y - 1), _reflectedPixel(image, x + 1, y), _reflectedPixel(image, x + 1, y + 1), @@ -148,25 +146,30 @@ class ImageDiff { } void _computeDiff() { - int goldenWidth = golden.width; - int goldenHeight = golden.height; + final int goldenWidth = golden.width; + final int goldenHeight = golden.height; _pixelCount = goldenWidth * goldenHeight; diff = Image(goldenWidth, goldenHeight); if (goldenWidth == other.width && goldenHeight == other.height) { - for(int y = 0; y < goldenHeight; y++) { + for (int y = 0; y < goldenHeight; y++) { for (int x = 0; x < goldenWidth; x++) { - final bool isExactlySame = golden.getPixel(x, y) == other.getPixel(x, y); + final bool isExactlySame = + golden.getPixel(x, y) == other.getPixel(x, y); final List goldenPixel = _getPixelRgbForComparison(golden, x, y); final List otherPixel = _getPixelRgbForComparison(other, x, y); - final double colorDistance = Color.distance(goldenPixel, otherPixel, false) / _maxTheoreticalColorDistance; + final double colorDistance = + Color.distance(goldenPixel, otherPixel, false) / + _maxTheoreticalColorDistance; final bool isFuzzySame = colorDistance < _kColorDistanceThreshold; if (isExactlySame || isFuzzySame) { diff.setPixel(x, y, _colorOk); } else { - final int goldenLuminance = getLuminanceRgb(goldenPixel[0], goldenPixel[1], goldenPixel[2]); - final int otherLuminance = getLuminanceRgb(otherPixel[0], otherPixel[1], otherPixel[2]); + final int goldenLuminance = + getLuminanceRgb(goldenPixel[0], goldenPixel[1], goldenPixel[2]); + final int otherLuminance = + getLuminanceRgb(otherPixel[0], otherPixel[1], otherPixel[2]); if (goldenLuminance < otherLuminance) { diff.setPixel(x, y, _colorExpectedPixel); } else { @@ -183,26 +186,31 @@ class ImageDiff { } } -// Returns text explaining pixel difference rate. +/// Returns text explaining pixel difference rate. String getPrintableDiffFilesInfo(double diffRate, double maxRate) => - '(${((diffRate) * 100).toStringAsFixed(4)}% of pixels were different. ' - 'Maximum allowed rate is: ${(maxRate * 100).toStringAsFixed(4)}%).'; + '(${((diffRate) * 100).toStringAsFixed(4)}% of pixels were different. ' + 'Maximum allowed rate is: ${(maxRate * 100).toStringAsFixed(4)}%).'; -/// Fetches golden files from github.com/flutter/goldens, cloning the repository if necessary. +/// Downloads the repository that stores the golden files. /// -/// The repository is cloned into web_ui/.dart_tool. -Future fetchGoldens() async { - await _GoldensRepoFetcher().fetch(); -} - -class _GoldensRepoFetcher { +/// Reads the url of the repo and `commit no` to sync to, from +/// `goldens_lock.yaml`. +class GoldensRepoFetcher { String _repository; String _revision; + final io.Directory _webUiGoldensRepositoryDirectory; + final String _lockFilePath; + /// Constructor that takes directory to download the repository and + /// file with goldens repo information. + GoldensRepoFetcher(this._webUiGoldensRepositoryDirectory, this._lockFilePath); + + /// Fetches golden files from github.com/flutter/goldens, cloning the + /// repository if necessary. + /// + /// The repository is cloned into web_ui/.dart_tool. Future fetch() async { - final io.File lockFile = io.File( - path.join(environment.webUiDevDir.path, 'goldens_lock.yaml') - ); + final io.File lockFile = io.File(path.join(_lockFilePath)); final YamlMap lock = loadYaml(lockFile.readAsStringSync()) as YamlMap; _repository = lock['repository'] as String; _revision = lock['revision'] as String; @@ -214,40 +222,32 @@ class _GoldensRepoFetcher { print('Fetching $_repository@$_revision'); - if (!environment.webUiGoldensRepositoryDirectory.existsSync()) { - environment.webUiGoldensRepositoryDirectory.createSync(recursive: true); - await runProcess( - 'git', + if (!_webUiGoldensRepositoryDirectory.existsSync()) { + _webUiGoldensRepositoryDirectory.createSync(recursive: true); + await _runGit( ['init'], - workingDirectory: environment.webUiGoldensRepositoryDirectory.path, - mustSucceed: true, + _webUiGoldensRepositoryDirectory.path, ); - await runProcess( - 'git', + + await _runGit( ['remote', 'add', 'origin', _repository], - workingDirectory: environment.webUiGoldensRepositoryDirectory.path, - mustSucceed: true, + _webUiGoldensRepositoryDirectory.path, ); } - await runProcess( - 'git', + await _runGit( ['fetch', 'origin', 'master'], - workingDirectory: environment.webUiGoldensRepositoryDirectory.path, - mustSucceed: true, + _webUiGoldensRepositoryDirectory.path, ); - await runProcess( - 'git', + await _runGit( ['checkout', _revision], - workingDirectory: environment.webUiGoldensRepositoryDirectory.path, - mustSucceed: true, + _webUiGoldensRepositoryDirectory.path, ); } Future _getLocalRevision() async { - final io.File head = io.File(path.join( - environment.webUiGoldensRepositoryDirectory.path, '.git', 'HEAD' - )); + final io.File head = io.File( + path.join(_webUiGoldensRepositoryDirectory.path, '.git', 'HEAD')); if (!head.existsSync()) { return null; @@ -255,4 +255,26 @@ class _GoldensRepoFetcher { return head.readAsStringSync().trim(); } + + /// Runs `git` with given arguments. + Future _runGit( + List arguments, + String workingDirectory, + ) async { + final io.Process process = await io.Process.start( + 'git', + arguments, + workingDirectory: workingDirectory, + // Running the process in a system shell for Windows. Otherwise + // the process is not able to get Dart from path. + runInShell: io.Platform.isWindows, + mode: io.ProcessStartMode.inheritStdio, + ); + final int exitCode = await process.exitCode; + if (exitCode != 0) { + throw Exception('Git command failed with arguments $arguments on ' + 'workingDirectory: $workingDirectory resulting with exitCode: ' + '$exitCode'); + } + } } diff --git a/web_sdk/web_test_utils/lib/image_compare.dart b/web_sdk/web_test_utils/lib/image_compare.dart new file mode 100644 index 0000000000000..137d04701aeda --- /dev/null +++ b/web_sdk/web_test_utils/lib/image_compare.dart @@ -0,0 +1,141 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:image/image.dart'; +import 'package:path/path.dart' as p; + +import 'environment.dart'; +import 'goldens.dart'; + +/// Compares a screenshot taken through a test with it's golden. +/// +/// Used by Flutter Web Engine unit tests and the integration tests. +/// +/// Returns the results of the tests as `String`. When tests passes the result +/// is simply `OK`, however when they fail it contains a detailed explanation +/// on which files are compared, their absolute locations and an HTML page +/// that the developer can see the comparison. +String compareImage( + Image screenshot, + bool doUpdateScreenshotGoldens, + String filename, + PixelComparison pixelComparison, + double maxDiffRateFailure, { + String goldensDirectory = '', + bool forIntegrationTests = false, + bool write = false, +}) { + final Environment environment = + forIntegrationTests ? Environment.forIntegrationTests() : Environment(); + if (goldensDirectory.isEmpty) { + goldensDirectory = p.join( + environment.webUiGoldensRepositoryDirectory.path, + 'engine', + 'web', + ); + } + // Bail out fast if golden doesn't exist, and user doesn't want to create it. + final File file = File(p.join( + goldensDirectory, + filename, + )); + if (!file.existsSync() && !write) { + return ''' +Golden file $filename does not exist. + +To automatically create this file call matchGoldenFile('$filename', write: true). +'''; + } + if (write) { + // Don't even bother with the comparison, just write and return + print('Updating screenshot golden: $file'); + file.writeAsBytesSync(encodePng(screenshot), flush: true); + if (doUpdateScreenshotGoldens) { + // Do not fail tests when bulk-updating screenshot goldens. + return 'OK'; + } else { + return 'Golden file $filename was updated. You can remove "write: true" ' + 'in the call to matchGoldenFile.'; + } + } + + final Image golden = decodeNamedImage(file.readAsBytesSync(), filename); + + // Compare screenshots. + final ImageDiff diff = ImageDiff( + golden: golden, + other: screenshot, + pixelComparison: pixelComparison, + ); + + if (diff.rate > 0) { + final String testResultsPath = environment.webUiTestResultsDirectory.path; + Directory(testResultsPath).createSync(recursive: true); + final String basename = p.basenameWithoutExtension(file.path); + + final File actualFile = + File(p.join(testResultsPath, '$basename.actual.png')); + actualFile.writeAsBytesSync(encodePng(screenshot), flush: true); + + final File diffFile = File(p.join(testResultsPath, '$basename.diff.png')); + diffFile.writeAsBytesSync(encodePng(diff.diff), flush: true); + + final File expectedFile = + File(p.join(testResultsPath, '$basename.expected.png')); + file.copySync(expectedFile.path); + + final File reportFile = + File(p.join(testResultsPath, '$basename.report.html')); + reportFile.writeAsStringSync(''' +Golden file $filename did not match the image generated by the test. + + + + + + + + + + + + +
ExpectedDiffActual
+ + + + + +
+'''); + + final StringBuffer message = StringBuffer(); + message.writeln( + 'Golden file $filename did not match the image generated by the test.'); + message.writeln(getPrintableDiffFilesInfo(diff.rate, maxDiffRateFailure)); + message.writeln('You can view the test report in your browser by opening:'); + + final String localReportPath = '$testResultsPath/$basename.report.html'; + message.writeln(localReportPath); + + message.writeln( + 'To update the golden file call matchGoldenFile(\'$filename\', write: ' + 'true).'); + message.writeln('Golden file: ${expectedFile.path}'); + message.writeln('Actual file: ${actualFile.path}'); + + if (diff.rate < maxDiffRateFailure) { + // Issue a warning but do not fail the test. + print('WARNING:'); + print(message); + return 'OK'; + } else { + // Fail test + return '$message'; + } + } + return 'OK'; +} diff --git a/web_sdk/web_test_utils/pubspec.yaml b/web_sdk/web_test_utils/pubspec.yaml new file mode 100644 index 0000000000000..37de1d9c5d34f --- /dev/null +++ b/web_sdk/web_test_utils/pubspec.yaml @@ -0,0 +1,10 @@ +name: web_test_utils + +environment: + sdk: ">=2.2.0 <3.0.0" + +dependencies: + path: 1.8.0-nullsafety.1 + image: 2.1.13 + js: 0.6.1+1 + yaml: 2.2.1 From d912d502d32d93f5f23284744815f274884a3b95 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Mon, 12 Oct 2020 16:35:09 -0700 Subject: [PATCH 078/219] [null-safety] fix build rule to produce sound dill (#21784) The space in the argument name was causing this argument to be dropped and the sound and unsound dills to be identical. --- web_sdk/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_sdk/BUILD.gn b/web_sdk/BUILD.gn index 4ce681c00d04a..4536f2670cb31 100644 --- a/web_sdk/BUILD.gn +++ b/web_sdk/BUILD.gn @@ -369,7 +369,7 @@ prebuilt_dart_action("flutter_dartdevc_kernel_sdk_outline_sound") { args = [ "--enable-experiment=non-nullable", - "--sound-null-safety ", + "--sound-null-safety", "--summary-only", "--target", "ddc", From 05d97932b3da928b9bf097c8eb70014221595af7 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Mon, 12 Oct 2020 17:50:11 -0700 Subject: [PATCH 079/219] Extract a TextRange class for selection (#21722) Extracts a TextRange class with a base and extent, and start(), end(), collapsed(), and length() getters. The possibility of reversed base and extent in selections and composing ranges makes reasoning about them complex and increases the chances of errors in the code. This change migrates most uses of base and extent in the text model to start()/end() or position(). The position() method is intended purely as an aid to readability to indicate that a collapsed selection is expected at the call site; it also enforces a debug-time assertion that this is the case. --- shell/platform/common/cpp/BUILD.gn | 8 +- shell/platform/common/cpp/text_input_model.cc | 107 ++++++++---------- shell/platform/common/cpp/text_input_model.h | 21 +--- shell/platform/common/cpp/text_range.h | 53 +++++++++ .../common/cpp/text_range_unittests.cc | 53 +++++++++ 5 files changed, 164 insertions(+), 78 deletions(-) create mode 100644 shell/platform/common/cpp/text_range.h create mode 100644 shell/platform/common/cpp/text_range_unittests.cc diff --git a/shell/platform/common/cpp/BUILD.gn b/shell/platform/common/cpp/BUILD.gn index a25c38f652588..d1b4888c2f9e9 100644 --- a/shell/platform/common/cpp/BUILD.gn +++ b/shell/platform/common/cpp/BUILD.gn @@ -37,7 +37,10 @@ copy("publish_headers") { } source_set("common_cpp_input") { - public = [ "text_input_model.h" ] + public = [ + "text_input_model.h", + "text_range.h", + ] sources = [ "text_input_model.cc" ] @@ -45,6 +48,8 @@ source_set("common_cpp_input") { public_configs = [ "//flutter:config" ] + deps = [ "//flutter/fml:fml" ] + if (is_win) { # For wstring_conversion. See issue #50053. defines = [ "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING" ] @@ -137,6 +142,7 @@ if (enable_unittests) { "json_message_codec_unittests.cc", "json_method_codec_unittests.cc", "text_input_model_unittests.cc", + "text_range_unittests.cc", ] deps = [ diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc index 8b17a3a26d640..1ef5b6f3883e9 100644 --- a/shell/platform/common/cpp/text_input_model.cc +++ b/shell/platform/common/cpp/text_input_model.cc @@ -38,27 +38,24 @@ void TextInputModel::SetText(const std::string& text) { std::wstring_convert, char16_t> utf16_converter; text_ = utf16_converter.from_bytes(text); - selection_base_ = 0; - selection_extent_ = 0; + selection_ = TextRange(0); } bool TextInputModel::SetSelection(size_t base, size_t extent) { - auto max_pos = text_.length(); + size_t max_pos = text_.length(); if (base > max_pos || extent > max_pos) { return false; } - selection_base_ = base; - selection_extent_ = extent; + selection_ = TextRange(base, extent); return true; } bool TextInputModel::DeleteSelected() { - if (selection_base_ == selection_extent_) { + if (selection_.collapsed()) { return false; } - text_.erase(selection_start(), selection_end() - selection_start()); - selection_base_ = selection_start(); - selection_extent_ = selection_base_; + text_.erase(selection_.start(), selection_.length()); + selection_ = TextRange(selection_.start()); return true; } @@ -78,9 +75,9 @@ void TextInputModel::AddCodePoint(char32_t c) { void TextInputModel::AddText(const std::u16string& text) { DeleteSelected(); - text_.insert(selection_extent_, text); - selection_extent_ += text.length(); - selection_base_ = selection_extent_; + size_t position = selection_.position(); + text_.insert(position, text); + selection_ = TextRange(position + text.length()); } void TextInputModel::AddText(const std::string& text) { @@ -90,38 +87,36 @@ void TextInputModel::AddText(const std::string& text) { } bool TextInputModel::Backspace() { - // If there's a selection, delete it. if (DeleteSelected()) { return true; } - // There's no selection; delete the preceding codepoint. - if (selection_base_ != 0) { - int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1; - text_.erase(selection_base_ - count, count); - selection_base_ -= count; - selection_extent_ = selection_base_; + // If there's no selection, delete the preceding codepoint. + size_t position = selection_.position(); + if (position != 0) { + int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; + text_.erase(position - count, count); + selection_ = TextRange(position - count); return true; } return false; } bool TextInputModel::Delete() { - // If there's a selection, delete it. if (DeleteSelected()) { return true; } - // There's no selection; delete the following codepoint. - if (selection_base_ != text_.length()) { - int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1; - text_.erase(selection_base_, count); - selection_extent_ = selection_base_; + // If there's no selection, delete the preceding codepoint. + size_t position = selection_.position(); + if (position != text_.length()) { + int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; + text_.erase(position, count); return true; } return false; } bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { - auto start = selection_extent_; + size_t start = selection_.extent(); if (offset_from_cursor < 0) { for (int i = 0; i < -offset_from_cursor; i++) { // If requested start is before the available text then reduce the @@ -150,65 +145,53 @@ bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) { text_.erase(start, end - start); // Cursor moves only if deleted area is before it. - if (offset_from_cursor <= 0) { - selection_base_ = start; - } - - // Clear selection. - selection_extent_ = selection_base_; + selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start()); return true; } bool TextInputModel::MoveCursorToBeginning() { - if (selection_base_ == 0 && selection_extent_ == 0) + if (selection_.collapsed() && selection_.position() == 0) return false; - - selection_base_ = 0; - selection_extent_ = 0; + selection_ = TextRange(0); return true; } bool TextInputModel::MoveCursorToEnd() { - auto max_pos = text_.length(); - if (selection_base_ == max_pos && selection_extent_ == max_pos) + size_t max_pos = text_.length(); + if (selection_.collapsed() && selection_.position() == max_pos) return false; - - selection_base_ = max_pos; - selection_extent_ = max_pos; + selection_ = TextRange(max_pos); return true; } bool TextInputModel::MoveCursorForward() { - // If about to move set to the end of the highlight (when not selecting). - if (selection_base_ != selection_extent_) { - selection_base_ = selection_end(); - selection_extent_ = selection_base_; + // If there's a selection, move to the end of the selection. + if (!selection_.collapsed()) { + selection_ = TextRange(selection_.end()); return true; } - // If not at the end, move the extent forward. - if (selection_extent_ != text_.length()) { - int count = IsLeadingSurrogate(text_.at(selection_base_)) ? 2 : 1; - selection_base_ += count; - selection_extent_ = selection_base_; + // Otherwise, move the cursor forward. + size_t position = selection_.position(); + if (position != text_.length()) { + int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1; + selection_ = TextRange(position + count); return true; } return false; } bool TextInputModel::MoveCursorBack() { - // If about to move set to the beginning of the highlight - // (when not selecting). - if (selection_base_ != selection_extent_) { - selection_base_ = selection_start(); - selection_extent_ = selection_base_; + // If there's a selection, move to the beginning of the selection. + if (!selection_.collapsed()) { + selection_ = TextRange(selection_.start()); return true; } - // If not at the start, move the beginning backward. - if (selection_base_ != 0) { - int count = IsTrailingSurrogate(text_.at(selection_base_ - 1)) ? 2 : 1; - selection_base_ -= count; - selection_extent_ = selection_base_; + // Otherwise, move the cursor backward. + size_t position = selection_.position(); + if (position != 0) { + int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1; + selection_ = TextRange(position - count); return true; } return false; @@ -221,9 +204,9 @@ std::string TextInputModel::GetText() const { } int TextInputModel::GetCursorOffset() const { - // Measure the length of the current text up to the cursor. + // Measure the length of the current text up to the selection extent. // There is probably a much more efficient way of doing this. - auto leading_text = text_.substr(0, selection_extent_); + auto leading_text = text_.substr(0, selection_.extent()); std::wstring_convert, char16_t> utf8_converter; return utf8_converter.to_bytes(leading_text).size(); diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h index fc2bd85ec848f..c5753f85e1218 100644 --- a/shell/platform/common/cpp/text_input_model.h +++ b/shell/platform/common/cpp/text_input_model.h @@ -5,11 +5,13 @@ #ifndef FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_ #define FLUTTER_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_ -#include #include #include +#include "flutter/shell/platform/common/cpp/text_range.h" + namespace flutter { + // Handles underlying text input state, using a simple ASCII model. // // Ignores special states like "insert mode" for now. @@ -104,10 +106,10 @@ class TextInputModel { int GetCursorOffset() const; // The position where the selection starts. - int selection_base() const { return selection_base_; } + int selection_base() const { return selection_.base(); } // The position of the cursor. - int selection_extent() const { return selection_extent_; } + int selection_extent() const { return selection_.extent(); } private: // Deletes the current selection, if any. @@ -117,18 +119,7 @@ class TextInputModel { bool DeleteSelected(); std::u16string text_; - size_t selection_base_ = 0; - size_t selection_extent_ = 0; - - // Returns the left hand side of the selection. - size_t selection_start() const { - return std::min(selection_base_, selection_extent_); - } - - // Returns the right hand side of the selection. - size_t selection_end() const { - return std::max(selection_base_, selection_extent_); - } + TextRange selection_ = TextRange(0); }; } // namespace flutter diff --git a/shell/platform/common/cpp/text_range.h b/shell/platform/common/cpp/text_range.h new file mode 100644 index 0000000000000..6524a26e2594a --- /dev/null +++ b/shell/platform/common/cpp/text_range.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "flutter/fml/logging.h" + +// A directional range of text. +// +// A |TextRange| describes a range of text with |base| and |extent| positions. +// In the case where |base| == |extent|, the range is said to be collapsed, and +// when |base| > |extent|, the range is said to be reversed. +class TextRange { + public: + explicit TextRange(size_t position) : base_(position), extent_(position) {} + explicit TextRange(size_t base, size_t extent) + : base_(base), extent_(extent) {} + TextRange(const TextRange&) = default; + TextRange& operator=(const TextRange&) = default; + + virtual ~TextRange() = default; + + // Returns the base position of the range. + size_t base() const { return base_; } + + // Returns the extent position of the range. + size_t extent() const { return extent_; } + + // Returns the lesser of the base and extent positions. + size_t start() const { return std::min(base_, extent_); } + + // Returns the greater of the base and extent positions. + size_t end() const { return std::max(base_, extent_); } + + // Returns the position of a collapsed range. + // + // Asserts that the range is of length 0. + size_t position() const { + FML_DCHECK(base_ == extent_); + return extent_; + } + + // Returns the length of the range. + size_t length() const { return end() - start(); } + + // Returns true if the range is of length 0. + bool collapsed() const { return base_ == extent_; } + + private: + size_t base_; + size_t extent_; +}; diff --git a/shell/platform/common/cpp/text_range_unittests.cc b/shell/platform/common/cpp/text_range_unittests.cc new file mode 100644 index 0000000000000..cdeff77a8ceea --- /dev/null +++ b/shell/platform/common/cpp/text_range_unittests.cc @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/common/cpp/text_range.h" + +#include "gtest/gtest.h" + +namespace flutter { + +TEST(TextRange, TextRangeFromPositionZero) { + TextRange range(0); + EXPECT_EQ(range.base(), size_t(0)); + EXPECT_EQ(range.extent(), size_t(0)); + EXPECT_EQ(range.start(), size_t(0)); + EXPECT_EQ(range.end(), size_t(0)); + EXPECT_EQ(range.length(), size_t(0)); + EXPECT_EQ(range.position(), size_t(0)); + EXPECT_TRUE(range.collapsed()); +} + +TEST(TextRange, TextRangeFromPositionNonZero) { + TextRange range(3); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(3)); + EXPECT_EQ(range.start(), size_t(3)); + EXPECT_EQ(range.end(), size_t(3)); + EXPECT_EQ(range.length(), size_t(0)); + EXPECT_EQ(range.position(), size_t(3)); + EXPECT_TRUE(range.collapsed()); +} + +TEST(TextRange, TextRangeFromRange) { + TextRange range(3, 7); + EXPECT_EQ(range.base(), size_t(3)); + EXPECT_EQ(range.extent(), size_t(7)); + EXPECT_EQ(range.start(), size_t(3)); + EXPECT_EQ(range.end(), size_t(7)); + EXPECT_EQ(range.length(), size_t(4)); + EXPECT_FALSE(range.collapsed()); +} + +TEST(TextRange, TextRangeFromReversedRange) { + TextRange range(7, 3); + EXPECT_EQ(range.base(), size_t(7)); + EXPECT_EQ(range.extent(), size_t(3)); + EXPECT_EQ(range.start(), size_t(3)); + EXPECT_EQ(range.end(), size_t(7)); + EXPECT_EQ(range.length(), size_t(4)); + EXPECT_FALSE(range.collapsed()); +} + +} // namespace flutter From c2938d06b193fb8a62e4c8fab65507be55f64242 Mon Sep 17 00:00:00 2001 From: Yuqian Li Date: Mon, 12 Oct 2020 19:26:41 -0700 Subject: [PATCH 080/219] Revert "Migration to PlatformDispatcher and multi-window #20496" (#21792) * Revert "Fix documentation build for window changes. (#21780)" This reverts commit 931a04683d6eb49fc92059b2384ac5b1618d5422. * Revert "Migration to PlatformDispatcher and multi-window (#20496)" This reverts commit 85b0031f73544e448354047dc6a236c0b0808252. --- ci/licenses_golden/licenses_flutter | 3 - lib/ui/compositing.dart | 22 +- lib/ui/compositing/scene.cc | 3 +- lib/ui/dart_ui.gni | 1 - lib/ui/hooks.dart | 225 ++- lib/ui/natives.dart | 2 +- lib/ui/painting/canvas.cc | 2 +- lib/ui/platform_dispatcher.dart | 1554 ----------------- lib/ui/semantics.dart | 17 +- lib/ui/text.dart | 2 +- lib/ui/ui.dart | 1 - lib/ui/window.dart | 1453 +++++++++------ lib/ui/window/platform_configuration.cc | 6 +- lib/ui/window/platform_configuration.h | 11 +- .../platform_configuration_unittests.cc | 27 +- lib/ui/window/window.cc | 4 +- lib/ui/window/window.h | 5 +- lib/web_ui/lib/src/engine.dart | 9 +- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 8 +- lib/web_ui/lib/src/engine/canvas_pool.dart | 12 +- .../src/engine/canvaskit/embedded_views.dart | 2 +- .../engine/canvaskit/skia_object_cache.dart | 2 +- lib/web_ui/lib/src/engine/dom_renderer.dart | 8 +- .../lib/src/engine/html/render_vertices.dart | 4 +- .../lib/src/engine/html/surface_stats.dart | 8 +- lib/web_ui/lib/src/engine/keyboard.dart | 6 +- .../lib/src/engine/navigation/history.dart | 12 +- .../lib/src/engine/platform_dispatcher.dart | 923 ---------- .../lib/src/engine/pointer_binding.dart | 4 +- lib/web_ui/lib/src/engine/profiler.dart | 4 +- .../src/engine/semantics/incrementable.dart | 4 +- .../lib/src/engine/semantics/scrollable.dart | 8 +- .../lib/src/engine/semantics/semantics.dart | 4 +- .../lib/src/engine/semantics/tappable.dart | 2 +- .../lib/src/engine/semantics/text_field.dart | 4 +- .../src/engine/text_editing/text_editing.dart | 18 +- lib/web_ui/lib/src/engine/util.dart | 4 +- lib/web_ui/lib/src/engine/window.dart | 853 +++++++-- .../lib/src/ui/platform_dispatcher.dart | 420 ----- lib/web_ui/lib/src/ui/text.dart | 1 + lib/web_ui/lib/src/ui/window.dart | 465 ++--- lib/web_ui/lib/ui.dart | 1 - .../canvaskit/skia_objects_cache_test.dart | 2 +- lib/web_ui/test/engine/history_test.dart | 12 +- .../engine/surface/platform_view_test.dart | 2 +- lib/web_ui/test/engine/window_test.dart | 28 +- .../engine/canvas_golden_test.dart | 4 +- .../engine/compositing_golden_test.dart | 4 +- lib/web_ui/test/window_test.dart | 2 +- runtime/runtime_controller.cc | 4 +- runtime/runtime_controller.h | 2 +- shell/common/engine.h | 19 +- shell/common/fixtures/shell_test.dart | 40 +- shell/common/shell_test.cc | 2 +- shell/common/shell_unittests.cc | 2 +- .../embedding/android/FlutterView.java | 1 + .../android/platform_view_android_jni_impl.cc | 18 +- .../framework/Source/FlutterViewController.mm | 20 +- .../macos/framework/Source/FlutterEngine.mm | 11 +- shell/platform/embedder/embedder.cc | 7 +- shell/platform/embedder/embedder.h | 4 - shell/platform/embedder/fixtures/main.dart | 180 +- .../dart/window_hooks_integration_test.dart | 146 +- .../lib/src/animated_color_square.dart | 8 +- .../scenario_app/lib/src/channel_util.dart | 4 +- .../lib/src/initial_route_reply.dart | 8 +- .../lib/src/locale_initialization.dart | 9 +- .../scenario_app/lib/src/platform_view.dart | 160 +- .../scenario_app/lib/src/poppable_screen.dart | 10 +- testing/scenario_app/lib/src/scenario.dart | 20 +- testing/scenario_app/lib/src/scenarios.dart | 48 +- .../lib/src/send_text_focus_semantics.dart | 8 +- .../lib/src/touches_scenario.dart | 2 +- 73 files changed, 2528 insertions(+), 4383 deletions(-) delete mode 100644 lib/ui/platform_dispatcher.dart delete mode 100644 lib/web_ui/lib/src/engine/platform_dispatcher.dart delete mode 100644 lib/web_ui/lib/src/ui/platform_dispatcher.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d987a83d8abf8..2553764df42c2 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -371,7 +371,6 @@ FILE: ../../../flutter/lib/ui/painting/single_frame_codec.h FILE: ../../../flutter/lib/ui/painting/vertices.cc FILE: ../../../flutter/lib/ui/painting/vertices.h FILE: ../../../flutter/lib/ui/painting/vertices_unittests.cc -FILE: ../../../flutter/lib/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/ui/plugins.dart FILE: ../../../flutter/lib/ui/plugins/callback_cache.cc FILE: ../../../flutter/lib/ui/plugins/callback_cache.h @@ -500,7 +499,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -555,7 +553,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/path_metrics.dart -FILE: ../../../flutter/lib/web_ui/lib/src/ui/platform_dispatcher.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/pointer.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/test_embedding.dart diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 266c1b2ba7300..f484f0e53c521 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -10,8 +10,8 @@ part of dart.ui; /// /// To create a Scene object, use a [SceneBuilder]. /// -/// Scene objects can be displayed on the screen using the [FlutterView.render] -/// method. +/// Scene objects can be displayed on the screen using the +/// [Window.render] method. @pragma('vm:entry-point') class Scene extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated @@ -186,7 +186,7 @@ class PhysicalShapeEngineLayer extends _EngineLayerWrapper { /// Builds a [Scene] containing the given visuals. /// -/// A [Scene] can then be rendered using [FlutterView.render]. +/// A [Scene] can then be rendered using [Window.render]. /// /// To draw graphical operations onto a [Scene], first create a /// [Picture] using a [PictureRecorder] and a [Canvas], and then add @@ -655,13 +655,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// - 0x08: visualizeEngineStatistics - graph UI thread frame times /// Set enabledOptions to 0x0F to enable all the currently defined features. /// - /// The "UI thread" is the thread that includes all the execution of the main - /// Dart isolate (the isolate that can call [FlutterView.render]). The UI - /// thread frame time is the total time spent executing the - /// [PlatformDispatcher.onBeginFrame] callback. The "raster thread" is the - /// thread (running on the CPU) that subsequently processes the [Scene] - /// provided by the Dart code to turn it into GPU commands and send it to the - /// GPU. + /// The "UI thread" is the thread that includes all the execution of + /// the main Dart isolate (the isolate that can call + /// [Window.render]). The UI thread frame time is the total time + /// spent executing the [Window.onBeginFrame] callback. The "raster + /// thread" is the thread (running on the CPU) that subsequently + /// processes the [Scene] provided by the Dart code to turn it into + /// GPU commands and send it to the GPU. /// /// See also the [PerformanceOverlayOption] enum in the rendering library. /// for more details. @@ -802,7 +802,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// Returns a [Scene] containing the objects that have been added to /// this scene builder. The [Scene] can then be displayed on the - /// screen with [FlutterView.render]. + /// screen with [Window.render]. /// /// After calling this function, the scene builder object is invalid and /// cannot be used further. diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 46ac6efa534ca..96eac5885d083 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -42,10 +42,9 @@ Scene::Scene(std::shared_ptr rootLayer, uint32_t rasterizerTracingThreshold, bool checkerboardRasterCacheImages, bool checkerboardOffscreenLayers) { - // Currently only supports a single window. auto viewport_metrics = UIDartState::Current() ->platform_configuration() - ->get_window(0) + ->window() ->viewport_metrics(); layer_tree_ = std::make_unique( diff --git a/lib/ui/dart_ui.gni b/lib/ui/dart_ui.gni index 11fa74da53f84..28cb82e9cd125 100644 --- a/lib/ui/dart_ui.gni +++ b/lib/ui/dart_ui.gni @@ -13,7 +13,6 @@ dart_ui_files = [ "//flutter/lib/ui/lerp.dart", "//flutter/lib/ui/natives.dart", "//flutter/lib/ui/painting.dart", - "//flutter/lib/ui/platform_dispatcher.dart", "//flutter/lib/ui/plugins.dart", "//flutter/lib/ui/pointer.dart", "//flutter/lib/ui/semantics.dart", diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 8802af5f27c18..a911d79f63a8d 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -11,7 +11,6 @@ part of dart.ui; @pragma('vm:entry-point') // ignore: unused_element void _updateWindowMetrics( - Object id, double devicePixelRatio, double width, double height, @@ -28,96 +27,186 @@ void _updateWindowMetrics( double systemGestureInsetBottom, double systemGestureInsetLeft, ) { - PlatformDispatcher.instance._updateWindowMetrics( - id, - devicePixelRatio, - width, - height, - viewPaddingTop, - viewPaddingRight, - viewPaddingBottom, - viewPaddingLeft, - viewInsetTop, - viewInsetRight, - viewInsetBottom, - viewInsetLeft, - systemGestureInsetTop, - systemGestureInsetRight, - systemGestureInsetBottom, - systemGestureInsetLeft, - ); + window + .._devicePixelRatio = devicePixelRatio + .._physicalSize = Size(width, height) + .._viewPadding = WindowPadding._( + top: viewPaddingTop, + right: viewPaddingRight, + bottom: viewPaddingBottom, + left: viewPaddingLeft) + .._viewInsets = WindowPadding._( + top: viewInsetTop, + right: viewInsetRight, + bottom: viewInsetBottom, + left: viewInsetLeft) + .._padding = WindowPadding._( + top: math.max(0.0, viewPaddingTop - viewInsetTop), + right: math.max(0.0, viewPaddingRight - viewInsetRight), + bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), + left: math.max(0.0, viewPaddingLeft - viewInsetLeft)) + .._systemGestureInsets = WindowPadding._( + top: math.max(0.0, systemGestureInsetTop), + right: math.max(0.0, systemGestureInsetRight), + bottom: math.max(0.0, systemGestureInsetBottom), + left: math.max(0.0, systemGestureInsetLeft)); + _invoke(window.onMetricsChanged, window._onMetricsChangedZone); } typedef _LocaleClosure = String? Function(); +String? _localeClosure() { + if (window.locale == null) { + return null; + } + return window.locale.toString(); +} + @pragma('vm:entry-point') // ignore: unused_element -_LocaleClosure? _getLocaleClosure() => PlatformDispatcher.instance._localeClosure; +_LocaleClosure? _getLocaleClosure() => _localeClosure; @pragma('vm:entry-point') // ignore: unused_element void _updateLocales(List locales) { - PlatformDispatcher.instance._updateLocales(locales); + const int stringsPerLocale = 4; + final int numLocales = locales.length ~/ stringsPerLocale; + final List newLocales = []; + for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { + final String countryCode = locales[localeIndex * stringsPerLocale + 1]; + final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; + + newLocales.add(Locale.fromSubtags( + languageCode: locales[localeIndex * stringsPerLocale], + countryCode: countryCode.isEmpty ? null : countryCode, + scriptCode: scriptCode.isEmpty ? null : scriptCode, + )); + } + window._locales = newLocales; + _invoke(window.onLocaleChanged, window._onLocaleChangedZone); } @pragma('vm:entry-point') // ignore: unused_element void _updateUserSettingsData(String jsonData) { - PlatformDispatcher.instance._updateUserSettingsData(jsonData); + final Map data = json.decode(jsonData) as Map; + if (data.isEmpty) { + return; + } + _updateTextScaleFactor((data['textScaleFactor'] as num).toDouble()); + _updateAlwaysUse24HourFormat(data['alwaysUse24HourFormat'] as bool); + _updatePlatformBrightness(data['platformBrightness'] as String); } @pragma('vm:entry-point') // ignore: unused_element void _updateLifecycleState(String state) { - PlatformDispatcher.instance._updateLifecycleState(state); + // We do not update the state if the state has already been used to initialize + // the lifecycleState. + if (!window._initialLifecycleStateAccessed) + window._initialLifecycleState = state; +} + + +void _updateTextScaleFactor(double textScaleFactor) { + window._textScaleFactor = textScaleFactor; + _invoke(window.onTextScaleFactorChanged, window._onTextScaleFactorChangedZone); +} + +void _updateAlwaysUse24HourFormat(bool alwaysUse24HourFormat) { + window._alwaysUse24HourFormat = alwaysUse24HourFormat; +} + +void _updatePlatformBrightness(String brightnessName) { + window._platformBrightness = brightnessName == 'dark' ? Brightness.dark : Brightness.light; + _invoke(window.onPlatformBrightnessChanged, window._onPlatformBrightnessChangedZone); } @pragma('vm:entry-point') // ignore: unused_element void _updateSemanticsEnabled(bool enabled) { - PlatformDispatcher.instance._updateSemanticsEnabled(enabled); + window._semanticsEnabled = enabled; + _invoke(window.onSemanticsEnabledChanged, window._onSemanticsEnabledChangedZone); } @pragma('vm:entry-point') // ignore: unused_element void _updateAccessibilityFeatures(int values) { - PlatformDispatcher.instance._updateAccessibilityFeatures(values); + final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); + if (newFeatures == window._accessibilityFeatures) + return; + window._accessibilityFeatures = newFeatures; + _invoke(window.onAccessibilityFeaturesChanged, window._onAccessibilityFeaturesChangedZone); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { - PlatformDispatcher.instance._dispatchPlatformMessage(name, data, responseId); + if (name == ChannelBuffers.kControlChannelName) { + try { + channelBuffers.handleMessage(data!); + } catch (ex) { + _printDebug('Message to "$name" caused exception $ex'); + } finally { + window._respondToPlatformMessage(responseId, null); + } + } else if (window.onPlatformMessage != null) { + _invoke3( + window.onPlatformMessage, + window._onPlatformMessageZone, + name, + data, + (ByteData? responseData) { + window._respondToPlatformMessage(responseId, responseData); + }, + ); + } else { + channelBuffers.push(name, data, (ByteData? responseData) { + window._respondToPlatformMessage(responseId, responseData); + }); + } } @pragma('vm:entry-point') // ignore: unused_element void _dispatchPointerDataPacket(ByteData packet) { - PlatformDispatcher.instance._dispatchPointerDataPacket(packet); + if (window.onPointerDataPacket != null) + _invoke1(window.onPointerDataPacket, window._onPointerDataPacketZone, _unpackPointerDataPacket(packet)); } @pragma('vm:entry-point') // ignore: unused_element void _dispatchSemanticsAction(int id, int action, ByteData? args) { - PlatformDispatcher.instance._dispatchSemanticsAction(id, action, args); + _invoke3( + window.onSemanticsAction, + window._onSemanticsActionZone, + id, + SemanticsAction.values[action]!, + args, + ); } @pragma('vm:entry-point') // ignore: unused_element void _beginFrame(int microseconds) { - PlatformDispatcher.instance._beginFrame(microseconds); + _invoke1(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds)); } @pragma('vm:entry-point') // ignore: unused_element void _reportTimings(List timings) { - PlatformDispatcher.instance._reportTimings(timings); + assert(timings.length % FramePhase.values.length == 0); + final List frameTimings = []; + for (int i = 0; i < timings.length; i += FramePhase.values.length) { + frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); + } + _invoke1(window.onReportTimings, window._onReportTimingsZone, frameTimings); } @pragma('vm:entry-point') // ignore: unused_element void _drawFrame() { - PlatformDispatcher.instance._drawFrame(); + _invoke(window.onDrawFrame, window._onDrawFrameZone); } // ignore: always_declare_return_types, prefer_generic_function_type_aliases @@ -130,7 +219,7 @@ typedef _BinaryFunction(Null args, Null message); void _runMainZoned(Function startMainIsolateFunction, Function userMainFunction, List args) { - startMainIsolateFunction(() { + startMainIsolateFunction((){ runZonedGuarded(() { if (userMainFunction is _BinaryFunction) { // This seems to be undocumented but supported by the command line VM. @@ -150,10 +239,9 @@ void _runMainZoned(Function startMainIsolateFunction, void _reportUnhandledException(String error, String stackTrace) native 'PlatformConfiguration_reportUnhandledException'; /// Invokes [callback] inside the given [zone]. -void _invoke(void Function()? callback, Zone zone) { - if (callback == null) { +void _invoke(void callback()?, Zone zone) { + if (callback == null) return; - } assert(zone != null); // ignore: unnecessary_null_comparison @@ -165,10 +253,9 @@ void _invoke(void Function()? callback, Zone zone) { } /// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1
(void Function(A a)? callback, Zone zone, A arg) { - if (callback == null) { +void _invoke1(void callback(A a)?, Zone zone, A arg) { + if (callback == null) return; - } assert(zone != null); // ignore: unnecessary_null_comparison @@ -180,16 +267,9 @@ void _invoke1(void Function(A a)? callback, Zone zone, A arg) { } /// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3( - void Function(A1 a1, A2 a2, A3 a3)? callback, - Zone zone, - A1 arg1, - A2 arg2, - A3 arg3, -) { - if (callback == null) { +void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone zone, A1 arg1, A2 arg2, A3 arg3) { + if (callback == null) return; - } assert(zone != null); // ignore: unnecessary_null_comparison @@ -201,3 +281,54 @@ void _invoke3( }); } } + +// If this value changes, update the encoding code in the following files: +// +// * pointer_data.cc +// * pointer.dart +// * AndroidTouchProcessor.java +const int _kPointerDataFieldCount = 29; + +PointerDataPacket _unpackPointerDataPacket(ByteData packet) { + const int kStride = Int64List.bytesPerElement; + const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; + final int length = packet.lengthInBytes ~/ kBytesPerPointerData; + assert(length * kBytesPerPointerData == packet.lengthInBytes); + final List data = []; + for (int i = 0; i < length; ++i) { + int offset = i * _kPointerDataFieldCount; + data.add(PointerData( + embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), + timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), + change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], + device: packet.getInt64(kStride * offset++, _kFakeHostEndian), + pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), + physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), + obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), + scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian) + )); + assert(offset == (i + 1) * _kPointerDataFieldCount); + } + return PointerDataPacket(data: data); +} diff --git a/lib/ui/natives.dart b/lib/ui/natives.dart index 13838cb1a512b..0f2939592add1 100644 --- a/lib/ui/natives.dart +++ b/lib/ui/natives.dart @@ -34,7 +34,7 @@ Future _scheduleFrame( Map parameters ) async { // Schedule the frame. - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); // Always succeed. return developer.ServiceExtensionResponse.result(json.encode({ 'type': 'Success', diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 575e752eeee24..68ae4898c176a 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -468,7 +468,7 @@ void Canvas::drawShadow(const CanvasPath* path, } SkScalar dpr = UIDartState::Current() ->platform_configuration() - ->get_window(0) + ->window() ->viewport_metrics() .device_pixel_ratio; flutter::PhysicalShapeLayer::DrawShadow(canvas_, path->path(), color, diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart deleted file mode 100644 index 766ac5535081a..0000000000000 --- a/lib/ui/platform_dispatcher.dart +++ /dev/null @@ -1,1554 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of dart.ui; - -/// Signature of callbacks that have no arguments and return no data. -typedef VoidCallback = void Function(); - -/// Signature for [PlatformDispatcher.onBeginFrame]. -typedef FrameCallback = void Function(Duration duration); - -/// Signature for [PlatformDispatcher.onReportTimings]. -/// -/// {@template dart.ui.TimingsCallback.list} -/// The callback takes a list of [FrameTiming] because it may not be -/// immediately triggered after each frame. Instead, Flutter tries to batch -/// frames together and send all their timings at once to decrease the -/// overhead (as this is available in the release mode). The list is sorted in -/// ascending order of time (earliest frame first). The timing of any frame -/// will be sent within about 1 second (100ms if in the profile/debug mode) -/// even if there are no later frames to batch. The timing of the first frame -/// will be sent immediately without batching. -/// {@endtemplate} -typedef TimingsCallback = void Function(List timings); - -/// Signature for [PlatformDispatcher.onPointerDataPacket]. -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); - -/// Signature for [PlatformDispatcher.onSemanticsAction]. -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); - -/// Signature for responses to platform messages. -/// -/// Used as a parameter to [PlatformDispatcher.sendPlatformMessage] and -/// [PlatformDispatcher.onPlatformMessage]. -typedef PlatformMessageResponseCallback = void Function(ByteData? data); - -/// Signature for [PlatformDispatcher.onPlatformMessage]. -typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); - -// Signature for _setNeedsReportTimings. -typedef _SetNeedsReportTimingsFunc = void Function(bool value); - -/// Signature for [PlatformDispatcher.onConfigurationChanged]. -typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); - -/// Platform event dispatcher singleton. -/// -/// The most basic interface to the host operating system's interface. -/// -/// This is the central entry point for platform messages and configuration -/// events from the platform. -/// -/// It exposes the core scheduler API, the input event callback, the graphics -/// drawing API, and other such core services. -/// -/// It manages the list of the application's [views] and the [screens] attached -/// to the device, as well as the [configuration] of various platform -/// attributes. -/// -/// Consider avoiding static references to this singleton through -/// [PlatformDispatcher.instance] and instead prefer using a binding for -/// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. -/// See [PlatformDispatcher.instance] for more information about why this is -/// preferred. -class PlatformDispatcher { - /// Private constructor, since only dart:ui is supposed to create one of - /// these. Use [instance] to access the singleton. - PlatformDispatcher._() { - _setNeedsReportTimings = _nativeSetNeedsReportTimings; - } - - /// The [PlatformDispatcher] singleton. - /// - /// Consider avoiding static references to this singleton though - /// [PlatformDispatcher.instance] and instead prefer using a binding for - /// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`. - /// - /// Static access of this object means that Flutter has few, if any options to - /// fake or mock the given object in tests. Even in cases where Dart offers - /// special language constructs to forcefully shadow such properties, those - /// mechanisms would only be reasonable for tests and they would not be - /// reasonable for a future of Flutter where we legitimately want to select an - /// appropriate implementation at runtime. - /// - /// The only place that `WidgetsBinding.instance.platformDispatcher` is - /// inappropriate is if access to these APIs is required before the binding is - /// initialized by invoking `runApp()` or - /// `WidgetsFlutterBinding.instance.ensureInitialized()`. In that case, it is - /// necessary (though unfortunate) to use the [PlatformDispatcher.instance] - /// object statically. - static PlatformDispatcher get instance => _instance; - static final PlatformDispatcher _instance = PlatformDispatcher._(); - - /// The current platform configuration. - /// - /// If values in this configuration change, [onPlatformConfigurationChanged] - /// will be called. - PlatformConfiguration get configuration => _configuration; - PlatformConfiguration _configuration = const PlatformConfiguration(); - - /// Called when the platform configuration changes. - /// - /// The engine invokes this callback in the same zone in which the callback - /// was set. - VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; - VoidCallback? _onPlatformConfigurationChanged; - Zone _onPlatformConfigurationChangedZone = Zone.root; - set onPlatformConfigurationChanged(VoidCallback? callback) { - _onPlatformConfigurationChanged = callback; - _onPlatformConfigurationChangedZone = Zone.current; - } - - /// The current list of views, including top level platform windows used by - /// the application. - /// - /// If any of their configurations change, [onMetricsChanged] will be called. - Iterable get views => _views.values; - Map _views = {}; - - // A map of opaque platform view identifiers to view configurations. - Map _viewConfigurations = {}; - - /// A callback that is invoked whenever the [ViewConfiguration] of any of the - /// [views] changes. - /// - /// For example when the device is rotated or when the application is resized - /// (e.g. when showing applications side-by-side on Android), - /// `onMetricsChanged` is called. - /// - /// The engine invokes this callback in the same zone in which the callback - /// was set. - /// - /// The framework registers with this callback and updates the layout - /// appropriately. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// register for notifications when this is called. - /// * [MediaQuery.of], a simpler mechanism for the same. - VoidCallback? get onMetricsChanged => _onMetricsChanged; - VoidCallback? _onMetricsChanged; - Zone _onMetricsChangedZone = Zone.root; - set onMetricsChanged(VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; - } - - // Called from the engine, via hooks.dart - // - // Updates the metrics of the window with the given id. - void _updateWindowMetrics( - Object id, - double devicePixelRatio, - double width, - double height, - double viewPaddingTop, - double viewPaddingRight, - double viewPaddingBottom, - double viewPaddingLeft, - double viewInsetTop, - double viewInsetRight, - double viewInsetBottom, - double viewInsetLeft, - double systemGestureInsetTop, - double systemGestureInsetRight, - double systemGestureInsetBottom, - double systemGestureInsetLeft, - ) { - final ViewConfiguration previousConfiguration = - _viewConfigurations[id] ?? const ViewConfiguration(); - if (!_views.containsKey(id)) { - _views[id] = FlutterWindow._(id, this); - } - _viewConfigurations[id] = previousConfiguration.copyWith( - window: _views[id], - devicePixelRatio: devicePixelRatio, - geometry: Rect.fromLTWH(0.0, 0.0, width, height), - viewPadding: WindowPadding._( - top: viewPaddingTop, - right: viewPaddingRight, - bottom: viewPaddingBottom, - left: viewPaddingLeft, - ), - viewInsets: WindowPadding._( - top: viewInsetTop, - right: viewInsetRight, - bottom: viewInsetBottom, - left: viewInsetLeft, - ), - padding: WindowPadding._( - top: math.max(0.0, viewPaddingTop - viewInsetTop), - right: math.max(0.0, viewPaddingRight - viewInsetRight), - bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom), - left: math.max(0.0, viewPaddingLeft - viewInsetLeft), - ), - systemGestureInsets: WindowPadding._( - top: math.max(0.0, systemGestureInsetTop), - right: math.max(0.0, systemGestureInsetRight), - bottom: math.max(0.0, systemGestureInsetBottom), - left: math.max(0.0, systemGestureInsetLeft), - ), - ); - _invoke(onMetricsChanged, _onMetricsChangedZone); - } - - /// A callback invoked when any view begins a frame. - /// - /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} - /// A callback that is invoked to notify the application that it is an - /// appropriate time to provide a scene using the [SceneBuilder] API and the - /// [FlutterView.render] method. - /// - /// When possible, this is driven by the hardware VSync signal of the attached - /// screen with the highest VSync rate. This is only called if - /// [PlatformDispatcher.scheduleFrame] has been called since the last time - /// this callback was invoked. - /// {@endtemplate} - FrameCallback? get onBeginFrame => _onBeginFrame; - FrameCallback? _onBeginFrame; - Zone _onBeginFrameZone = Zone.root; - set onBeginFrame(FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _beginFrame(int microseconds) { - _invoke1( - onBeginFrame, - _onBeginFrameZone, - Duration(microseconds: microseconds), - ); - } - - /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} - /// A callback that is invoked for each frame after [onBeginFrame] has - /// completed and after the microtask queue has been drained. - /// - /// This can be used to implement a second phase of frame rendering that - /// happens after any deferred work queued by the [onBeginFrame] phase. - /// {@endtemplate} - VoidCallback? get onDrawFrame => _onDrawFrame; - VoidCallback? _onDrawFrame; - Zone _onDrawFrameZone = Zone.root; - set onDrawFrame(VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _drawFrame() { - _invoke(onDrawFrame, _onDrawFrameZone); - } - - /// A callback that is invoked when pointer data is available. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - /// - /// See also: - /// - /// * [GestureBinding], the Flutter framework class which manages pointer - /// events. - PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - PointerDataPacketCallback? _onPointerDataPacket; - Zone _onPointerDataPacketZone = Zone.root; - set onPointerDataPacket(PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _dispatchPointerDataPacket(ByteData packet) { - if (onPointerDataPacket != null) { - _invoke1( - onPointerDataPacket, - _onPointerDataPacketZone, - _unpackPointerDataPacket(packet), - ); - } - } - - // If this value changes, update the encoding code in the following files: - // - // * pointer_data.cc - // * pointer.dart - // * AndroidTouchProcessor.java - static const int _kPointerDataFieldCount = 29; - - static PointerDataPacket _unpackPointerDataPacket(ByteData packet) { - const int kStride = Int64List.bytesPerElement; - const int kBytesPerPointerData = _kPointerDataFieldCount * kStride; - final int length = packet.lengthInBytes ~/ kBytesPerPointerData; - assert(length * kBytesPerPointerData == packet.lengthInBytes); - final List data = []; - for (int i = 0; i < length; ++i) { - int offset = i * _kPointerDataFieldCount; - data.add(PointerData( - embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian), - timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)), - change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], - device: packet.getInt64(kStride * offset++, _kFakeHostEndian), - pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), - physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), - obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, - pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - size: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian), - scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), - )); - assert(offset == (i + 1) * _kPointerDataFieldCount); - } - return PointerDataPacket(data: data); - } - - /// A callback that is invoked to report the [FrameTiming] of recently - /// rasterized frames. - /// - /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use - /// [onReportTimings] directly because [SchedulerBinding.addTimingsCallback] - /// allows multiple callbacks. - /// - /// This can be used to see if the application has missed frames (through - /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high - /// latencies (through [FrameTiming.totalSpan]). - /// - /// Unlike [Timeline], the timing information here is available in the release - /// mode (additional to the profile and the debug mode). Hence this can be - /// used to monitor the application's performance in the wild. - /// - /// {@macro dart.ui.TimingsCallback.list} - /// - /// If this is null, no additional work will be done. If this is not null, - /// Flutter spends less than 0.1ms every 1 second to report the timings - /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for - /// 60fps), or 0.01% CPU usage per second. - TimingsCallback? get onReportTimings => _onReportTimings; - TimingsCallback? _onReportTimings; - Zone _onReportTimingsZone = Zone.root; - set onReportTimings(TimingsCallback? callback) { - if ((callback == null) != (_onReportTimings == null)) { - _setNeedsReportTimings(callback != null); - } - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; - } - - late _SetNeedsReportTimingsFunc _setNeedsReportTimings; - void _nativeSetNeedsReportTimings(bool value) - native 'PlatformConfiguration_setNeedsReportTimings'; - - // Called from the engine, via hooks.dart - void _reportTimings(List timings) { - assert(timings.length % FramePhase.values.length == 0); - final List frameTimings = []; - for (int i = 0; i < timings.length; i += FramePhase.values.length) { - frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length))); - } - _invoke1(onReportTimings, _onReportTimingsZone, frameTimings); - } - - /// Sends a message to a platform-specific plugin. - /// - /// The `name` parameter determines which plugin receives the message. The - /// `data` parameter contains the message payload and is typically UTF-8 - /// encoded JSON but can be arbitrary data. If the plugin replies to the - /// message, `callback` will be called with the response. - /// - /// The framework invokes [callback] in the same zone in which this method was - /// called. - void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { - final String? error = - _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); - if (error != null) - throw Exception(error); - } - - String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data) - native 'PlatformConfiguration_sendPlatformMessage'; - - /// Called whenever this platform dispatcher receives a message from a - /// platform-specific plugin. - /// - /// The `name` parameter determines which plugin sent the message. The `data` - /// parameter is the payload and is typically UTF-8 encoded JSON but can be - /// arbitrary data. - /// - /// Message handlers must call the function given in the `callback` parameter. - /// If the handler does not need to respond, the handler should pass null to - /// the callback. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - PlatformMessageCallback? _onPlatformMessage; - Zone _onPlatformMessageZone = Zone.root; - set onPlatformMessage(PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Called by [_dispatchPlatformMessage]. - void _respondToPlatformMessage(int responseId, ByteData? data) - native 'PlatformConfiguration_respondToPlatformMessage'; - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback( - PlatformMessageResponseCallback? callback, - ) { - if (callback == null) { - return null; - } - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; - } - - // Called from the engine, via hooks.dart - void _dispatchPlatformMessage(String name, ByteData? data, int responseId) { - if (name == ChannelBuffers.kControlChannelName) { - try { - channelBuffers.handleMessage(data!); - } catch (ex) { - _printDebug('Message to "$name" caused exception $ex'); - } finally { - _respondToPlatformMessage(responseId, null); - } - } else if (onPlatformMessage != null) { - _invoke3( - onPlatformMessage, - _onPlatformMessageZone, - name, - data, - (ByteData? responseData) { - _respondToPlatformMessage(responseId, responseData); - }, - ); - } else { - channelBuffers.push(name, data, (ByteData? responseData) { - _respondToPlatformMessage(responseId, responseData); - }); - } - } - - /// Set the debug name associated with this platform dispatcher's root - /// isolate. - /// - /// Normally debug names are automatically generated from the Dart port, entry - /// point, and source file. For example: `main.dart$main-1234`. - /// - /// This can be combined with flutter tools `--isolate-filter` flag to debug - /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. - /// Note that this does not rename any child isolates of the root. - void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; - - /// The embedder can specify data that the isolate can request synchronously - /// on launch. This accessor fetches that data. - /// - /// This data is persistent for the duration of the Flutter application and is - /// available even after isolate restarts. Because of this lifecycle, the size - /// of this data must be kept to a minimum. - /// - /// For asynchronous communication between the embedder and isolate, a - /// platform channel may be used. - ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; - - /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and - /// [onDrawFrame] callbacks be invoked. - /// - /// See also: - /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; - - /// Additional accessibility features that may be enabled by the platform. - AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; - - /// A callback that is invoked when the value of [accessibilityFeatures] - /// changes. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; - VoidCallback? _onAccessibilityFeaturesChanged; - Zone _onAccessibilityFeaturesChangedZone = Zone.root; - set onAccessibilityFeaturesChanged(VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _updateAccessibilityFeatures(int values) { - final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values); - final PlatformConfiguration previousConfiguration = configuration; - if (newFeatures == previousConfiguration.accessibilityFeatures) { - return; - } - _configuration = previousConfiguration.copyWith( - accessibilityFeatures: newFeatures, - ); - _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone,); - _invoke(onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone,); - } - - /// Change the retained semantics data about this platform dispatcher. - /// - /// If [semanticsEnabled] is true, the user has requested that this function - /// be called whenever the semantic content of this platform dispatcher - /// changes. - /// - /// In either case, this function disposes the given update, which means the - /// semantics update cannot be used further. - void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; - - /// The system-reported default locale of the device. - /// - /// This establishes the language and formatting conventions that application - /// should, if possible, use to render their user interface. - /// - /// This is the first locale selected by the user and is the user's primary - /// locale (the locale the device UI is displayed in) - /// - /// This is equivalent to `locales.first` and will provide an empty non-null - /// locale if the [locales] list has not been set or is empty. - Locale? get locale { - if (locales != null && locales!.isNotEmpty) { - return locales!.first; - } - return null; - } - - /// The full system-reported supported locales of the device. - /// - /// This establishes the language and formatting conventions that application - /// should, if possible, use to render their user interface. - /// - /// The list is ordered in order of priority, with lower-indexed locales being - /// preferred over higher-indexed ones. The first element is the primary - /// [locale]. - /// - /// The [onLocaleChanged] callback is called whenever this value changes. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - List? get locales => configuration.locales; - - /// Performs the platform-native locale resolution. - /// - /// Each platform may return different results. - /// - /// If the platform fails to resolve a locale, then this will return null. - /// - /// This method returns synchronously and is a direct call to - /// platform specific APIs without invoking method channels. - Locale? computePlatformResolvedLocale(List supportedLocales) { - final List supportedLocalesData = []; - for (Locale locale in supportedLocales) { - supportedLocalesData.add(locale.languageCode); - supportedLocalesData.add(locale.countryCode); - supportedLocalesData.add(locale.scriptCode); - } - - final List result = _computePlatformResolvedLocale(supportedLocalesData); - - if (result.isNotEmpty) { - return Locale.fromSubtags( - languageCode: result[0], - countryCode: result[1] == '' ? null : result[1], - scriptCode: result[2] == '' ? null : result[2]); - } - return null; - } - List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; - - /// A callback that is invoked whenever [locale] changes value. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - VoidCallback? get onLocaleChanged => _onLocaleChanged; - VoidCallback? _onLocaleChanged; - Zone _onLocaleChangedZone = Zone.root; // ignore: unused_field - set onLocaleChanged(VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _updateLocales(List locales) { - const int stringsPerLocale = 4; - final int numLocales = locales.length ~/ stringsPerLocale; - final PlatformConfiguration previousConfiguration = configuration; - final List newLocales = []; - bool localesDiffer = numLocales != previousConfiguration.locales.length; - for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) { - final String countryCode = locales[localeIndex * stringsPerLocale + 1]; - final String scriptCode = locales[localeIndex * stringsPerLocale + 2]; - - newLocales.add(Locale.fromSubtags( - languageCode: locales[localeIndex * stringsPerLocale], - countryCode: countryCode.isEmpty ? null : countryCode, - scriptCode: scriptCode.isEmpty ? null : scriptCode, - )); - if (!localesDiffer && newLocales[localeIndex] != previousConfiguration.locales[localeIndex]) { - localesDiffer = true; - } - } - if (!localesDiffer) { - return; - } - _configuration = previousConfiguration.copyWith(locales: newLocales); - _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); - _invoke(onLocaleChanged, _onLocaleChangedZone); - } - - // Called from the engine, via hooks.dart - String? _localeClosure() { - if (locale == null) { - return null; - } - return locale.toString(); - } - - /// The lifecycle state immediately after dart isolate initialization. - /// - /// This property will not be updated as the lifecycle changes. - /// - /// It is used to initialize [SchedulerBinding.lifecycleState] at startup with - /// any buffered lifecycle state events. - String get initialLifecycleState { - _initialLifecycleStateAccessed = true; - return _initialLifecycleState; - } - - late String _initialLifecycleState; - - /// Tracks if the initial state has been accessed. Once accessed, we will stop - /// updating the [initialLifecycleState], as it is not the preferred way to - /// access the state. - bool _initialLifecycleStateAccessed = false; - - // Called from the engine, via hooks.dart - void _updateLifecycleState(String state) { - // We do not update the state if the state has already been used to initialize - // the lifecycleState. - if (!_initialLifecycleStateAccessed) - _initialLifecycleState = state; - } - - /// The setting indicating whether time should always be shown in the 24-hour - /// format. - /// - /// This option is used by [showTimePicker]. - bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; - - /// The system-reported text scale. - /// - /// This establishes the text scaling factor to use when rendering text, - /// according to the user's platform preferences. - /// - /// The [onTextScaleFactorChanged] callback is called whenever this value - /// changes. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - double get textScaleFactor => configuration.textScaleFactor; - - /// A callback that is invoked whenever [textScaleFactor] changes value. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - VoidCallback? _onTextScaleFactorChanged; - Zone _onTextScaleFactorChangedZone = Zone.root; - set onTextScaleFactorChanged(VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; - } - - /// The setting indicating the current brightness mode of the host platform. - /// If the platform has no preference, [platformBrightness] defaults to - /// [Brightness.light]. - Brightness get platformBrightness => configuration.platformBrightness; - - /// A callback that is invoked whenever [platformBrightness] changes value. - /// - /// The framework invokes this callback in the same zone in which the callback - /// was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; - VoidCallback? _onPlatformBrightnessChanged; - Zone _onPlatformBrightnessChangedZone = Zone.root; - set onPlatformBrightnessChanged(VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _updateUserSettingsData(String jsonData) { - final Map data = json.decode(jsonData) as Map; - if (data.isEmpty) { - return; - } - - final double textScaleFactor = (data['textScaleFactor'] as num).toDouble(); - final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat'] as bool; - final Brightness platformBrightness = - data['platformBrightness'] as String == 'dark' ? Brightness.dark : Brightness.light; - final PlatformConfiguration previousConfiguration = configuration; - final bool platformBrightnessChanged = - previousConfiguration.platformBrightness != platformBrightness; - final bool textScaleFactorChanged = previousConfiguration.textScaleFactor != textScaleFactor; - final bool alwaysUse24HourFormatChanged = - previousConfiguration.alwaysUse24HourFormat != alwaysUse24HourFormat; - if (!platformBrightnessChanged && !textScaleFactorChanged && !alwaysUse24HourFormatChanged) { - return; - } - _configuration = previousConfiguration.copyWith( - textScaleFactor: textScaleFactor, - alwaysUse24HourFormat: alwaysUse24HourFormat, - platformBrightness: platformBrightness, - ); - _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); - if (textScaleFactorChanged) { - _invoke(onTextScaleFactorChanged, _onTextScaleFactorChangedZone); - } - if (platformBrightnessChanged) { - _invoke(onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); - } - } - - /// Whether the user has requested that [updateSemantics] be called when the - /// semantic contents of a view changes. - /// - /// The [onSemanticsEnabledChanged] callback is called whenever this value - /// changes. - bool get semanticsEnabled => configuration.semanticsEnabled; - - /// A callback that is invoked when the value of [semanticsEnabled] changes. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - VoidCallback? _onSemanticsEnabledChanged; - Zone _onSemanticsEnabledChangedZone = Zone.root; - set onSemanticsEnabledChanged(VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _updateSemanticsEnabled(bool enabled) { - final PlatformConfiguration previousConfiguration = configuration; - if (previousConfiguration.semanticsEnabled == enabled) { - return; - } - _configuration = previousConfiguration.copyWith( - semanticsEnabled: enabled, - ); - _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); - _invoke(onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); - } - - /// A callback that is invoked whenever the user requests an action to be - /// performed. - /// - /// This callback is used when the user expresses the action they wish to - /// perform based on the semantics supplied by [updateSemantics]. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - SemanticsActionCallback? _onSemanticsAction; - Zone _onSemanticsActionZone = Zone.root; - set onSemanticsAction(SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; - } - - // Called from the engine, via hooks.dart - void _dispatchSemanticsAction(int id, int action, ByteData? args) { - _invoke3( - onSemanticsAction, - _onSemanticsActionZone, - id, - SemanticsAction.values[action]!, - args, - ); - } - - /// The route or path that the embedder requested when the application was - /// launched. - /// - /// This will be the string "`/`" if no particular route was requested. - /// - /// ## Android - /// - /// On Android, calling - /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) - /// will set this value. The value must be set sufficiently early, i.e. before - /// the [runApp] call is executed in Dart, for this to have any effect on the - /// framework. The `createFlutterView` method in your `FlutterActivity` - /// subclass is a suitable time to set the value. The application's - /// `AndroidManifest.xml` file must also be updated to have a suitable - /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). - /// - /// ## iOS - /// - /// On iOS, calling - /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) - /// will set this value. The value must be set sufficiently early, i.e. before - /// the [runApp] call is executed in Dart, for this to have any effect on the - /// framework. The `application:didFinishLaunchingWithOptions:` method is a - /// suitable time to set this value. - /// - /// See also: - /// - /// * [Navigator], a widget that handles routing. - /// * [SystemChannels.navigation], which handles subsequent navigation - /// requests from the embedder. - String get defaultRouteName => _defaultRouteName(); - String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; -} - -/// Configuration of the platform. -/// -/// Immutable class (but can't use @immutable in dart:ui) -class PlatformConfiguration { - /// Const constructor for [PlatformConfiguration]. - const PlatformConfiguration({ - this.accessibilityFeatures = const AccessibilityFeatures._(0), - this.alwaysUse24HourFormat = false, - this.semanticsEnabled = false, - this.platformBrightness = Brightness.light, - this.textScaleFactor = 1.0, - this.locales = const [], - this.defaultRouteName, - }); - - /// Copy a [PlatformConfiguration] with some fields replaced. - PlatformConfiguration copyWith({ - AccessibilityFeatures? accessibilityFeatures, - bool? alwaysUse24HourFormat, - bool? semanticsEnabled, - Brightness? platformBrightness, - double? textScaleFactor, - List? locales, - String? defaultRouteName, - }) { - return PlatformConfiguration( - accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, - alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, - semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, - platformBrightness: platformBrightness ?? this.platformBrightness, - textScaleFactor: textScaleFactor ?? this.textScaleFactor, - locales: locales ?? this.locales, - defaultRouteName: defaultRouteName ?? this.defaultRouteName, - ); - } - - /// Additional accessibility features that may be enabled by the platform. - final AccessibilityFeatures accessibilityFeatures; - - /// The setting indicating whether time should always be shown in the 24-hour - /// format. - final bool alwaysUse24HourFormat; - - /// Whether the user has requested that [updateSemantics] be called when the - /// semantic contents of a view changes. - final bool semanticsEnabled; - - /// The setting indicating the current brightness mode of the host platform. - /// If the platform has no preference, [platformBrightness] defaults to - /// [Brightness.light]. - final Brightness platformBrightness; - - /// The system-reported text scale. - final double textScaleFactor; - - /// The full system-reported supported locales of the device. - final List locales; - - /// The route or path that the embedder requested when the application was - /// launched. - final String? defaultRouteName; -} - -/// An immutable view configuration. -class ViewConfiguration { - /// A const constructor for an immutable [ViewConfiguration]. - const ViewConfiguration({ - this.window, - this.devicePixelRatio = 1.0, - this.geometry = Rect.zero, - this.visible = false, - this.viewInsets = WindowPadding.zero, - this.viewPadding = WindowPadding.zero, - this.systemGestureInsets = WindowPadding.zero, - this.padding = WindowPadding.zero, - }); - - /// Copy this configuration with some fields replaced. - ViewConfiguration copyWith({ - FlutterView? window, - double? devicePixelRatio, - Rect? geometry, - bool? visible, - WindowPadding? viewInsets, - WindowPadding? viewPadding, - WindowPadding? systemGestureInsets, - WindowPadding? padding, - }) { - return ViewConfiguration( - window: window ?? this.window, - devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, - geometry: geometry ?? this.geometry, - visible: visible ?? this.visible, - viewInsets: viewInsets ?? this.viewInsets, - viewPadding: viewPadding ?? this.viewPadding, - systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, - padding: padding ?? this.padding, - ); - } - - /// The top level view into which the view is placed and its geometry is - /// relative to. - /// - /// If null, then this configuration represents a top level view itself. - final FlutterView? window; - - /// The pixel density of the output surface. - final double devicePixelRatio; - - /// The geometry requested for the view on the screen or within its parent - /// window, in logical pixels. - final Rect geometry; - - /// Whether or not the view is currently visible on the screen. - final bool visible; - - /// The view insets, as it intersects with [Screen.viewInsets] for the screen - /// it is on. - /// - /// For instance, if the view doesn't overlap the - /// [ScreenConfiguration.viewInsets] area, [viewInsets] will be - /// [WindowPadding.zero]. - /// - /// The number of physical pixels on each side of this view rectangle into - /// which the application can draw, but over which the operating system will - /// likely place system UI, such as the keyboard or system menus, that fully - /// obscures any content. - final WindowPadding viewInsets; - - /// The view insets, as it intersects with [ScreenConfiguration.viewPadding] - /// for the screen it is on. - /// - /// For instance, if the view doesn't overlap the - /// [ScreenConfiguration.viewPadding] area, [viewPadding] will be - /// [WindowPadding.zero]. - /// - /// The number of physical pixels on each side of this screen rectangle into - /// which the application can place a view, but which may be partially - /// obscured by system UI (such as the system notification area), or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). - final WindowPadding viewPadding; - - /// The view insets, as it intersects with - /// [ScreenConfiguration.systemGestureInsets] for the screen it is on. - /// - /// For instance, if the view doesn't overlap the - /// [ScreenConfiguration.systemGestureInsets] area, [systemGestureInsets] will - /// be [WindowPadding.zero]. - /// - /// The number of physical pixels on each side of this screen rectangle into - /// which the application can place a view, but where the operating system - /// will consume input gestures for the sake of system navigation. - final WindowPadding systemGestureInsets; - - /// The view insets, as it intersects with [ScreenConfiguration.padding] for - /// the screen it is on. - /// - /// For instance, if the view doesn't overlap the - /// [ScreenConfiguration.padding] area, [padding] will be - /// [WindowPadding.zero]. - /// - /// The number of physical pixels on each side of this screen rectangle into - /// which the application can place a view, but which may be partially - /// obscured by system UI (such as the system notification area), or physical - /// intrusions in the display (e.g. overscan regions on television screens or - /// phone sensor housings). - final WindowPadding padding; - - @override - String toString() { - return '$runtimeType[window: $window, geometry: $geometry]'; - } -} - -/// Various important time points in the lifetime of a frame. -/// -/// [FrameTiming] records a timestamp of each phase for performance analysis. -enum FramePhase { - /// The timestamp of the vsync signal given by the operating system. - /// - /// See also [FrameTiming.vsyncOverhead]. - vsyncStart, - - /// When the UI thread starts building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildStart, - - /// When the UI thread finishes building a frame. - /// - /// See also [FrameTiming.buildDuration]. - buildFinish, - - /// When the raster thread starts rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterStart, - - /// When the raster thread finishes rasterizing a frame. - /// - /// See also [FrameTiming.rasterDuration]. - rasterFinish, -} - -/// Time-related performance metrics of a frame. -/// -/// If you're using the whole Flutter framework, please use -/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using -/// [PlatformDispatcher.onReportTimings] directly because -/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If -/// [SchedulerBinding] is unavailable, then see [PlatformDispatcher.onReportTimings] -/// for how to get this. -/// -/// The metrics in debug mode (`flutter run` without any flags) may be very -/// different from those in profile and release modes due to the debug overhead. -/// Therefore it's recommended to only monitor and analyze performance metrics -/// in profile and release modes. -class FrameTiming { - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// This constructor is used for unit test only. Real [FrameTiming]s should - /// be retrieved from [PlatformDispatcher.onReportTimings]. - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - - /// Construct [FrameTiming] with raw timestamps in microseconds. - /// - /// List [timestamps] must have the same number of elements as - /// [FramePhase.values]. - /// - /// This constructor is usually only called by the Flutter engine, or a test. - /// To get the [FrameTiming] of your app, see [PlatformDispatcher.onReportTimings]. - FrameTiming._(this._timestamps) - : assert(_timestamps.length == FramePhase.values.length); - - /// This is a raw timestamp in microseconds from some epoch. The epoch in all - /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - - /// The duration to build the frame on the UI thread. - /// - /// The build starts approximately when [PlatformDispatcher.onBeginFrame] is - /// called. The [Duration] in the [PlatformDispatcher.onBeginFrame] callback - /// is exactly the `Duration(microseconds: - /// timestampInMicroseconds(FramePhase.buildStart))`. - /// - /// The build finishes when [FlutterView.render] is called. - /// - /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// To ensure smooth animations of X fps, this should not exceed 1000/X - /// milliseconds. - /// {@endtemplate} - /// {@template dart.ui.FrameTiming.fps_milliseconds} - /// That's about 16ms for 60fps, and 8ms for 120fps. - /// {@endtemplate} - Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - - /// The duration to rasterize the frame on the raster thread. - /// - /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - - /// The duration between receiving the vsync signal and starting building the - /// frame. - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - - /// The timespan between vsync start and raster finish. - /// - /// To achieve the lowest latency on an X fps display, this should not exceed - /// 1000/X milliseconds. - /// {@macro dart.ui.FrameTiming.fps_milliseconds} - /// - /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. - Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - -/// States that an application can be in. -/// -/// The values below describe notifications from the operating system. -/// Applications should not expect to always receive all possible -/// notifications. For example, if the users pulls out the battery from the -/// device, no notification will be sent before the application is suddenly -/// terminated, along with the rest of the operating system. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state -/// from the widgets layer. -enum AppLifecycleState { - /// The application is visible and responding to user input. - resumed, - - /// The application is in an inactive state and is not receiving user input. - /// - /// On iOS, this state corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when in - /// a phone call, responding to a TouchID request, when entering the app - /// switcher or the control center, or when the UIViewController hosting the - /// Flutter app is transitioning. - /// - /// On Android, this corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when - /// another activity is focused, such as a split-screen app, a phone call, - /// a picture-in-picture app, a system dialog, or another window. - /// - /// Apps in this state should assume that they may be [paused] at any time. - inactive, - - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. - /// - /// When the application is in this state, the engine will not call the - /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame] - /// callbacks. - paused, - - /// The application is still hosted on a flutter engine but is detached from - /// any host views. - /// - /// When the application is in this state, the engine is running without - /// a view. It can either be in the progress of attaching a view when engine - /// was first initializes, or after the view being destroyed due to a Navigator - /// pop. - detached, -} - -/// A representation of distances for each of the four edges of a rectangle, -/// used to encode the view insets and padding that applications should place -/// around their user interface, as exposed by [FlutterView.viewInsets] and -/// [FlutterView.padding]. View insets and padding are preferably read via -/// [MediaQuery.of]. -/// -/// For a generic class that represents distances around a rectangle, see the -/// [EdgeInsets] class. -/// -/// See also: -/// -/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive -/// notifications when the padding changes. -/// * [MediaQuery.of], for the preferred mechanism for accessing these values. -/// * [Scaffold], which automatically applies the padding in material design -/// applications. -class WindowPadding { - const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); - - /// The distance from the left edge to the first unpadded pixel, in physical pixels. - final double left; - - /// The distance from the top edge to the first unpadded pixel, in physical pixels. - final double top; - - /// The distance from the right edge to the first unpadded pixel, in physical pixels. - final double right; - - /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. - final double bottom; - - /// A window padding that has zeros for each edge. - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); - - @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } -} - -/// An identifier used to select a user's language and formatting preferences. -/// -/// This represents a [Unicode Language -/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) -/// (i.e. without Locale extensions), except variants are not supported. -/// -/// Locales are canonicalized according to the "preferred value" entries in the -/// [IANA Language Subtag -/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). -/// For example, `const Locale('he')` and `const Locale('iw')` are equal and -/// both have the [languageCode] `he`, because `iw` is a deprecated language -/// subtag that was replaced by the subtag `he`. -/// -/// See also: -/// -/// * [PlatformDispatcher.locale], which specifies the system's currently selected -/// [Locale]. -class Locale { - /// Creates a new Locale object. The first argument is the - /// primary language subtag, the second is the region (also - /// referred to as 'country') subtag. - /// - /// For example: - /// - /// ```dart - /// const Locale swissFrench = Locale('fr', 'CH'); - /// const Locale canadianFrench = Locale('fr', 'CA'); - /// ``` - /// - /// The primary language subtag must not be null. The region subtag is - /// optional. When there is no region/country subtag, the parameter should - /// be omitted or passed `null` instead of an empty-string. - /// - /// The subtag values are _case sensitive_ and must be one of the valid - /// subtags according to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The - /// primary language subtag must be at least two and at most eight lowercase - /// letters, but not four letters. The region region subtag must be two - /// uppercase letters or three digits. See the [Unicode Language - /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) - /// specification. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which also allows a [scriptCode] to be - /// specified. - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - - /// Creates a new Locale object. - /// - /// The keyword arguments specify the subtags of the Locale. - /// - /// The subtag values are _case sensitive_ and must be valid subtags according - /// to CLDR supplemental data: - /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), - /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and - /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for - /// each of languageCode, scriptCode and countryCode respectively. - /// - /// The [countryCode] subtag is optional. When there is no country subtag, - /// the parameter should be omitted or passed `null` instead of an empty-string. - /// - /// Validity is not checked by default, but some methods may throw away - /// invalid data. - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - - /// The primary language subtag for the locale. - /// - /// This must not be null. It may be 'und', representing 'undefined'. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "language". The string specified must match the case of the - /// string in the registry. - /// - /// Language subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const - /// Locale('he')` and `const Locale('iw')` are equal, and both have the - /// [languageCode] `he`, because `iw` is a deprecated language subtag that was - /// replaced by the subtag `he`. - /// - /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR - /// supplemental - /// data](http://unicode.org/cldr/latest/common/validity/language.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - - /// The script subtag for the locale. - /// - /// This may be null, indicating that there is no specified script subtag. - /// - /// This must be a valid Unicode Language Identifier script subtag as listed - /// in [Unicode CLDR supplemental - /// data](http://unicode.org/cldr/latest/common/validity/script.xml). - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - final String? scriptCode; - - /// The region subtag for the locale. - /// - /// This may be null, indicating that there is no specified region subtag. - /// - /// This is expected to be string registered in the [IANA Language Subtag - /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) - /// with the type "region". The string specified must match the case of the - /// string in the registry. - /// - /// Region subtags that are deprecated in the registry and have a preferred - /// code are changed to their preferred code. For example, `const Locale('de', - /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the - /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was - /// replaced by the subtag `DE`. - /// - /// See also: - /// - /// * [Locale.fromSubtags], which describes the conventions for creating - /// [Locale] objects. - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) - return true; - if (other is! Locale) { - return false; - } - final String? countryCode = _countryCode; - final String? otherCountryCode = other.countryCode; - return other.languageCode == languageCode - && other.scriptCode == scriptCode // scriptCode cannot be '' - && (other.countryCode == countryCode // Treat '' as equal to null. - || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null - || countryCode != null && countryCode.isEmpty && other.countryCode == null); - } - - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); - - static Locale? _cachedLocale; - static String? _cachedLocaleString; - - /// Returns a string representing the locale. - /// - /// This identifier happens to be a valid Unicode Locale Identifier using - /// underscores as separator, however it is intended to be used for debugging - /// purposes only. For parsable results, use [toLanguageTag] instead. - @keepToString - @override - String toString() { - if (!identical(_cachedLocale, this)) { - _cachedLocale = this; - _cachedLocaleString = _rawToString('_'); - } - return _cachedLocaleString!; - } - - /// Returns a syntactically valid Unicode BCP47 Locale Identifier. - /// - /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and - /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical - /// details. - String toLanguageTag() => _rawToString('-'); - - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null && scriptCode!.isNotEmpty) - out.write('$separator$scriptCode'); - if (_countryCode != null && _countryCode!.isNotEmpty) - out.write('$separator$countryCode'); - return out.toString(); - } -} \ No newline at end of file diff --git a/lib/ui/semantics.dart b/lib/ui/semantics.dart index 5769905628fdf..30bce7cc91e47 100644 --- a/lib/ui/semantics.dart +++ b/lib/ui/semantics.dart @@ -625,8 +625,7 @@ class SemanticsFlag { /// An object that creates [SemanticsUpdate] objects. /// /// Once created, the [SemanticsUpdate] objects can be passed to -/// [PlatformDispatcher.updateSemantics] to update the semantics conveyed to the -/// user. +/// [Window.updateSemantics] to update the semantics conveyed to the user. @pragma('vm:entry-point') class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates an empty [SemanticsUpdateBuilder] object. @@ -654,10 +653,10 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// /// The `actions` are a bit field of [SemanticsAction]s that can be undertaken /// by this node. If the user wishes to undertake one of these actions on this - /// node, the [PlatformDispatcher.onSemanticsAction] will be called with `id` - /// and one of the possible [SemanticsAction]s. Because the semantics tree is - /// maintained asynchronously, the [PlatformDispatcher.onSemanticsAction] - /// callback might be called with an action that is no longer possible. + /// node, the [Window.onSemanticsAction] will be called with `id` and one of + /// the possible [SemanticsAction]s. Because the semantics tree is maintained + /// asynchronously, the [Window.onSemanticsAction] callback might be called + /// with an action that is no longer possible. /// /// The `label` is a string that describes this node. The `value` property /// describes the current value of the node as a string. The `increasedValue` @@ -833,8 +832,8 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// Creates a [SemanticsUpdate] object that encapsulates the updates recorded /// by this object. /// - /// The returned object can be passed to [PlatformDispatcher.updateSemantics] - /// to actually update the semantics retained by the system. + /// The returned object can be passed to [Window.updateSemantics] to actually + /// update the semantics retained by the system. SemanticsUpdate build() { final SemanticsUpdate semanticsUpdate = SemanticsUpdate._(); _build(semanticsUpdate); @@ -848,7 +847,7 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { /// To create a SemanticsUpdate object, use a [SemanticsUpdateBuilder]. /// /// Semantics updates can be applied to the system's retained semantics tree -/// using the [PlatformDispatcher.updateSemantics] method. +/// using the [Window.updateSemantics] method. @pragma('vm:entry-point') class SemanticsUpdate extends NativeFieldWrapperClass2 { /// This class is created by the engine, and should not be instantiated diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 96e78678f60a2..8a03b68c68fa1 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -2273,7 +2273,7 @@ final ByteData _fontChangeMessage = utf8.encoder.convert( ).buffer.asByteData(); FutureOr _sendFontChangeMessage() async { - PlatformDispatcher.instance.onPlatformMessage?.call( + window.onPlatformMessage?.call( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart index 4603fb3ce7774..fe0e4fa16bcd6 100644 --- a/lib/ui/ui.dart +++ b/lib/ui/ui.dart @@ -33,7 +33,6 @@ part 'isolate_name_server.dart'; part 'lerp.dart'; part 'natives.dart'; part 'painting.dart'; -part 'platform_dispatcher.dart'; part 'plugins.dart'; part 'pointer.dart'; part 'semantics.dart'; diff --git a/lib/ui/window.dart b/lib/ui/window.dart index a2372728346a7..a39c5407ad45e 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -5,72 +5,614 @@ // @dart = 2.10 part of dart.ui; -/// A view into which a Flutter [Scene] is drawn. +/// Signature of callbacks that have no arguments and return no data. +typedef VoidCallback = void Function(); + +/// Signature for [Window.onBeginFrame]. +typedef FrameCallback = void Function(Duration duration); + +/// Signature for [Window.onReportTimings]. +/// +/// {@template dart.ui.TimingsCallback.list} +/// The callback takes a list of [FrameTiming] because it may not be +/// immediately triggered after each frame. Instead, Flutter tries to batch +/// frames together and send all their timings at once to decrease the +/// overhead (as this is available in the release mode). The list is sorted in +/// ascending order of time (earliest frame first). The timing of any frame +/// will be sent within about 1 second (100ms if in the profile/debug mode) +/// even if there are no later frames to batch. The timing of the first frame +/// will be sent immediately without batching. +/// {@endtemplate} +typedef TimingsCallback = void Function(List timings); + +/// Signature for [Window.onPointerDataPacket]. +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); + +/// Signature for [Window.onSemanticsAction]. +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); + +/// Signature for responses to platform messages. +/// +/// Used as a parameter to [Window.sendPlatformMessage] and +/// [Window.onPlatformMessage]. +typedef PlatformMessageResponseCallback = void Function(ByteData? data); + +/// Signature for [Window.onPlatformMessage]. +typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback); + +// Signature for _setNeedsReportTimings. +typedef _SetNeedsReportTimingsFunc = void Function(bool value); + +/// Various important time points in the lifetime of a frame. +/// +/// [FrameTiming] records a timestamp of each phase for performance analysis. +enum FramePhase { + /// The timestamp of the vsync signal given by the operating system. + /// + /// See also [FrameTiming.vsyncOverhead]. + vsyncStart, + + /// When the UI thread starts building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildStart, + + /// When the UI thread finishes building a frame. + /// + /// See also [FrameTiming.buildDuration]. + buildFinish, + + /// When the raster thread starts rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterStart, + + /// When the raster thread finishes rasterizing a frame. + /// + /// See also [FrameTiming.rasterDuration]. + rasterFinish, +} + +/// Time-related performance metrics of a frame. +/// +/// If you're using the whole Flutter framework, please use +/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using +/// [Window.onReportTimings] directly because +/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If +/// [SchedulerBinding] is unavailable, then see [Window.onReportTimings] for how +/// to get this. +/// +/// The metrics in debug mode (`flutter run` without any flags) may be very +/// different from those in profile and release modes due to the debug overhead. +/// Therefore it's recommended to only monitor and analyze performance metrics +/// in profile and release modes. +class FrameTiming { + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// This constructor is used for unit test only. Real [FrameTiming]s should + /// be retrieved from [Window.onReportTimings]. + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + + /// Construct [FrameTiming] with raw timestamps in microseconds. + /// + /// List [timestamps] must have the same number of elements as + /// [FramePhase.values]. + /// + /// This constructor is usually only called by the Flutter engine, or a test. + /// To get the [FrameTiming] of your app, see [Window.onReportTimings]. + FrameTiming._(List timestamps) + : assert(timestamps.length == FramePhase.values.length), _timestamps = timestamps; + + /// This is a raw timestamp in microseconds from some epoch. The epoch in all + /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + + /// The duration to build the frame on the UI thread. + /// + /// The build starts approximately when [Window.onBeginFrame] is called. The + /// [Duration] in the [Window.onBeginFrame] callback is exactly the + /// `Duration(microseconds: timestampInMicroseconds(FramePhase.buildStart))`. + /// + /// The build finishes when [Window.render] is called. + /// + /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// To ensure smooth animations of X fps, this should not exceed 1000/X + /// milliseconds. + /// {@endtemplate} + /// {@template dart.ui.FrameTiming.fps_milliseconds} + /// That's about 16ms for 60fps, and 8ms for 120fps. + /// {@endtemplate} + Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + + /// The duration to rasterize the frame on the raster thread. + /// + /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds} + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + + /// The duration between receiving the vsync signal and starting building the + /// frame. + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + + /// The timespan between vsync start and raster finish. + /// + /// To achieve the lowest latency on an X fps display, this should not exceed + /// 1000/X milliseconds. + /// {@macro dart.ui.FrameTiming.fps_milliseconds} + /// + /// See also [vsyncOverhead], [buildDuration] and [rasterDuration]. + Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + +/// States that an application can be in. +/// +/// The values below describe notifications from the operating system. +/// Applications should not expect to always receive all possible +/// notifications. For example, if the users pulls out the battery from the +/// device, no notification will be sent before the application is suddenly +/// terminated, along with the rest of the operating system. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state +/// from the widgets layer. +enum AppLifecycleState { + /// The application is visible and responding to user input. + resumed, + + /// The application is in an inactive state and is not receiving user input. + /// + /// On iOS, this state corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when in + /// a phone call, responding to a TouchID request, when entering the app + /// switcher or the control center, or when the UIViewController hosting the + /// Flutter app is transitioning. + /// + /// On Android, this corresponds to an app or the Flutter host view running + /// in the foreground inactive state. Apps transition to this state when + /// another activity is focused, such as a split-screen app, a phone call, + /// a picture-in-picture app, a system dialog, or another window. + /// + /// Apps in this state should assume that they may be [paused] at any time. + inactive, + + /// The application is not currently visible to the user, not responding to + /// user input, and running in the background. + /// + /// When the application is in this state, the engine will not call the + /// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks. + paused, + + /// The application is still hosted on a flutter engine but is detached from + /// any host views. + /// + /// When the application is in this state, the engine is running without + /// a view. It can either be in the progress of attaching a view when engine + /// was first initializes, or after the view being destroyed due to a Navigator + /// pop. + detached, +} + +/// A representation of distances for each of the four edges of a rectangle, +/// used to encode the view insets and padding that applications should place +/// around their user interface, as exposed by [Window.viewInsets] and +/// [Window.padding]. View insets and padding are preferably read via +/// [MediaQuery.of]. +/// +/// For a generic class that represents distances around a rectangle, see the +/// [EdgeInsets] class. +/// +/// See also: +/// +/// * [WidgetsBindingObserver], for a widgets layer mechanism to receive +/// notifications when the padding changes. +/// * [MediaQuery.of], for the preferred mechanism for accessing these values. +/// * [Scaffold], which automatically applies the padding in material design +/// applications. +class WindowPadding { + const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom }); + + /// The distance from the left edge to the first unpadded pixel, in physical pixels. + final double left; + + /// The distance from the top edge to the first unpadded pixel, in physical pixels. + final double top; + + /// The distance from the right edge to the first unpadded pixel, in physical pixels. + final double right; + + /// The distance from the bottom edge to the first unpadded pixel, in physical pixels. + final double bottom; + + /// A window padding that has zeros for each edge. + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); + + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; + } +} + +/// An identifier used to select a user's language and formatting preferences. /// -/// Each [FlutterView] has its own layer tree that is rendered into an area -/// inside of a [FlutterWindow] whenever [render] is called with a [Scene]. +/// This represents a [Unicode Language +/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) +/// (i.e. without Locale extensions), except variants are not supported. +/// +/// Locales are canonicalized according to the "preferred value" entries in the +/// [IANA Language Subtag +/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). +/// For example, `const Locale('he')` and `const Locale('iw')` are equal and +/// both have the [languageCode] `he`, because `iw` is a deprecated language +/// subtag that was replaced by the subtag `he`. +/// +/// See also: +/// +/// * [Window.locale], which specifies the system's currently selected +/// [Locale]. +class Locale { + /// Creates a new Locale object. The first argument is the + /// primary language subtag, the second is the region (also + /// referred to as 'country') subtag. + /// + /// For example: + /// + /// ```dart + /// const Locale swissFrench = Locale('fr', 'CH'); + /// const Locale canadianFrench = Locale('fr', 'CA'); + /// ``` + /// + /// The primary language subtag must not be null. The region subtag is + /// optional. When there is no region/country subtag, the parameter should + /// be omitted or passed `null` instead of an empty-string. + /// + /// The subtag values are _case sensitive_ and must be one of the valid + /// subtags according to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The + /// primary language subtag must be at least two and at most eight lowercase + /// letters, but not four letters. The region region subtag must be two + /// uppercase letters or three digits. See the [Unicode Language + /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier) + /// specification. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which also allows a [scriptCode] to be + /// specified. + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + + /// Creates a new Locale object. + /// + /// The keyword arguments specify the subtags of the Locale. + /// + /// The subtag values are _case sensitive_ and must be valid subtags according + /// to CLDR supplemental data: + /// [language](http://unicode.org/cldr/latest/common/validity/language.xml), + /// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and + /// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for + /// each of languageCode, scriptCode and countryCode respectively. + /// + /// The [countryCode] subtag is optional. When there is no country subtag, + /// the parameter should be omitted or passed `null` instead of an empty-string. + /// + /// Validity is not checked by default, but some methods may throw away + /// invalid data. + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + + /// The primary language subtag for the locale. + /// + /// This must not be null. It may be 'und', representing 'undefined'. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "language". The string specified must match the case of the + /// string in the registry. + /// + /// Language subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const + /// Locale('he')` and `const Locale('iw')` are equal, and both have the + /// [languageCode] `he`, because `iw` is a deprecated language subtag that was + /// replaced by the subtag `he`. + /// + /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR + /// supplemental + /// data](http://unicode.org/cldr/latest/common/validity/language.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + + /// The script subtag for the locale. + /// + /// This may be null, indicating that there is no specified script subtag. + /// + /// This must be a valid Unicode Language Identifier script subtag as listed + /// in [Unicode CLDR supplemental + /// data](http://unicode.org/cldr/latest/common/validity/script.xml). + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + final String? scriptCode; + + /// The region subtag for the locale. + /// + /// This may be null, indicating that there is no specified region subtag. + /// + /// This is expected to be string registered in the [IANA Language Subtag + /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry) + /// with the type "region". The string specified must match the case of the + /// string in the registry. + /// + /// Region subtags that are deprecated in the registry and have a preferred + /// code are changed to their preferred code. For example, `const Locale('de', + /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the + /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was + /// replaced by the subtag `DE`. + /// + /// See also: + /// + /// * [Locale.fromSubtags], which describes the conventions for creating + /// [Locale] objects. + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other is! Locale) { + return false; + } + final String? countryCode = _countryCode; + final String? otherCountryCode = other.countryCode; + return other.languageCode == languageCode + && other.scriptCode == scriptCode // scriptCode cannot be '' + && (other.countryCode == countryCode // Treat '' as equal to null. + || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null + || countryCode != null && countryCode.isEmpty && other.countryCode == null); + } + + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode); + + static Locale? _cachedLocale; + static String? _cachedLocaleString; + + /// Returns a string representing the locale. + /// + /// This identifier happens to be a valid Unicode Locale Identifier using + /// underscores as separator, however it is intended to be used for debugging + /// purposes only. For parseable results, use [toLanguageTag] instead. + @keepToString + @override + String toString() { + if (!identical(_cachedLocale, this)) { + _cachedLocale = this; + _cachedLocaleString = _rawToString('_'); + } + return _cachedLocaleString!; + } + + /// Returns a syntactically valid Unicode BCP47 Locale Identifier. + /// + /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and + /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical + /// details. + String toLanguageTag() => _rawToString('-'); + + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null && scriptCode!.isNotEmpty) + out.write('$separator$scriptCode'); + if (_countryCode != null && _countryCode!.isNotEmpty) + out.write('$separator$countryCode'); + return out.toString(); + } +} + +/// The most basic interface to the host operating system's user interface. +/// +/// It exposes the size of the display, the core scheduler API, the input event +/// callback, the graphics drawing API, and other such core services. +/// +/// There is a single Window instance in the system, which you can +/// obtain from `WidgetsBinding.instance.window`. +/// +/// There is also a [window] singleton object in `dart:ui` if `WidgetsBinding` +/// is unavailable. But we strongly advise to avoid statically referencing it. +/// See the document of [window] for more details of why it should be avoided. /// /// ## Insets and Padding /// /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4} /// -/// In this illustration, the black areas represent system UI that the app -/// cannot draw over. The red area represents view padding that the view may not +/// In this diagram, the black areas represent system UI that the app cannot +/// draw over. The red area represents view padding that the application may not /// be able to detect gestures in and may not want to draw in. The grey area -/// represents the system keyboard, which can cover over the bottom view padding -/// when visible. +/// represents the system keyboard, which can cover over the bottom view +/// padding when visible. /// -/// The [viewInsets] are the physical pixels which the operating +/// The [Window.viewInsets] are the physical pixels which the operating /// system reserves for system UI, such as the keyboard, which would fully /// obscure any content drawn in that area. /// -/// The [viewPadding] are the physical pixels on each side of the -/// display that may be partially obscured by system UI or by physical -/// intrusions into the display, such as an overscan region on a television or a -/// "notch" on a phone. Unlike the insets, these areas may have portions that -/// show the user view-painted pixels without being obscured, such as a -/// notch at the top of a phone that covers only a subset of the area. Insets, -/// on the other hand, either partially or fully obscure the window, such as an -/// opaque keyboard or a partially translucent status bar, which cover an area -/// without gaps. +/// The [Window.viewPadding] are the physical pixels on each side of the display +/// that may be partially obscured by system UI or by physical intrusions into +/// the display, such as an overscan region on a television or a "notch" on a +/// phone. Unlike the insets, these areas may have portions that show the user +/// application painted pixels without being obscured, such as a notch at the +/// top of a phone that covers only a subset of the area. Insets, on the other +/// hand, either partially or fully obscure the window, such as an opaque +/// keyboard or a partially transluscent statusbar, which cover an area without +/// gaps. /// -/// The [padding] property is computed from both -/// [viewInsets] and [viewPadding]. It will allow a -/// view inset to consume view padding where appropriate, such as when a phone's -/// keyboard is covering the bottom view padding and so "absorbs" it. +/// The [Window.padding] property is computed from both [Window.viewInsets] and +/// [Window.viewPadding]. It will allow a view inset to consume view padding +/// where appropriate, such as when a phone's keyboard is covering the bottom +/// view padding and so "absorbs" it. /// /// Clients that want to position elements relative to the view padding -/// regardless of the view insets should use the [viewPadding] -/// property, e.g. if you wish to draw a widget at the center of the screen with -/// respect to the iPhone "safe area" regardless of whether the keyboard is -/// showing. -/// -/// [padding] is useful for clients that want to know how much -/// padding should be accounted for without concern for the current inset(s) -/// state, e.g. determining whether a gesture should be considered for scrolling -/// purposes. This value varies based on the current state of the insets. For -/// example, a visible keyboard will consume all gestures in the bottom part of -/// the [viewPadding] anyway, so there is no need to account for -/// that in the [padding], which is always safe to use for such -/// calculations. -/// -/// See also: +/// regardless of the view insets should use the [Window.viewPadding] property, +/// e.g. if you wish to draw a widget at the center of the screen with respect +/// to the iPhone "safe area" regardless of whether the keyboard is showing. /// -/// * [FlutterWindow], a special case of a [FlutterView] that is represented on -/// the platform as a separate window which can host other [FlutterView]s. -abstract class FlutterView { - /// The platform dispatcher that this view is registered with, and gets its - /// information from. - PlatformDispatcher get platformDispatcher; - - /// The configuration of this view. - ViewConfiguration get viewConfiguration; +/// [Window.padding] is useful for clients that want to know how much padding +/// should be accounted for without concern for the current inset(s) state, e.g. +/// determining whether a gesture should be considered for scrolling purposes. +/// This value varies based on the current state of the insets. For example, a +/// visible keyboard will consume all gestures in the bottom part of the +/// [Window.viewPadding] anyway, so there is no need to account for that in the +/// [Window.padding], which is always safe to use for such calculations. +class Window { + Window._() { + _setNeedsReportTimings = _nativeSetNeedsReportTimings; + } - /// The number of device pixels for each logical pixel for the screen this - /// view is displayed on. - /// - /// This number might not be a power of two. Indeed, it might not even be an - /// integer. For example, the Nexus 6 has a device pixel ratio of 3.5. + /// The number of device pixels for each logical pixel. This number might not + /// be a power of two. Indeed, it might not even be an integer. For example, + /// the Nexus 6 has a device pixel ratio of 3.5. /// /// Device pixels are also referred to as physical pixels. Logical pixels are /// also referred to as device-independent or resolution-independent pixels. @@ -91,59 +633,39 @@ abstract class FlutterView { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get devicePixelRatio => viewConfiguration.devicePixelRatio; + double get devicePixelRatio => _devicePixelRatio; + double _devicePixelRatio = 1.0; - /// The dimensions and location of the rectangle into which the scene rendered - /// in this view will be drawn on the screen, in physical pixels. + /// The dimensions of the rectangle into which the application will be drawn, + /// in physical pixels. /// /// When this changes, [onMetricsChanged] is called. /// - /// At startup, the size and location of the view may not be known before Dart + /// At startup, the size of the application window may not be known before Dart /// code runs. If this value is observed early in the application lifecycle, - /// it may report [Rect.zero]. + /// it may report [Size.zero]. /// /// This value does not take into account any on-screen keyboards or other /// system UI. The [padding] and [viewInsets] properties provide a view into - /// how much of each side of the view may be obscured by system UI. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - Rect get physicalGeometry => viewConfiguration.geometry; - - /// The dimensions of the rectangle into which the scene rendered in this view - /// will be drawn on the screen, in physical pixels. - /// - /// When this changes, [onMetricsChanged] is called. - /// - /// At startup, the size of the view may not be known before Dart code runs. - /// If this value is observed early in the application lifecycle, it may - /// report [Size.zero]. - /// - /// This value does not take into account any on-screen keyboards or other - /// system UI. The [padding] and [viewInsets] properties provide information - /// about how much of each side of the view may be obscured by system UI. - /// - /// This value is the same as the `size` member of [physicalGeometry]. + /// how much of each side of the application may be obscured by system UI. /// /// See also: /// - /// * [physicalGeometry], which reports the location of the view as well as - /// its size. /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - Size get physicalSize => viewConfiguration.geometry.size; + Size get physicalSize => _physicalSize; + Size _physicalSize = Size.zero; /// The number of physical pixels on each side of the display rectangle into - /// which the view can render, but over which the operating system will likely - /// place system UI, such as the keyboard, that fully obscures any content. + /// which the application can render, but over which the operating system + /// will likely place system UI, such as the keyboard, that fully obscures + /// any content. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [viewInsets], - /// [viewPadding], and [padding] are described in - /// more detail in the documentation for [FlutterView]. + /// The relationship between this [Window.viewInsets], [Window.viewPadding], + /// and [Window.padding] are described in more detail in the documentation for + /// [Window]. /// /// See also: /// @@ -152,24 +674,25 @@ abstract class FlutterView { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the view insets in material /// design applications. - WindowPadding get viewInsets => viewConfiguration.viewInsets; + WindowPadding get viewInsets => _viewInsets; + WindowPadding _viewInsets = WindowPadding.zero; /// The number of physical pixels on each side of the display rectangle into - /// which the view can render, but which may be partially obscured by system - /// UI (such as the system notification area), or or physical intrusions in - /// the display (e.g. overscan regions on television screens or phone sensor - /// housings). + /// which the application can render, but which may be partially obscured by + /// system UI (such as the system notification area), or or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). /// - /// Unlike [padding], this value does not change relative to - /// [viewInsets]. For example, on an iPhone X, it will not - /// change in response to the soft keyboard being visible or hidden, whereas - /// [padding] will. + /// Unlike [Window.padding], this value does not change relative to + /// [Window.viewInsets]. For example, on an iPhone X, it will not change in + /// response to the soft keyboard being visible or hidden, whereas + /// [Window.padding] will. /// /// When this property changes, [onMetricsChanged] is called. /// - /// The relationship between this [viewInsets], - /// [viewPadding], and [padding] are described in - /// more detail in the documentation for [FlutterView]. + /// The relationship between this [Window.viewInsets], [Window.viewPadding], + /// and [Window.padding] are described in more detail in the documentation for + /// [Window]. /// /// See also: /// @@ -178,11 +701,12 @@ abstract class FlutterView { /// * [MediaQuery.of], a simpler mechanism for the same. /// * [Scaffold], which automatically applies the padding in material design /// applications. - WindowPadding get viewPadding => viewConfiguration.viewPadding; + WindowPadding get viewPadding => _viewPadding; + WindowPadding _viewPadding = WindowPadding.zero; /// The number of physical pixels on each side of the display rectangle into - /// which the view can render, but where the operating system will consume - /// input gestures for the sake of system navigation. + /// which the application can render, but where the operating system will + /// consume input gestures for the sake of system navigation. /// /// For example, an operating system might use the vertical edges of the /// screen, where swiping inwards from the edges takes users backward @@ -195,158 +719,85 @@ abstract class FlutterView { /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. /// * [MediaQuery.of], a simpler mechanism for the same. - WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; + WindowPadding get systemGestureInsets => _systemGestureInsets; + WindowPadding _systemGestureInsets = WindowPadding.zero; /// The number of physical pixels on each side of the display rectangle into - /// which the view can render, but which may be partially obscured by system - /// UI (such as the system notification area), or or physical intrusions in - /// the display (e.g. overscan regions on television screens or phone sensor - /// housings). - /// - /// This value is calculated by taking `max(0.0, FlutterView.viewPadding - - /// FlutterView.viewInsets)`. This will treat a system IME that increases the - /// bottom inset as consuming that much of the bottom padding. For example, on - /// an iPhone X, [EdgeInsets.bottom] of [FlutterView.padding] is the same as - /// [EdgeInsets.bottom] of [FlutterView.viewPadding] when the soft keyboard is - /// not drawn (to account for the bottom soft button area), but will be `0.0` - /// when the soft keyboard is visible. + /// which the application can render, but which may be partially obscured by + /// system UI (such as the system notification area), or or physical + /// intrusions in the display (e.g. overscan regions on television screens or + /// phone sensor housings). + /// + /// This value is calculated by taking + /// `max(0.0, Window.viewPadding - Window.viewInsets)`. This will treat a + /// system IME that increases the bottom inset as consuming that much of the + /// bottom padding. For example, on an iPhone X, [EdgeInsets.bottom] of + /// [Window.padding] is the same as [EdgeInsets.bottom] of + /// [Window.viewPadding] when the soft keyboard is not drawn (to account for + /// the bottom soft button area), but will be `0.0` when the soft keyboard is + /// visible. /// /// When this changes, [onMetricsChanged] is called. /// - /// The relationship between this [viewInsets], [viewPadding], and [padding] - /// are described in more detail in the documentation for [FlutterView]. + /// The relationship between this [Window.viewInsets], [Window.viewPadding], + /// and [Window.padding] are described in more detail in the documentation for + /// [Window]. /// /// See also: /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - /// * [MediaQuery.of], a simpler mechanism for the same. - /// * [Scaffold], which automatically applies the padding in material design - /// applications. - WindowPadding get padding => viewConfiguration.padding; + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + /// * [MediaQuery.of], a simpler mechanism for the same. + /// * [Scaffold], which automatically applies the padding in material design + /// applications. + WindowPadding get padding => _padding; + WindowPadding _padding = WindowPadding.zero; - /// Updates the view's rendering on the GPU with the newly provided [Scene]. - /// - /// This function must be called within the scope of the - /// [PlatformDispatcher.onBeginFrame] or [PlatformDispatcher.onDrawFrame] - /// callbacks being invoked. - /// - /// If this function is called a second time during a single - /// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame] - /// callback sequence or called outside the scope of those callbacks, the call - /// will be ignored. + /// A callback that is invoked whenever the [devicePixelRatio], + /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] + /// values change, for example when the device is rotated or when the + /// application is resized (e.g. when showing applications side-by-side + /// on Android). /// - /// To record graphical operations, first create a [PictureRecorder], then - /// construct a [Canvas], passing that [PictureRecorder] to its constructor. - /// After issuing all the graphical operations, call the - /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain - /// the final [Picture] that represents the issued graphical operations. + /// The engine invokes this callback in the same zone in which the callback + /// was set. /// - /// Next, create a [SceneBuilder], and add the [Picture] to it using - /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can - /// then obtain a [Scene] object, which you can display to the user via this - /// [render] function. + /// The framework registers with this callback and updates the layout + /// appropriately. /// /// See also: /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - /// * [RendererBinding], the Flutter framework class which manages layout and - /// painting. - void render(Scene scene) => _render(scene, this); - void _render(Scene scene, FlutterView view) native 'PlatformConfiguration_render'; -} - -/// A top-level platform window displaying a Flutter layer tree drawn from a -/// [Scene]. -/// -/// The current list of all Flutter views for the application is available from -/// `WidgetsBinding.instance.platformDispatcher.views`. Only views that are of type -/// [FlutterWindow] are top level platform windows. -/// -/// There is also a [PlatformDispatcher.instance] singleton object in `dart:ui` -/// if `WidgetsBinding` is unavailable, but we strongly advise avoiding a static -/// reference to it. See the documentation for [PlatformDispatcher.instance] for -/// more details about why it should be avoided. -/// -/// See also: -/// -/// * [PlatformDispatcher], which manages the current list of [FlutterView] (and -/// thus [FlutterWindow]) instances. -class FlutterWindow extends FlutterView { - FlutterWindow._(this._windowId, this.platformDispatcher); - - /// The opaque ID for this view. - final Object _windowId; - - @override - final PlatformDispatcher platformDispatcher; - - @override - ViewConfiguration get viewConfiguration { - assert(platformDispatcher._viewConfigurations.containsKey(_windowId)); - return platformDispatcher._viewConfigurations[_windowId]!; - } -} - -/// A [FlutterWindow] that includes access to setting callbacks and retrieving -/// properties that reside on the [PlatformDispatcher]. -/// -/// It is the type of the global [window] singleton used by applications that -/// only have a single main window. -/// -/// In addition to the properties of [FlutterView], this class provides access -/// to platform-specific properties. To modify or retrieve these properties, -/// applications designed for more than one main window should prefer using -/// `WidgetsBinding.instance.platformDispatcher` instead. -/// -/// Prefer access through `WidgetsBinding.instance.window` or -/// `WidgetsBinding.instance.platformDispatcher` over a static reference to -/// [window], or [PlatformDispatcher.instance]. See the documentation for -/// [PlatformDispatcher.instance] for more details about this recommendation. -class SingletonFlutterWindow extends FlutterWindow { - SingletonFlutterWindow._(Object windowId, PlatformDispatcher platformDispatcher) - : super._(windowId, platformDispatcher); - - /// A callback that is invoked whenever the [devicePixelRatio], - /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], or - /// [systemGestureInsets] values change. - /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// - /// See [PlatformDispatcher.onMetricsChanged] for more information. - VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// register for notifications when this is called. + /// * [MediaQuery.of], a simpler mechanism for the same. + VoidCallback? get onMetricsChanged => _onMetricsChanged; + VoidCallback? _onMetricsChanged; + Zone _onMetricsChangedZone = Zone.root; set onMetricsChanged(VoidCallback? callback) { - platformDispatcher.onMetricsChanged = callback; + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; } /// The system-reported default locale of the device. /// - /// {@template flutter.lib.ui.window.accessorForwardWarning} - /// Accessing this value returns the value contained in the - /// [PlatformDispatcher] singleton, so instead of getting it from here, you - /// should consider getting it from `WidgetsBinding.instance.platformDispatcher` instead - /// (or, when `WidgetsBinding` isn't available, from - /// [PlatformDispatcher.instance]). The reason this value forwards to the - /// [PlatformDispatcher] is to provide convenience for applications that only - /// use a single main window. - /// {@endtemplate} - /// - /// This establishes the language and formatting conventions that window + /// This establishes the language and formatting conventions that application /// should, if possible, use to render their user interface. /// - /// This is the first locale selected by the user and is the user's primary - /// locale (the locale the device UI is displayed in) + /// This is the first locale selected by the user and is the user's + /// primary locale (the locale the device UI is displayed in) /// - /// This is equivalent to `locales.first` and will provide an empty non-null - /// locale if the [locales] list has not been set or is empty. - Locale? get locale => platformDispatcher.locale; + /// This is equivalent to `locales.first` and will provide an empty non-null locale + /// if the [locales] list has not been set or is empty. + Locale? get locale { + if (_locales != null && _locales!.isNotEmpty) { + return _locales!.first; + } + return null; + } /// The full system-reported supported locales of the device. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// - /// This establishes the language and formatting conventions that window + /// This establishes the language and formatting conventions that application /// should, if possible, use to render their user interface. /// /// The list is ordered in order of priority, with lower-indexed locales being @@ -358,7 +809,8 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - List? get locales => platformDispatcher.locales; + List? get locales => _locales; + List? _locales; /// Performs the platform-native locale resolution. /// @@ -369,13 +821,27 @@ class SingletonFlutterWindow extends FlutterWindow { /// This method returns synchronously and is a direct call to /// platform specific APIs without invoking method channels. Locale? computePlatformResolvedLocale(List supportedLocales) { - return platformDispatcher.computePlatformResolvedLocale(supportedLocales); + final List supportedLocalesData = []; + for (Locale locale in supportedLocales) { + supportedLocalesData.add(locale.languageCode); + supportedLocalesData.add(locale.countryCode); + supportedLocalesData.add(locale.scriptCode); + } + + final List result = _computePlatformResolvedLocale(supportedLocalesData); + + if (result.isNotEmpty) { + return Locale.fromSubtags( + languageCode: result[0], + countryCode: result[1] == '' ? null : result[1], + scriptCode: result[2] == '' ? null : result[2]); + } + return null; } + List _computePlatformResolvedLocale(List supportedLocalesData) native 'PlatformConfiguration_computePlatformResolvedLocale'; /// A callback that is invoked whenever [locale] changes value. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -383,25 +849,32 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; + VoidCallback? get onLocaleChanged => _onLocaleChanged; + VoidCallback? _onLocaleChanged; + Zone _onLocaleChangedZone = Zone.root; set onLocaleChanged(VoidCallback? callback) { - platformDispatcher.onLocaleChanged = callback; + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; } /// The lifecycle state immediately after dart isolate initialization. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// This property will not be updated as the lifecycle changes. /// /// It is used to initialize [SchedulerBinding.lifecycleState] at startup /// with any buffered lifecycle state events. - String get initialLifecycleState => platformDispatcher.initialLifecycleState; + String get initialLifecycleState { + _initialLifecycleStateAccessed = true; + return _initialLifecycleState; + } + late String _initialLifecycleState; + /// Tracks if the initial state has been accessed. Once accessed, we + /// will stop updating the [initialLifecycleState], as it is not the + /// preferred way to access the state. + bool _initialLifecycleStateAccessed = false; /// The system-reported text scale. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// This establishes the text scaling factor to use when rendering text, /// according to the user's platform preferences. /// @@ -412,20 +885,18 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this value changes. - double get textScaleFactor => platformDispatcher.textScaleFactor; + double get textScaleFactor => _textScaleFactor; + double _textScaleFactor = 1.0; /// The setting indicating whether time should always be shown in the 24-hour /// format. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// This option is used by [showTimePicker]. - bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; + bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; + bool _alwaysUse24HourFormat = false; /// A callback that is invoked whenever [textScaleFactor] changes value. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -433,23 +904,21 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; + VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + VoidCallback? _onTextScaleFactorChanged; + Zone _onTextScaleFactorChangedZone = Zone.root; set onTextScaleFactorChanged(VoidCallback? callback) { - platformDispatcher.onTextScaleFactorChanged = callback; + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; } /// The setting indicating the current brightness mode of the host platform. - /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// - /// If the platform has no preference, [platformBrightness] defaults to - /// [Brightness.light]. - Brightness get platformBrightness => platformDispatcher.platformBrightness; + /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. + Brightness get platformBrightness => _platformBrightness; + Brightness _platformBrightness = Brightness.light; /// A callback that is invoked whenever [platformBrightness] changes value. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -457,20 +926,19 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to /// observe when this callback is invoked. - VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; + VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; + VoidCallback? _onPlatformBrightnessChanged; + Zone _onPlatformBrightnessChangedZone = Zone.root; set onPlatformBrightnessChanged(VoidCallback? callback) { - platformDispatcher.onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; } - /// A callback that is invoked to notify the window that it is an appropriate - /// time to provide a scene using the [SceneBuilder] API and the [render] - /// method. - /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// - /// When possible, this is driven by the hardware VSync signal. This is only - /// called if [scheduleFrame] has been called since the last time this - /// callback was invoked. + /// A callback that is invoked to notify the application that it is an + /// appropriate time to provide a scene using the [SceneBuilder] API and the + /// [render] method. When possible, this is driven by the hardware VSync + /// signal. This is only called if [scheduleFrame] has been called since the + /// last time this callback was invoked. /// /// The [onDrawFrame] callback is invoked immediately after [onBeginFrame], /// after draining any microtasks (e.g. completions of any [Future]s) queued @@ -485,18 +953,18 @@ class SingletonFlutterWindow extends FlutterWindow { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; + FrameCallback? get onBeginFrame => _onBeginFrame; + FrameCallback? _onBeginFrame; + Zone _onBeginFrameZone = Zone.root; set onBeginFrame(FrameCallback? callback) { - platformDispatcher.onBeginFrame = callback; + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; } /// A callback that is invoked for each frame after [onBeginFrame] has - /// completed and after the microtask queue has been drained. - /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// - /// This can be used to implement a second phase of frame rendering that - /// happens after any deferred work queued by the [onBeginFrame] phase. + /// completed and after the microtask queue has been drained. This can be + /// used to implement a second phase of frame rendering that happens + /// after any deferred work queued by the [onBeginFrame] phase. /// /// The framework invokes this callback in the same zone in which the /// callback was set. @@ -507,21 +975,22 @@ class SingletonFlutterWindow extends FlutterWindow { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; + VoidCallback? get onDrawFrame => _onDrawFrame; + VoidCallback? _onDrawFrame; + Zone _onDrawFrameZone = Zone.root; set onDrawFrame(VoidCallback? callback) { - platformDispatcher.onDrawFrame = callback; + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; } /// A callback that is invoked to report the [FrameTiming] of recently /// rasterized frames. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use - /// [SingletonFlutterWindow.onReportTimings] directly because + /// [Window.onReportTimings] directly because /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. /// - /// This can be used to see if the window has missed frames (through + /// This can be used to see if the application has missed frames (through /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high /// latencies (through [FrameTiming.totalSpan]). /// @@ -535,15 +1004,22 @@ class SingletonFlutterWindow extends FlutterWindow { /// Flutter spends less than 0.1ms every 1 second to report the timings /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for /// 60fps), or 0.01% CPU usage per second. - TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; + TimingsCallback? get onReportTimings => _onReportTimings; + TimingsCallback? _onReportTimings; + Zone _onReportTimingsZone = Zone.root; set onReportTimings(TimingsCallback? callback) { - platformDispatcher.onReportTimings = callback; + if ((callback == null) != (_onReportTimings == null)) { + _setNeedsReportTimings(callback != null); + } + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; } + late _SetNeedsReportTimingsFunc _setNeedsReportTimings; + void _nativeSetNeedsReportTimings(bool value) native 'PlatformConfiguration_setNeedsReportTimings'; + /// A callback that is invoked when pointer data is available. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. /// @@ -551,16 +1027,17 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// * [GestureBinding], the Flutter framework class which manages pointer /// events. - PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; + PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + PointerDataPacketCallback? _onPointerDataPacket; + Zone _onPointerDataPacketZone = Zone.root; set onPointerDataPacket(PointerDataPacketCallback? callback) { - platformDispatcher.onPointerDataPacket = callback; + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; } /// The route or path that the embedder requested when the application was /// launched. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// This will be the string "`/`" if no particular route was requested. /// /// ## Android @@ -584,89 +1061,117 @@ class SingletonFlutterWindow extends FlutterWindow { /// * [Navigator], a widget that handles routing. /// * [SystemChannels.navigation], which handles subsequent navigation /// requests from the embedder. - String get defaultRouteName => platformDispatcher.defaultRouteName; - - /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and - /// [onDrawFrame] callbacks be invoked. - /// - /// {@template flutter.lib.ui.window.functionForwardWarning} - /// Calling this function forwards the call to the same function on the - /// [PlatformDispatcher] singleton, so instead of calling it here, you should - /// consider calling it on `WidgetsBinding.instance.platformDispatcher` instead (or, when - /// `WidgetsBinding` isn't available, on [PlatformDispatcher.instance]). The - /// reason this function forwards to the [PlatformDispatcher] is to provide - /// convenience for applications that only use a single main window. - /// {@endtemplate} + String get defaultRouteName => _defaultRouteName(); + String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName'; + + /// Requests that, at the next appropriate opportunity, the [onBeginFrame] + /// and [onDrawFrame] callbacks be invoked. /// /// See also: /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - void scheduleFrame() => platformDispatcher.scheduleFrame(); + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + void scheduleFrame() native 'PlatformConfiguration_scheduleFrame'; + + /// Updates the application's rendering on the GPU with the newly provided + /// [Scene]. This function must be called within the scope of the + /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function + /// is called a second time during a single [onBeginFrame]/[onDrawFrame] + /// callback sequence or called outside the scope of those callbacks, the call + /// will be ignored. + /// + /// To record graphical operations, first create a [PictureRecorder], then + /// construct a [Canvas], passing that [PictureRecorder] to its constructor. + /// After issuing all the graphical operations, call the + /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain + /// the final [Picture] that represents the issued graphical operations. + /// + /// Next, create a [SceneBuilder], and add the [Picture] to it using + /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can + /// then obtain a [Scene] object, which you can display to the user via this + /// [render] function. + /// + /// See also: + /// + /// * [SchedulerBinding], the Flutter framework class which manages the + /// scheduling of frames. + /// * [RendererBinding], the Flutter framework class which manages layout and + /// painting. + void render(Scene scene) native 'PlatformConfiguration_render'; /// Whether the user has requested that [updateSemantics] be called when /// the semantic contents of window changes. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The [onSemanticsEnabledChanged] callback is called whenever this value /// changes. - bool get semanticsEnabled => platformDispatcher.semanticsEnabled; + bool get semanticsEnabled => _semanticsEnabled; + bool _semanticsEnabled = false; /// A callback that is invoked when the value of [semanticsEnabled] changes. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; + VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + VoidCallback? _onSemanticsEnabledChanged; + Zone _onSemanticsEnabledChangedZone = Zone.root; set onSemanticsEnabledChanged(VoidCallback? callback) { - platformDispatcher.onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; } /// A callback that is invoked whenever the user requests an action to be /// performed. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// This callback is used when the user expresses the action they wish to /// perform based on the semantics supplied by [updateSemantics]. /// /// The framework invokes this callback in the same zone in which the /// callback was set. - SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; + SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + SemanticsActionCallback? _onSemanticsAction; + Zone _onSemanticsActionZone = Zone.root; set onSemanticsAction(SemanticsActionCallback? callback) { - platformDispatcher.onSemanticsAction = callback; + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; } /// Additional accessibility features that may be enabled by the platform. - AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; + AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; + // The zero value matches the default value in `platform_data.h`. + AccessibilityFeatures _accessibilityFeatures = const AccessibilityFeatures._(0); /// A callback that is invoked when the value of [accessibilityFeatures] changes. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The framework invokes this callback in the same zone in which the /// callback was set. - VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged; + VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; + VoidCallback? _onAccessibilityFeaturesChanged; + Zone _onAccessibilityFeaturesChangedZone = Zone.root; set onAccessibilityFeaturesChanged(VoidCallback? callback) { - platformDispatcher.onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; } /// Change the retained semantics data about this window. /// - /// {@macro flutter.lib.ui.window.functionForwardWarning} - /// /// If [semanticsEnabled] is true, the user has requested that this function /// be called whenever the semantic content of this window changes. /// /// In either case, this function disposes the given update, which means the /// semantics update cannot be used further. - void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); + void updateSemantics(SemanticsUpdate update) native 'PlatformConfiguration_updateSemantics'; - /// Sends a message to a platform-specific plugin. + /// Set the debug name associated with this window's root isolate. + /// + /// Normally debug names are automatically generated from the Dart port, entry + /// point, and source file. For example: `main.dart$main-1234`. /// - /// {@macro flutter.lib.ui.window.functionForwardWarning} + /// This can be combined with flutter tools `--isolate-filter` flag to debug + /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. + /// Note that this does not rename any child isolates of the root. + void setIsolateDebugName(String name) native 'PlatformConfiguration_setIsolateDebugName'; + + /// Sends a message to a platform-specific plugin. /// /// The `name` parameter determines which plugin receives the message. The /// `data` parameter contains the message payload and is typically UTF-8 @@ -676,16 +1181,20 @@ class SingletonFlutterWindow extends FlutterWindow { /// The framework invokes [callback] in the same zone in which this method /// was called. void sendPlatformMessage(String name, - ByteData? data, - PlatformMessageResponseCallback? callback) { - platformDispatcher.sendPlatformMessage(name, data, callback); + ByteData? data, + PlatformMessageResponseCallback? callback) { + final String? error = + _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data); + if (error != null) + throw Exception(error); } + String? _sendPlatformMessage(String name, + PlatformMessageResponseCallback? callback, + ByteData? data) native 'PlatformConfiguration_sendPlatformMessage'; /// Called whenever this window receives a message from a platform-specific /// plugin. /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} - /// /// The `name` parameter determines which plugin sent the message. The `data` /// parameter is the payload and is typically UTF-8 encoded JSON but can be /// arbitrary data. @@ -696,23 +1205,43 @@ class SingletonFlutterWindow extends FlutterWindow { /// /// The framework invokes this callback in the same zone in which the /// callback was set. - PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + PlatformMessageCallback? _onPlatformMessage; + Zone _onPlatformMessageZone = Zone.root; set onPlatformMessage(PlatformMessageCallback? callback) { - platformDispatcher.onPlatformMessage = callback; + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; } - /// Set the debug name associated with this platform dispatcher's root - /// isolate. - /// - /// {@macro flutter.lib.ui.window.accessorForwardWarning} + /// Called by [_dispatchPlatformMessage]. + void _respondToPlatformMessage(int responseId, ByteData? data) + native 'PlatformConfiguration_respondToPlatformMessage'; + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback? callback) { + if (callback == null) + return null; + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + + /// The embedder can specify data that the isolate can request synchronously + /// on launch. This accessor fetches that data. /// - /// Normally debug names are automatically generated from the Dart port, entry - /// point, and source file. For example: `main.dart$main-1234`. + /// This data is persistent for the duration of the Flutter application and is + /// available even after isolate restarts. Because of this lifecycle, the size + /// of this data must be kept to a minimum. /// - /// This can be combined with flutter tools `--isolate-filter` flag to debug - /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`. - /// Note that this does not rename any child isolates of the root. - void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); + /// For asynchronous communication between the embedder and isolate, a + /// platform channel may be used. + ByteData? getPersistentIsolateData() native 'PlatformConfiguration_getPersistentIsolateData'; } /// Additional accessibility features that may be enabled by the platform. @@ -794,220 +1323,6 @@ class AccessibilityFeatures { int get hashCode => _index.hashCode; } -/// A soon-to-be deprecated class that is wholly replaced by -/// [SingletonFlutterWindow]. -/// -/// This class will be removed once the framework no longer refers to it. -// In order for the documentation build to succeed, this interface duplicates -// all of the methods with documentation, overrides them, and calls the super -// implementation. Once this merges into the framework and the framework -// references to it can be updated, this class will be removed entirely. -class Window extends SingletonFlutterWindow { - Window._(Object windowId, PlatformDispatcher platformDispatcher) - : super._(windowId, platformDispatcher); - - @override - // ignore: unnecessary_overrides - double get devicePixelRatio => super.devicePixelRatio; - - @override - // ignore: unnecessary_overrides - Rect get physicalGeometry => super.physicalGeometry; - - @override - // ignore: unnecessary_overrides - Size get physicalSize => super.physicalSize; - - @override - // ignore: unnecessary_overrides - WindowPadding get viewInsets => super.viewInsets; - - @override - // ignore: unnecessary_overrides - WindowPadding get viewPadding => super.viewPadding; - - @override - // ignore: unnecessary_overrides - WindowPadding get systemGestureInsets => super.systemGestureInsets; - - @override - // ignore: unnecessary_overrides - WindowPadding get padding => super.padding; - - @override - // ignore: unnecessary_overrides - void render(Scene scene) => super.render(scene); - - @override - // ignore: unnecessary_overrides - VoidCallback? get onMetricsChanged => super.onMetricsChanged; - @override - // ignore: unnecessary_overrides - set onMetricsChanged(VoidCallback? callback) { - super.onMetricsChanged = callback; - } - - @override - // ignore: unnecessary_overrides - Locale? get locale => super.locale; - - @override - // ignore: unnecessary_overrides - List? get locales => super.locales; - - @override - // ignore: unnecessary_overrides - Locale? computePlatformResolvedLocale(List supportedLocales) { - return super.computePlatformResolvedLocale(supportedLocales); - } - - @override - // ignore: unnecessary_overrides - VoidCallback? get onLocaleChanged => super.onLocaleChanged; - @override - // ignore: unnecessary_overrides - set onLocaleChanged(VoidCallback? callback) { - super.onLocaleChanged = callback; - } - - @override - // ignore: unnecessary_overrides - String get initialLifecycleState => super.initialLifecycleState; - - @override - // ignore: unnecessary_overrides - double get textScaleFactor => super.textScaleFactor; - - @override - // ignore: unnecessary_overrides - bool get alwaysUse24HourFormat => super.alwaysUse24HourFormat; - - @override - // ignore: unnecessary_overrides - VoidCallback? get onTextScaleFactorChanged => super.onTextScaleFactorChanged; - @override - // ignore: unnecessary_overrides - set onTextScaleFactorChanged(VoidCallback? callback) { - super.onTextScaleFactorChanged = callback; - } - - @override - // ignore: unnecessary_overrides - Brightness get platformBrightness => super.platformBrightness; - - @override - // ignore: unnecessary_overrides - VoidCallback? get onPlatformBrightnessChanged => super.onPlatformBrightnessChanged; - @override - // ignore: unnecessary_overrides - set onPlatformBrightnessChanged(VoidCallback? callback) { - super.onPlatformBrightnessChanged = callback; - } - - @override - // ignore: unnecessary_overrides - FrameCallback? get onBeginFrame => super.onBeginFrame; - @override - // ignore: unnecessary_overrides - set onBeginFrame(FrameCallback? callback) { - super.onBeginFrame = callback; - } - - @override - // ignore: unnecessary_overrides - VoidCallback? get onDrawFrame => super.onDrawFrame; - @override - // ignore: unnecessary_overrides - set onDrawFrame(VoidCallback? callback) { - super.onDrawFrame = callback; - } - - @override - // ignore: unnecessary_overrides - TimingsCallback? get onReportTimings => super.onReportTimings; - @override - // ignore: unnecessary_overrides - set onReportTimings(TimingsCallback? callback) { - super.onReportTimings = callback; - } - - @override - // ignore: unnecessary_overrides - PointerDataPacketCallback? get onPointerDataPacket => super.onPointerDataPacket; - @override - // ignore: unnecessary_overrides - set onPointerDataPacket(PointerDataPacketCallback? callback) { - super.onPointerDataPacket = callback; - } - - @override - // ignore: unnecessary_overrides - String get defaultRouteName => super.defaultRouteName; - - @override - // ignore: unnecessary_overrides - void scheduleFrame() => super.scheduleFrame(); - - @override - // ignore: unnecessary_overrides - bool get semanticsEnabled => super.semanticsEnabled; - - @override - // ignore: unnecessary_overrides - VoidCallback? get onSemanticsEnabledChanged => super.onSemanticsEnabledChanged; - @override - // ignore: unnecessary_overrides - set onSemanticsEnabledChanged(VoidCallback? callback) { - super.onSemanticsEnabledChanged = callback; - } - - @override - // ignore: unnecessary_overrides - SemanticsActionCallback? get onSemanticsAction => super.onSemanticsAction; - @override - // ignore: unnecessary_overrides - set onSemanticsAction(SemanticsActionCallback? callback) { - super.onSemanticsAction = callback; - } - - @override - // ignore: unnecessary_overrides - AccessibilityFeatures get accessibilityFeatures => super.accessibilityFeatures; - - @override - // ignore: unnecessary_overrides - VoidCallback? get onAccessibilityFeaturesChanged => - super.onAccessibilityFeaturesChanged; - @override - // ignore: unnecessary_overrides - set onAccessibilityFeaturesChanged(VoidCallback? callback) { - super.onAccessibilityFeaturesChanged = callback; - } - - @override - // ignore: unnecessary_overrides - void updateSemantics(SemanticsUpdate update) => super.updateSemantics(update); - - @override - // ignore: unnecessary_overrides - void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) { - super.sendPlatformMessage(name, data, callback); - } - - @override - // ignore: unnecessary_overrides - PlatformMessageCallback? get onPlatformMessage => super.onPlatformMessage; - @override - // ignore: unnecessary_overrides - set onPlatformMessage(PlatformMessageCallback? callback) { - super.onPlatformMessage = callback; - } - - @override - // ignore: unnecessary_overrides - void setIsolateDebugName(String name) => super.setIsolateDebugName(name); -} - /// Describes the contrast of a theme or color palette. enum Brightness { /// The color is dark and will require a light text color to achieve readable @@ -1023,18 +1338,12 @@ enum Brightness { light, } -/// The [SingletonFlutterWindow] representing the main window for applications -/// where there is only one window, such as applications designed for -/// single-display mobile devices. -/// -/// Applications that are designed to use more than one window should interact -/// with the `WidgetsBinding.instance.platformDispatcher` instead. +/// The [Window] singleton. /// -/// Consider avoiding static references to this singleton through -/// [PlatformDispatcher.instance] and instead prefer using a binding for -/// dependency resolution such as `WidgetsBinding.instance.window`. +/// Please try to avoid statically referencing this and instead use a +/// binding for dependency resolution such as `WidgetsBinding.instance.window`. /// -/// Static access of this `window` object means that Flutter has few, if any +/// Static access of this "window" object means that Flutter has few, if any /// options to fake or mock the given object in tests. Even in cases where Dart /// offers special language constructs to forcefully shadow such properties, /// those mechanisms would only be reasonable for tests and they would not be @@ -1042,14 +1351,6 @@ enum Brightness { /// appropriate implementation at runtime. /// /// The only place that `WidgetsBinding.instance.window` is inappropriate is if -/// access to these APIs is required before the binding is initialized by -/// invoking `runApp()` or `WidgetsFlutterBinding.instance.ensureInitialized()`. -/// In that case, it is necessary (though unfortunate) to use the -/// [PlatformDispatcher.instance] object statically. -/// -/// See also: -/// -/// * [PlatformDispatcher.views], contains the current list of Flutter windows -/// belonging to the application, including top level application windows like -/// this one. -final Window window = Window._(0, PlatformDispatcher.instance); +/// a `Window` is required before invoking `runApp()`. In that case, it is +/// acceptable (though unfortunate) to use this object statically. +final Window window = Window._(); diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index e8ff2274fe7a1..e470e1dab79e6 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -9,7 +9,6 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_message_response_dart.h" -#include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" @@ -198,8 +197,7 @@ PlatformConfiguration::~PlatformConfiguration() {} void PlatformConfiguration::DidCreateIsolate() { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); - windows_.insert(std::make_pair(0, std::unique_ptr(new Window{ - 0, ViewportMetrics{1.0, 0.0, 0.0}}))); + window_.reset(new Window({1.0, 0.0, 0.0})); } void PlatformConfiguration::UpdateLocales( @@ -423,7 +421,7 @@ void PlatformConfiguration::RegisterNatives( true}, {"PlatformConfiguration_respondToPlatformMessage", _RespondToPlatformMessage, 3, true}, - {"PlatformConfiguration_render", Render, 3, true}, + {"PlatformConfiguration_render", Render, 2, true}, {"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true}, {"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2, true}, diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index e4ce4bd5b0307..4652c7c5dcdbb 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -6,12 +6,14 @@ #define FLUTTER_LIB_UI_WINDOW_PLATFORM_CONFIGURATION_H_ #include +#include #include #include #include #include "flutter/fml/time/time_point.h" #include "flutter/lib/ui/semantics/semantics_update.h" +#include "flutter/lib/ui/window/platform_message.h" #include "flutter/lib/ui/window/pointer_data_packet.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" @@ -376,14 +378,11 @@ class PlatformConfiguration final { static void RegisterNatives(tonic::DartLibraryNatives* natives); //---------------------------------------------------------------------------- - /// @brief Retrieves the Window with the given ID managed by the - /// `PlatformConfiguration`. - /// - /// @param[in] window_id The id of the window to find and return. + /// @brief Retrieves the Window managed by the PlatformConfiguration. /// /// @return a pointer to the Window. /// - Window* get_window(int window_id) { return windows_[window_id].get(); } + Window* window() const { return window_.get(); } //---------------------------------------------------------------------------- /// @brief Responds to a previous platform message to the engine from the @@ -409,7 +408,7 @@ class PlatformConfiguration final { PlatformConfigurationClient* client_; tonic::DartPersistentValue library_; - std::unordered_map> windows_; + std::unique_ptr window_; // We use id 0 to mean that no response is expected. int next_response_id_ = 1; diff --git a/lib/ui/window/platform_configuration_unittests.cc b/lib/ui/window/platform_configuration_unittests.cc index f38498614e187..1ff3ce01de52d 100644 --- a/lib/ui/window/platform_configuration_unittests.cc +++ b/lib/ui/window/platform_configuration_unittests.cc @@ -54,14 +54,11 @@ TEST_F(ShellTest, PlatformConfigurationInitialization) { Dart_NativeArguments args) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->get_window(0), nullptr); - ASSERT_EQ( - configuration->get_window(0)->viewport_metrics().device_pixel_ratio, - 1.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, - 0.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, - 0.0); + ASSERT_NE(configuration->window(), nullptr); + ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, + 1.0); + ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 0.0); + ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, 0.0); message_latch->Signal(); }; @@ -100,15 +97,13 @@ TEST_F(ShellTest, PlatformConfigurationWindowMetricsUpdate) { PlatformConfiguration* configuration = UIDartState::Current()->platform_configuration(); - ASSERT_NE(configuration->get_window(0), nullptr); - configuration->get_window(0)->UpdateWindowMetrics( + ASSERT_NE(configuration->window(), nullptr); + configuration->window()->UpdateWindowMetrics( ViewportMetrics{2.0, 10.0, 20.0}); - ASSERT_EQ( - configuration->get_window(0)->viewport_metrics().device_pixel_ratio, - 2.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_width, - 10.0); - ASSERT_EQ(configuration->get_window(0)->viewport_metrics().physical_height, + ASSERT_EQ(configuration->window()->viewport_metrics().device_pixel_ratio, + 2.0); + ASSERT_EQ(configuration->window()->viewport_metrics().physical_width, 10.0); + ASSERT_EQ(configuration->window()->viewport_metrics().physical_height, 20.0); message_latch->Signal(); diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index 082df1b823d32..1779998945554 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -11,8 +11,7 @@ namespace flutter { -Window::Window(int64_t window_id, ViewportMetrics metrics) - : window_id_(window_id), viewport_metrics_(metrics) { +Window::Window(ViewportMetrics metrics) : viewport_metrics_(metrics) { library_.Set(tonic::DartState::Current(), Dart_LookupLibrary(tonic::ToDart("dart:ui"))); } @@ -47,7 +46,6 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { tonic::LogIfError(tonic::DartInvokeField( library_.value(), "_updateWindowMetrics", { - tonic::ToDart(window_id_), tonic::ToDart(metrics.device_pixel_ratio), tonic::ToDart(metrics.physical_width), tonic::ToDart(metrics.physical_height), diff --git a/lib/ui/window/window.h b/lib/ui/window/window.h index b6fa2555b04d7..172cf6b8c2ae5 100644 --- a/lib/ui/window/window.h +++ b/lib/ui/window/window.h @@ -18,12 +18,10 @@ namespace flutter { class Window final { public: - Window(int64_t window_id, ViewportMetrics metrics); + explicit Window(ViewportMetrics metrics); ~Window(); - int window_id() const { return window_id_; } - const ViewportMetrics& viewport_metrics() const { return viewport_metrics_; } void DispatchPointerDataPacket(const PointerDataPacket& packet); @@ -31,7 +29,6 @@ class Window final { private: tonic::DartPersistentValue library_; - int64_t window_id_; ViewportMetrics viewport_metrics_; }; diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 5eb19ad5e70f6..d8b372ba5387a 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -99,7 +99,6 @@ part 'engine/keyboard.dart'; part 'engine/mouse_cursor.dart'; part 'engine/onscreen_logging.dart'; part 'engine/picture.dart'; -part 'engine/platform_dispatcher.dart'; part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; @@ -225,17 +224,17 @@ void initializeEngine() { // part of the rasterization process, particularly in the HTML // renderer, takes place in the `SceneBuilder.build()`. _frameTimingsOnBuildStart(); - if (EnginePlatformDispatcher.instance._onBeginFrame != null) { - EnginePlatformDispatcher.instance.invokeOnBeginFrame( + if (window._onBeginFrame != null) { + window.invokeOnBeginFrame( Duration(microseconds: highResTimeMicroseconds)); } - if (EnginePlatformDispatcher.instance._onDrawFrame != null) { + if (window._onDrawFrame != null) { // TODO(yjbanov): technically Flutter flushes microtasks between // onBeginFrame and onDrawFrame. We don't, which hasn't // been an issue yet, but eventually we'll have to // implement it properly. - EnginePlatformDispatcher.instance.invokeOnDrawFrame(); + window.invokeOnDrawFrame(); } }); } diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 96f0c304950f8..0f054559b18a0 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -71,7 +71,7 @@ class BitmapCanvas extends EngineCanvas { /// Keeps track of what device pixel ratio was used when this [BitmapCanvas] /// was created. - final double _devicePixelRatio = EnginePlatformDispatcher.browserDevicePixelRatio; + final double _devicePixelRatio = EngineWindow.browserDevicePixelRatio; // Compensation for [_initializeViewport] snapping canvas position to 1 pixel. int? _canvasPositionX, _canvasPositionY; @@ -150,13 +150,13 @@ class BitmapCanvas extends EngineCanvas { static int _widthToPhysical(double width) { final double boundsWidth = width + 1; - return (boundsWidth * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + + return (boundsWidth * EngineWindow.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } static int _heightToPhysical(double height) { final double boundsHeight = height + 1; - return (boundsHeight * EnginePlatformDispatcher.browserDevicePixelRatio).ceil() + + return (boundsHeight * EngineWindow.browserDevicePixelRatio).ceil() + 2 * kPaddingPixels; } @@ -198,7 +198,7 @@ class BitmapCanvas extends EngineCanvas { /// * [PersistedPicture._recycleCanvas] which also uses this method /// for the same reason. bool isReusable() { - return _devicePixelRatio == EnginePlatformDispatcher.browserDevicePixelRatio; + return _devicePixelRatio == EngineWindow.browserDevicePixelRatio; } /// Returns a "data://" URI containing a representation of the image in this diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index b0c3a7d2f3388..d56351124eed6 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -99,9 +99,9 @@ class _CanvasPool extends _SaveStackTracking { // * To make sure that when we scale the canvas by devicePixelRatio (see // _initializeViewport below) the pixels line up. final double cssWidth = - _widthInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; + _widthInBitmapPixels / EngineWindow.browserDevicePixelRatio; final double cssHeight = - _heightInBitmapPixels / EnginePlatformDispatcher.browserDevicePixelRatio; + _heightInBitmapPixels / EngineWindow.browserDevicePixelRatio; canvas = html.CanvasElement( width: _widthInBitmapPixels, height: _heightInBitmapPixels, @@ -194,7 +194,7 @@ class _CanvasPool extends _SaveStackTracking { clipTimeTransform[5] != prevTransform[5] || clipTimeTransform[12] != prevTransform[12] || clipTimeTransform[13] != prevTransform[13]) { - final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; + final double ratio = EngineWindow.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform( clipTimeTransform[0], @@ -223,7 +223,7 @@ class _CanvasPool extends _SaveStackTracking { transform[5] != prevTransform[5] || transform[12] != prevTransform[12] || transform[13] != prevTransform[13]) { - final double ratio = EnginePlatformDispatcher.browserDevicePixelRatio; + final double ratio = EngineWindow.browserDevicePixelRatio; ctx.setTransform(ratio, 0, 0, ratio, 0, 0); ctx.transform(transform[0], transform[1], transform[4], transform[5], transform[12], transform[13]); @@ -307,8 +307,8 @@ class _CanvasPool extends _SaveStackTracking { // This scale makes sure that 1 CSS pixel is translated to the correct // number of bitmap pixels. - ctx.scale(EnginePlatformDispatcher.browserDevicePixelRatio, - EnginePlatformDispatcher.browserDevicePixelRatio); + ctx.scale(EngineWindow.browserDevicePixelRatio, + EngineWindow.browserDevicePixelRatio); } void resetTransform() { diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index a7c8c2f6dfe2f..5f8c4a9b8e73c 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -292,7 +292,7 @@ class HtmlViewEmbedder { // // HTML elements use logical (CSS) pixels, but we have been using physical // pixels, so scale down the head element to match the logical resolution. - final double scale = EnginePlatformDispatcher.browserDevicePixelRatio; + final double scale = EngineWindow.browserDevicePixelRatio; final double inverseScale = 1 / scale; final Matrix4 scaleMatrix = Matrix4.diagonal3Values(inverseScale, inverseScale, 1); diff --git a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart index 74f013ed0ebbb..eaca5c43ca4b9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart @@ -348,7 +348,7 @@ class SkiaObjects { if (_addedCleanupCallback) { return; } - EnginePlatformDispatcher.instance.rasterizer!.addPostFrameCallback(postFrameCleanUp); + window.rasterizer!.addPostFrameCallback(postFrameCleanUp); _addedCleanupCallback = true; } diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 0a35dc3a11262..415ea66c86190 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -466,7 +466,7 @@ flt-glass-pane * { } _localeSubscription = languageChangeEvent.forTarget(html.window) .listen(_languageDidChange); - EnginePlatformDispatcher.instance._updateLocales(); + window._updateLocales(); } /// Called immediately after browser window metrics change. @@ -481,18 +481,18 @@ flt-glass-pane * { void _metricsDidChange(html.Event? event) { if(isMobile && !window.isRotation() && textEditing.isEditing) { window.computeOnScreenKeyboardInsets(); - EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); + window.invokeOnMetricsChanged(); } else { window._computePhysicalSize(); // When physical size changes this value has to be recalculated. window.computeOnScreenKeyboardInsets(); - EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); + window.invokeOnMetricsChanged(); } } /// Called immediately after browser window language change. void _languageDidChange(html.Event event) { - EnginePlatformDispatcher.instance._updateLocales(); + window._updateLocales(); if (ui.window.onLocaleChanged != null) { ui.window.onLocaleChanged!(); } diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 3c2cdc86cba7e..0720dd4a2a864 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -668,8 +668,8 @@ class _OffscreenCanvas { height: heightInPixels, ); _glCanvas!.className = 'gl-canvas'; - final double cssWidth = widthInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; - final double cssHeight = heightInPixels / EnginePlatformDispatcher.browserDevicePixelRatio; + final double cssWidth = widthInPixels / EngineWindow.browserDevicePixelRatio; + final double cssHeight = heightInPixels / EngineWindow.browserDevicePixelRatio; _glCanvas!.style ..position = 'absolute' ..width = '${cssWidth}px' diff --git a/lib/web_ui/lib/src/engine/html/surface_stats.dart b/lib/web_ui/lib/src/engine/html/surface_stats.dart index 6e524a4f6e266..911a825ad03ab 100644 --- a/lib/web_ui/lib/src/engine/html/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/html/surface_stats.dart @@ -125,9 +125,9 @@ void _debugRepaintSurfaceStatsOverlay(PersistedScene scene) { ..fill(); final double physicalScreenWidth = - html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; + html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; + html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; @@ -296,9 +296,9 @@ void _debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { return pixels; }).fold(0, (int total, int pixels) => total + pixels); final double physicalScreenWidth = - html.window.innerWidth! * EnginePlatformDispatcher.browserDevicePixelRatio; + html.window.innerWidth! * EngineWindow.browserDevicePixelRatio; final double physicalScreenHeight = - html.window.innerHeight! * EnginePlatformDispatcher.browserDevicePixelRatio; + html.window.innerHeight! * EngineWindow.browserDevicePixelRatio; final double physicsScreenPixelCount = physicalScreenWidth * physicalScreenHeight; final double screenPixelRatio = pixelCount / physicsScreenPixelCount; diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index ad9e11204dc43..558e53f6f927a 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -81,7 +81,7 @@ class Keyboard { final html.KeyboardEvent keyboardEvent = event; - if (EnginePlatformDispatcher.instance._onPlatformMessage == null) { + if (window._onPlatformMessage == null) { return; } @@ -135,7 +135,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', + window.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } @@ -157,7 +157,7 @@ class Keyboard { 'metaState': _lastMetaState, }; - EnginePlatformDispatcher.instance.invokeOnPlatformMessage('flutter/keyevent', + window.invokeOnPlatformMessage('flutter/keyevent', _messageCodec.encodeMessage(eventData), _noopCallback); } } diff --git a/lib/web_ui/lib/src/engine/navigation/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart index 5cfc6c6c0af8f..0a578162a9096 100644 --- a/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -151,8 +151,8 @@ class MultiEntriesBrowserHistory extends BrowserHistory { currentPath); } _lastSeenSerialCount = _currentSerialCount; - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRouteInformation', { @@ -272,8 +272,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _setupFlutterEntry(urlStrategy!); // 2. Send a 'popRoute' platform message so the app can handle it accordingly. - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(_popRouteMethodCall), (_) {}, @@ -291,8 +291,8 @@ class SingleEntryBrowserHistory extends BrowserHistory { _userProvidedRouteName = null; // Send a 'pushRoute' platform message so the app handles it accordingly. - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/navigation', const JSONMethodCodec().encodeMethodCall( MethodCall('pushRoute', newRouteName), diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart deleted file mode 100644 index d1e72189df398..0000000000000 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ /dev/null @@ -1,923 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of engine; - -/// Requests that the browser schedule a frame. -/// -/// This may be overridden in tests, for example, to pump fake frames. -ui.VoidCallback? scheduleFrameCallback; - -/// Platform event dispatcher. -/// -/// This is the central entry point for platform messages and configuration -/// events from the platform. -class EnginePlatformDispatcher extends ui.PlatformDispatcher { - /// Private constructor, since only dart:ui is supposed to create one of - /// these. - EnginePlatformDispatcher._() { - _addBrightnessMediaQueryListener(); - } - - /// The [EnginePlatformDispatcher] singleton. - static EnginePlatformDispatcher get instance => _instance; - static final EnginePlatformDispatcher _instance = EnginePlatformDispatcher._(); - - /// The current platform configuration. - @override - ui.PlatformConfiguration get configuration => _configuration; - ui.PlatformConfiguration _configuration = ui.PlatformConfiguration(locales: parseBrowserLanguages()); - - /// Receives all events related to platform configuration changes. - @override - ui.VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged; - ui.VoidCallback? _onPlatformConfigurationChanged; - Zone? _onPlatformConfigurationChangedZone; - @override - set onPlatformConfigurationChanged(ui.VoidCallback? callback) { - _onPlatformConfigurationChanged = callback; - _onPlatformConfigurationChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformConfigurationChanged() { - _invoke(_onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone); - } - - /// The current list of windows, - Iterable get views => _windows.values; - Map _windows = {}; - - /// A map of opaque platform window identifiers to window configurations. - /// - /// This should be considered a protected member, only to be used by - /// [PlatformDispatcher] subclasses. - Map _windowConfigurations = {}; - - /// A callback that is invoked whenever the platform's [devicePixelRatio], - /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] - /// values change, for example when the device is rotated or when the - /// application is resized (e.g. when showing applications side-by-side - /// on Android). - /// - /// The engine invokes this callback in the same zone in which the callback - /// was set. - /// - /// The framework registers with this callback and updates the layout - /// appropriately. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// register for notifications when this is called. - /// * [MediaQuery.of], a simpler mechanism for the same. - @override - ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; - ui.VoidCallback? _onMetricsChanged; - Zone? _onMetricsChangedZone; - @override - set onMetricsChanged(ui.VoidCallback? callback) { - _onMetricsChanged = callback; - _onMetricsChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnMetricsChanged() { - if (_onMetricsChanged != null) { - _invoke(_onMetricsChanged, _onMetricsChangedZone); - } - } - - /// Returns device pixel ratio returned by browser. - static double get browserDevicePixelRatio { - double? ratio = html.window.devicePixelRatio as double?; - // Guard against WebOS returning 0 and other browsers returning null. - return (ratio == null || ratio == 0.0) ? 1.0 : ratio; - } - - /// A callback invoked when any window begins a frame. - /// - /// {@template flutter.foundation.PlatformDispatcher.onBeginFrame} - /// A callback that is invoked to notify the application that it is an - /// appropriate time to provide a scene using the [SceneBuilder] API and the - /// [PlatformWindow.render] method. - /// When possible, this is driven by the hardware VSync signal of the attached - /// screen with the highest VSync rate. This is only called if - /// [PlatformWindow.scheduleFrame] has been called since the last time this - /// callback was invoked. - /// {@endtemplate} - @override - ui.FrameCallback? get onBeginFrame => _onBeginFrame; - ui.FrameCallback? _onBeginFrame; - Zone? _onBeginFrameZone; - @override - set onBeginFrame(ui.FrameCallback? callback) { - _onBeginFrame = callback; - _onBeginFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnBeginFrame(Duration duration) { - _invoke1(_onBeginFrame, _onBeginFrameZone, duration); - } - - /// {@template flutter.foundation.PlatformDispatcher.onDrawFrame} - /// A callback that is invoked for each frame after [onBeginFrame] has - /// completed and after the microtask queue has been drained. - /// - /// This can be used to implement a second phase of frame rendering that - /// happens after any deferred work queued by the [onBeginFrame] phase. - /// {@endtemplate} - @override - ui.VoidCallback? get onDrawFrame => _onDrawFrame; - ui.VoidCallback? _onDrawFrame; - Zone? _onDrawFrameZone; - @override - set onDrawFrame(ui.VoidCallback? callback) { - _onDrawFrame = callback; - _onDrawFrameZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnDrawFrame() { - _invoke(_onDrawFrame, _onDrawFrameZone); - } - - /// A callback that is invoked when pointer data is available. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - /// - /// See also: - /// - /// * [GestureBinding], the Flutter framework class which manages pointer - /// events. - @override - ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; - ui.PointerDataPacketCallback? _onPointerDataPacket; - Zone? _onPointerDataPacketZone; - @override - set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { - _onPointerDataPacket = callback; - _onPointerDataPacketZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPointerDataPacket(ui.PointerDataPacket dataPacket) { - _invoke1(_onPointerDataPacket, _onPointerDataPacketZone, dataPacket); - } - - /// A callback that is invoked to report the [FrameTiming] of recently - /// rasterized frames. - /// - /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use - /// [Window.onReportTimings] directly because - /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. - /// - /// This can be used to see if the application has missed frames (through - /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high - /// latencies (through [FrameTiming.totalSpan]). - /// - /// Unlike [Timeline], the timing information here is available in the release - /// mode (additional to the profile and the debug mode). Hence this can be - /// used to monitor the application's performance in the wild. - /// - /// {@macro dart.ui.TimingsCallback.list} - /// - /// If this is null, no additional work will be done. If this is not null, - /// Flutter spends less than 0.1ms every 1 second to report the timings - /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for - /// 60fps), or 0.01% CPU usage per second. - @override - ui.TimingsCallback? get onReportTimings => _onReportTimings; - ui.TimingsCallback? _onReportTimings; - Zone? _onReportTimingsZone; - @override - set onReportTimings(ui.TimingsCallback? callback) { - _onReportTimings = callback; - _onReportTimingsZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnReportTimings(List timings) { - _invoke1>(_onReportTimings, _onReportTimingsZone, timings); - } - - @override - void sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - _sendPlatformMessage(name, data, _zonedPlatformMessageResponseCallback(callback)); - } - - @override - ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; - ui.PlatformMessageCallback? _onPlatformMessage; - Zone? _onPlatformMessageZone; - @override - set onPlatformMessage(ui.PlatformMessageCallback? callback) { - _onPlatformMessage = callback; - _onPlatformMessageZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformMessage(String name, ByteData? data, - ui.PlatformMessageResponseCallback callback) { - _invoke3( - _onPlatformMessage, - _onPlatformMessageZone, - name, - data, - callback, - ); - } - - /// Wraps the given [callback] in another callback that ensures that the - /// original callback is called in the zone it was registered in. - static ui.PlatformMessageResponseCallback? - _zonedPlatformMessageResponseCallback( - ui.PlatformMessageResponseCallback? callback) { - if (callback == null) - return null; - - // Store the zone in which the callback is being registered. - final Zone registrationZone = Zone.current; - - return (ByteData? data) { - registrationZone.runUnaryGuarded(callback, data); - }; - } - - void _sendPlatformMessage( - String name, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - // In widget tests we want to bypass processing of platform messages. - if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { - return; - } - - if (_debugPrintPlatformMessages) { - print('Sent platform message on channel: "$name"'); - } - - if (assertionsEnabled && name == 'flutter/debug-echo') { - // Echoes back the data unchanged. Used for testing purposes. - _replyToPlatformMessage(callback, data); - return; - } - - switch (name) { - /// This should be in sync with shell/common/shell.cc - case 'flutter/skia': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'Skia.setResourceCacheMaxBytes': - if (decoded.arguments is int) { - rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); - } - break; - } - return; - - case 'flutter/assets': - assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison - final String url = utf8.decode(data!.buffer.asUint8List()); - ui.webOnlyAssetManager.load(url).then((ByteData assetData) { - _replyToPlatformMessage(callback, assetData); - }, onError: (dynamic error) { - html.window.console - .warn('Error while trying to load an asset: $error'); - _replyToPlatformMessage(callback, null); - }); - return; - - case 'flutter/platform': - const MethodCodec codec = JSONMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - switch (decoded.method) { - case 'SystemNavigator.pop': - // TODO(gspencergoog): As multi-window support expands, the pop call - // will need to include the window ID. Right now only one window is - // supported. - (_windows[0] as EngineFlutterWindow).browserHistory.exit().then((_) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(true)); - }); - return; - case 'HapticFeedback.vibrate': - final String type = decoded.arguments; - domRenderer.vibrate(_getHapticFeedbackDuration(type)); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setApplicationSwitcherDescription': - final Map arguments = decoded.arguments; - domRenderer.setTitle(arguments['label']); - domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return; - case 'SystemChrome.setPreferredOrientations': - final List arguments = decoded.arguments; - domRenderer.setPreferredOrientation(arguments).then((bool success) { - _replyToPlatformMessage( - callback, codec.encodeSuccessEnvelope(success)); - }); - return; - case 'SystemSound.play': - // There are no default system sounds on web. - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - return; - case 'Clipboard.setData': - ClipboardMessageHandler().setDataMethodCall(decoded, callback); - return; - case 'Clipboard.getData': - ClipboardMessageHandler().getDataMethodCall(callback); - return; - } - break; - - // Dispatched by the bindings to delay service worker initialization. - case 'flutter/service_worker': - html.window.dispatchEvent(html.Event('flutter-first-frame')); - return; - - case 'flutter/textinput': - textEditing.channel.handleTextInput(data, callback); - return; - - case 'flutter/mousecursor': - const MethodCodec codec = StandardMethodCodec(); - final MethodCall decoded = codec.decodeMethodCall(data); - final Map arguments = decoded.arguments; - switch (decoded.method) { - case 'activateSystemCursor': - MouseCursor.instance!.activateSystemCursor(arguments['kind']); - } - return; - - case 'flutter/web_test_e2e': - const MethodCodec codec = JSONMethodCodec(); - _replyToPlatformMessage( - callback, - codec.encodeSuccessEnvelope( - _handleWebTestEnd2EndMessage(codec, data))); - return; - - case 'flutter/platform_views': - if (experimentalUseSkia) { - rasterizer!.surface.viewEmbedder - .handlePlatformViewCall(data, callback); - } else { - ui.handlePlatformViewCall(data!, callback!); - } - return; - - case 'flutter/accessibility': - // In widget tests we want to bypass processing of platform messages. - final StandardMessageCodec codec = StandardMessageCodec(); - accessibilityAnnouncements.handleMessage(codec, data); - _replyToPlatformMessage(callback, codec.encodeMessage(true)); - return; - - case 'flutter/navigation': - // TODO(gspencergoog): As multi-window support expands, the navigation call - // will need to include the window ID. Right now only one window is - // supported. - (_windows[0] as EngineFlutterWindow).handleNavigationMessage(data).then((bool handled) { - if (handled) { - const MethodCodec codec = JSONMethodCodec(); - _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); - } else { - callback?.call(null); - } - }); - - // As soon as Flutter starts taking control of the app navigation, we - // should reset _defaultRouteName to "/" so it doesn't have any - // further effect after this point. - _defaultRouteName = '/'; - return; - } - - if (pluginMessageCallHandler != null) { - pluginMessageCallHandler!(name, data, callback); - return; - } - - // Passing [null] to [callback] indicates that the platform message isn't - // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is - // handled. - _replyToPlatformMessage(callback, null); - } - - - - int _getHapticFeedbackDuration(String type) { - switch (type) { - case 'HapticFeedbackType.lightImpact': - return DomRenderer.vibrateLightImpact; - case 'HapticFeedbackType.mediumImpact': - return DomRenderer.vibrateMediumImpact; - case 'HapticFeedbackType.heavyImpact': - return DomRenderer.vibrateHeavyImpact; - case 'HapticFeedbackType.selectionClick': - return DomRenderer.vibrateSelectionClick; - default: - return DomRenderer.vibrateLongPress; - } - } - - /// Requests that, at the next appropriate opportunity, the [onBeginFrame] - /// and [onDrawFrame] callbacks be invoked. - /// - /// See also: - /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - @override - void scheduleFrame() { - if (scheduleFrameCallback == null) { - throw new Exception( - 'scheduleFrameCallback must be initialized first.'); - } - scheduleFrameCallback!(); - } - - /// Updates the application's rendering on the GPU with the newly provided - /// [Scene]. This function must be called within the scope of the - /// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function - /// is called a second time during a single [onBeginFrame]/[onDrawFrame] - /// callback sequence or called outside the scope of those callbacks, the call - /// will be ignored. - /// - /// To record graphical operations, first create a [PictureRecorder], then - /// construct a [Canvas], passing that [PictureRecorder] to its constructor. - /// After issuing all the graphical operations, call the - /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain - /// the final [Picture] that represents the issued graphical operations. - /// - /// Next, create a [SceneBuilder], and add the [Picture] to it using - /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can - /// then obtain a [Scene] object, which you can display to the user via this - /// [render] function. - /// - /// See also: - /// - /// * [SchedulerBinding], the Flutter framework class which manages the - /// scheduling of frames. - /// * [RendererBinding], the Flutter framework class which manages layout and - /// painting. - @override - void render(ui.Scene scene, [ui.FlutterView? view]) { - if (experimentalUseSkia) { - // "Build finish" and "raster start" happen back-to-back because we - // render on the same thread, so there's no overhead from hopping to - // another thread. - // - // CanvasKit works differently from the HTML renderer in that in HTML - // we update the DOM in SceneBuilder.build, which is these function calls - // here are CanvasKit-only. - _frameTimingsOnBuildFinish(); - _frameTimingsOnRasterStart(); - - final LayerScene layerScene = scene as LayerScene; - rasterizer!.draw(layerScene.layerTree); - } else { - final SurfaceScene surfaceScene = scene as SurfaceScene; - domRenderer.renderScene(surfaceScene.webOnlyRootElement); - } - _frameTimingsOnRasterFinish(); - } - - /// Additional accessibility features that may be enabled by the platform. - ui.AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures; - - /// A callback that is invoked when the value of [accessibilityFeatures] changes. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - ui.VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged; - ui.VoidCallback? _onAccessibilityFeaturesChanged; - Zone? _onAccessibilityFeaturesChangedZone; - set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { - _onAccessibilityFeaturesChanged = callback; - _onAccessibilityFeaturesChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnAccessibilityFeaturesChanged() { - _invoke(_onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); - } - - /// Change the retained semantics data about this window. - /// - /// If [semanticsEnabled] is true, the user has requested that this function - /// be called whenever the semantic content of this window changes. - /// - /// In either case, this function disposes the given update, which means the - /// semantics update cannot be used further. - void updateSemantics(ui.SemanticsUpdate update) { - EngineSemanticsOwner.instance.updateSemantics(update); - } - - /// We use the first locale in the [locales] list instead of the browser's - /// built-in `navigator.language` because browsers do not agree on the - /// implementation. - /// - /// See also: - /// - /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, - /// which explains browser quirks in the implementation notes. - ui.Locale get locale => locales.first; - - /// The full system-reported supported locales of the device. - /// - /// This establishes the language and formatting conventions that application - /// should, if possible, use to render their user interface. - /// - /// The list is ordered in order of priority, with lower-indexed locales being - /// preferred over higher-indexed ones. The first element is the primary [locale]. - /// - /// The [onLocaleChanged] callback is called whenever this value changes. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - List get locales => configuration.locales; - - /// Performs the platform-native locale resolution. - /// - /// Each platform may return different results. - /// - /// If the platform fails to resolve a locale, then this will return null. - /// - /// This method returns synchronously and is a direct call to - /// platform specific APIs without invoking method channels. - ui.Locale? computePlatformResolvedLocale(List supportedLocales) { - // TODO(garyq): Implement on web. - return null; - } - - /// A callback that is invoked whenever [locale] changes value. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; - ui.VoidCallback? _onLocaleChanged; - Zone? _onLocaleChangedZone; - set onLocaleChanged(ui.VoidCallback? callback) { - _onLocaleChanged = callback; - _onLocaleChangedZone = Zone.current; - } - - /// The locale used when we fail to get the list from the browser. - static const ui.Locale _defaultLocale = const ui.Locale('en', 'US'); - - /// Sets locales to an empty list. - /// - /// The empty list is not a valid value for locales. This is only used for - /// testing locale update logic. - void debugResetLocales() { - _configuration = _configuration.copyWith(locales: const []); - } - - // Called by DomRenderer when browser languages change. - void _updateLocales() { - _configuration = _configuration.copyWith(locales: parseBrowserLanguages()); - } - - static List parseBrowserLanguages() { - // TODO(yjbanov): find a solution for IE - var languages = html.window.navigator.languages; - if (languages == null || languages.isEmpty) { - // To make it easier for the app code, let's not leave the locales list - // empty. This way there's fewer corner cases for apps to handle. - return const [_defaultLocale]; - } - - final List locales = []; - for (final String language in languages) { - final List parts = language.split('-'); - if (parts.length > 1) { - locales.add(ui.Locale(parts.first, parts.last)); - } else { - locales.add(ui.Locale(language)); - } - } - - assert(locales.isNotEmpty); - return locales; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnLocaleChanged() { - _invoke(_onLocaleChanged, _onLocaleChangedZone); - } - - /// The system-reported text scale. - /// - /// This establishes the text scaling factor to use when rendering text, - /// according to the user's platform preferences. - /// - /// The [onTextScaleFactorChanged] callback is called whenever this value - /// changes. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this value changes. - double get textScaleFactor => configuration.textScaleFactor; - - /// The setting indicating whether time should always be shown in the 24-hour - /// format. - /// - /// This option is used by [showTimePicker]. - bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; - - /// A callback that is invoked whenever [textScaleFactor] changes value. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; - ui.VoidCallback? _onTextScaleFactorChanged; - Zone? _onTextScaleFactorChangedZone; - set onTextScaleFactorChanged(ui.VoidCallback? callback) { - _onTextScaleFactorChanged = callback; - _onTextScaleFactorChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnTextScaleFactorChanged() { - _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); - } - - /// The setting indicating the current brightness mode of the host platform. - /// If the platform has no preference, [platformBrightness] defaults to [Brightness.light]. - ui.Brightness get platformBrightness => configuration.platformBrightness; - - /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] - /// callback if [_platformBrightness] changed. - void _updatePlatformBrightness(ui.Brightness value) { - if (configuration.platformBrightness != value) { - _configuration = configuration.copyWith(platformBrightness: value); - invokeOnPlatformConfigurationChanged(); - invokeOnPlatformBrightnessChanged(); - } - } - - /// Reference to css media query that indicates the user theme preference on the web. - final html.MediaQueryList _brightnessMediaQuery = - html.window.matchMedia('(prefers-color-scheme: dark)'); - - /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. - /// - /// Updates the [_platformBrightness] with the new user preference. - html.EventListener? _brightnessMediaQueryListener; - - /// Set the callback function for listening changes in [_brightnessMediaQuery] value. - void _addBrightnessMediaQueryListener() { - _updatePlatformBrightness(_brightnessMediaQuery.matches - ? ui.Brightness.dark - : ui.Brightness.light); - - _brightnessMediaQueryListener = (html.Event event) { - final html.MediaQueryListEvent mqEvent = - event as html.MediaQueryListEvent; - _updatePlatformBrightness( - mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); - }; - _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); - registerHotRestartListener(() { - _removeBrightnessMediaQueryListener(); - }); - } - - /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. - void _removeBrightnessMediaQueryListener() { - _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); - _brightnessMediaQueryListener = null; - } - - /// A callback that is invoked whenever [platformBrightness] changes value. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - /// - /// See also: - /// - /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to - /// observe when this callback is invoked. - ui.VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged; - ui.VoidCallback? _onPlatformBrightnessChanged; - Zone? _onPlatformBrightnessChangedZone; - set onPlatformBrightnessChanged(ui.VoidCallback? callback) { - _onPlatformBrightnessChanged = callback; - _onPlatformBrightnessChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnPlatformBrightnessChanged() { - _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); - } - - /// Whether the user has requested that [updateSemantics] be called when - /// the semantic contents of window changes. - /// - /// The [onSemanticsEnabledChanged] callback is called whenever this value - /// changes. - bool get semanticsEnabled => configuration.semanticsEnabled; - - /// A callback that is invoked when the value of [semanticsEnabled] changes. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; - ui.VoidCallback? _onSemanticsEnabledChanged; - Zone? _onSemanticsEnabledChangedZone; - set onSemanticsEnabledChanged(ui.VoidCallback? callback) { - _onSemanticsEnabledChanged = callback; - _onSemanticsEnabledChangedZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsEnabledChanged() { - _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); - } - - /// A callback that is invoked whenever the user requests an action to be - /// performed. - /// - /// This callback is used when the user expresses the action they wish to - /// perform based on the semantics supplied by [updateSemantics]. - /// - /// The framework invokes this callback in the same zone in which the - /// callback was set. - ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; - ui.SemanticsActionCallback? _onSemanticsAction; - Zone? _onSemanticsActionZone; - set onSemanticsAction(ui.SemanticsActionCallback? callback) { - _onSemanticsAction = callback; - _onSemanticsActionZone = Zone.current; - } - - /// Engine code should use this method instead of the callback directly. - /// Otherwise zones won't work properly. - void invokeOnSemanticsAction( - int id, ui.SemanticsAction action, ByteData? args) { - _invoke3( - _onSemanticsAction, _onSemanticsActionZone, id, action, args); - } - - /// The route or path that the embedder requested when the application was - /// launched. - /// - /// This will be the string "`/`" if no particular route was requested. - /// - /// ## Android - /// - /// On Android, calling - /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-) - /// will set this value. The value must be set sufficiently early, i.e. before - /// the [runApp] call is executed in Dart, for this to have any effect on the - /// framework. The `createFlutterView` method in your `FlutterActivity` - /// subclass is a suitable time to set the value. The application's - /// `AndroidManifest.xml` file must also be updated to have a suitable - /// [``](https://developer.android.com/guide/topics/manifest/intent-filter-element.html). - /// - /// ## iOS - /// - /// On iOS, calling - /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:) - /// will set this value. The value must be set sufficiently early, i.e. before - /// the [runApp] call is executed in Dart, for this to have any effect on the - /// framework. The `application:didFinishLaunchingWithOptions:` method is a - /// suitable time to set this value. - /// - /// See also: - /// - /// * [Navigator], a widget that handles routing. - /// * [SystemChannels.navigation], which handles subsequent navigation - /// requests from the embedder. - String get defaultRouteName { - return _defaultRouteName ??= (_windows[0]! as EngineFlutterWindow).browserHistory.currentPath; - } - - /// Lazily initialized when the `defaultRouteName` getter is invoked. - /// - /// The reason for the lazy initialization is to give enough time for the app - /// to set [locationStrategy] in `lib/src/ui/initialization.dart`. - String? _defaultRouteName; - - @visibleForTesting - late Rasterizer? rasterizer = - experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; - - /// In Flutter, platform messages are exchanged between threads so the - /// messages and responses have to be exchanged asynchronously. We simulate - /// that by adding a zero-length delay to the reply. - void _replyToPlatformMessage( - ui.PlatformMessageResponseCallback? callback, - ByteData? data, - ) { - Future.delayed(Duration.zero).then((_) { - if (callback != null) { - callback(data); - } - }); - } -} - -bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { - final MethodCall decoded = codec.decodeMethodCall(data); - double ratio = double.parse(decoded.arguments); - switch (decoded.method) { - case 'setDevicePixelRatio': - window.debugOverrideDevicePixelRatio(ratio); - EnginePlatformDispatcher.instance.onMetricsChanged!(); - return true; - } - return false; -} - -/// Invokes [callback] inside the given [zone]. -void _invoke(void callback()?, Zone? zone) { - if (callback == null) { - return; - } - - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(); - } else { - zone!.runGuarded(callback); - } -} - -/// Invokes [callback] inside the given [zone] passing it [arg]. -void _invoke1(void callback(A a)?, Zone? zone, A arg) { - if (callback == null) { - return; - } - - assert(zone != null); - - if (identical(zone, Zone.current)) { - callback(arg); - } else { - zone!.runUnaryGuarded(callback, arg); - } -} - -/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. -void _invoke3( - void callback(A1 a1, A2 a2, A3 a3)?, - Zone? zone, - A1 arg1, - A2 arg2, - A3 arg3, - ) { - if (callback == null) { - return; - } - - assert(zone != null); - - if (identical(zone!, Zone.current)) { - callback(arg1, arg2, arg3); - } else { - zone.runGuarded(() { - callback(arg1, arg2, arg3); - }); - } -} - diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index ef0eb4667d779..f6bb53110491b 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -125,7 +125,9 @@ class PointerBinding { void _onPointerData(Iterable data) { final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList()); - EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(packet); + if (window._onPointerDataPacket != null) { + window.invokeOnPointerDataPacket(packet); + } } } diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart index eeeab3d576d1c..312938dbc57ef 100644 --- a/lib/web_ui/lib/src/engine/profiler.dart +++ b/lib/web_ui/lib/src/engine/profiler.dart @@ -110,7 +110,7 @@ class Profiler { /// Whether we are collecting [ui.FrameTiming]s. bool get _frameTimingsEnabled { - return EnginePlatformDispatcher.instance._onReportTimings != null; + return window._onReportTimings != null; } /// Collects frame timings from frames. @@ -202,7 +202,7 @@ void _frameTimingsOnRasterFinish() { _rasterFinishMicros = -1; if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) { _frameTimingsLastSubmitTime = now; - EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings); + window.invokeOnReportTimings(_frameTimings); _frameTimings = []; } } diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index 616663a25d9ac..77fbb6daf5e47 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -53,11 +53,11 @@ class Incrementable extends RoleManager { final int newInputValue = int.parse(_element.value!); if (newInputValue > _currentSurrogateValue) { _currentSurrogateValue += 1; - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.increase, null); } else if (newInputValue < _currentSurrogateValue) { _currentSurrogateValue -= 1; - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.decrease, null); } }); diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 9cae3a9189c11..50624ddd4ba09 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -53,20 +53,20 @@ class Scrollable extends RoleManager { final int semanticsId = semanticsObject.id; if (doScrollForward) { if (semanticsObject.isVerticalScrollContainer) { - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollUp, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollLeft, null); } } else { if (semanticsObject.isVerticalScrollContainer) { - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollDown, null); } else { assert(semanticsObject.isHorizontalScrollContainer); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsId, ui.SemanticsAction.scrollRight, null); } } diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index f3b626e9b0535..57c02b5bf22db 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1253,8 +1253,8 @@ class EngineSemanticsOwner { _gestureModeClock?.datetime = null; } - if (EnginePlatformDispatcher.instance._onSemanticsEnabledChanged != null) { - EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); + if (window._onSemanticsEnabledChanged != null) { + window.invokeOnSemanticsEnabledChanged(); } } diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 8439dd3f70f08..3f58195b1d46e 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -40,7 +40,7 @@ class Tappable extends RoleManager { GestureMode.browserGestures) { return; } - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); }; element.addEventListener('click', _clickListener); diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 91b5d8ba9dc5b..243a154c708b6 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -148,7 +148,7 @@ class TextField extends RoleManager { } textEditing.useCustomEditableElement(textEditingElement); - EnginePlatformDispatcher.instance + window .invokeOnSemanticsAction(semanticsObject.id, ui.SemanticsAction.tap, null); }); } @@ -186,7 +186,7 @@ class TextField extends RoleManager { if (offsetX * offsetX + offsetY * offsetY < kTouchSlop) { // Recognize it as a tap that requires a keyboard. - EnginePlatformDispatcher.instance.invokeOnSemanticsAction( + window.invokeOnSemanticsAction( semanticsObject.id, ui.SemanticsAction.tap, null); } } else { 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 baa4b5e506d77..ce8b31d5fd23a 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 @@ -276,8 +276,8 @@ class EngineAutofillForm { /// Sends the 'TextInputClient.updateEditingStateWithTag' message to the framework. void _sendAutofillEditingState(String? tag, EditingState editingState) { - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1363,7 +1363,7 @@ class TextEditingChannel { throw StateError( 'Unsupported method call on the flutter/textinput channel: ${call.method}'); } - EnginePlatformDispatcher.instance._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + window._replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); } /// Used for submitting the forms attached on the DOM. @@ -1392,8 +1392,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.updateEditingState' message to the framework. void updateEditingState(int? clientId, EditingState? editingState) { - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall('TextInputClient.updateEditingState', [ @@ -1408,8 +1408,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.performAction' message to the framework. void performAction(int? clientId, String? inputAction) { - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( @@ -1424,8 +1424,8 @@ class TextEditingChannel { /// Sends the 'TextInputClient.onConnectionClosed' message to the framework. void onConnectionClosed(int? clientId) { - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) { - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + if (window._onPlatformMessage != null) { + window.invokeOnPlatformMessage( 'flutter/textinput', const JSONMethodCodec().encodeMethodCall( MethodCall( diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 22a252c578179..277f5484e2f07 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -482,13 +482,13 @@ final ByteData? _fontChangeMessage = JSONMessageCodec().encodeMessage( sendFontChangeMessage() async { - if (EnginePlatformDispatcher.instance._onPlatformMessage != null) + if (window._onPlatformMessage != null) if (!_fontChangeScheduled) { _fontChangeScheduled = true; // Batch updates into next animationframe. html.window.requestAnimationFrame((num _) { _fontChangeScheduled = false; - EnginePlatformDispatcher.instance.invokeOnPlatformMessage( + window.invokeOnPlatformMessage( 'flutter/system', _fontChangeMessage, (_) {}, diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index ea0d5743c3701..19597f6ae32de 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -6,103 +6,56 @@ part of engine; /// When set to true, all platform messages will be printed to the console. -const bool/*!*/ _debugPrintPlatformMessages = false; +const bool _debugPrintPlatformMessages = false; -/// The Web implementation of [ui.Window]. -// TODO(gspencergoog): Once the framework no longer uses ui.Window, make this extend -// ui.SingletonFlutterWindow instead. -class EngineFlutterWindow extends ui.Window { - EngineFlutterWindow(this._windowId, this.platformDispatcher) { - final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; - engineDispatcher._windows[_windowId] = this; - engineDispatcher._windowConfigurations[_windowId] = ui.ViewConfiguration(); - _addUrlStrategyListener(); - } - - final Object _windowId; - final ui.PlatformDispatcher platformDispatcher; - - void _addUrlStrategyListener() { - _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { - assert( - _browserHistory == null, - 'Cannot set URL strategy more than once.', - ); - final UrlStrategy? strategy = - jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - }); - registerHotRestartListener(() { - _jsSetUrlStrategy = null; - }); - } +/// Requests that the browser schedule a frame. +/// +/// This may be overridden in tests, for example, to pump fake frames. +ui.VoidCallback? scheduleFrameCallback; - /// Handles the browser history integration to allow users to use the back - /// button, etc. - @visibleForTesting - BrowserHistory get browserHistory { - return _browserHistory ??= - MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); - } +typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); - BrowserHistory? _browserHistory; +/// A JavaScript hook to customize the URL strategy of a Flutter app. +// +// Keep this js name in sync with flutter_web_plugins. Find it at: +// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart +// +// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 +@JS('_flutter_web_set_location_strategy') +external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); - Future _useSingleEntryBrowserHistory() async { - if (_browserHistory is SingleEntryBrowserHistory) { - return; - } - final UrlStrategy? strategy = _browserHistory?.urlStrategy; - await _browserHistory?.tearDown(); - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } +UrlStrategy? _createDefaultUrlStrategy() { + return ui.debugEmulateFlutterTesterEnvironment + ? null + : const HashUrlStrategy(); +} - @visibleForTesting - Future debugInitializeHistory( - UrlStrategy? strategy, { - required bool useSingle, - }) async { - await _browserHistory?.tearDown(); - if (useSingle) { - _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); - } else { - _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); - } +/// The Web implementation of [ui.Window]. +class EngineWindow extends ui.Window { + EngineWindow() { + _addBrightnessMediaQueryListener(); + _addUrlStrategyListener(); } - @visibleForTesting - Future debugResetHistory() async { - await _browserHistory?.tearDown(); - _browserHistory = null; + @override + double get devicePixelRatio => + _debugDevicePixelRatio ?? browserDevicePixelRatio; + + /// Returns device pixel ratio returned by browser. + static double get browserDevicePixelRatio { + double? ratio = html.window.devicePixelRatio as double?; + // Guard against WebOS returning 0 and other browsers returning null. + return (ratio == null || ratio == 0.0) ? 1.0 : ratio; } - Future handleNavigationMessage( - ByteData? data, - ) async { - final MethodCall decoded = JSONMethodCodec().decodeMethodCall(data); - final Map arguments = decoded.arguments; - - switch (decoded.method) { - case 'routeUpdated': - await _useSingleEntryBrowserHistory(); - browserHistory.setRouteName(arguments['routeName']); - return true; - case 'routeInformationUpdated': - assert(browserHistory is MultiEntriesBrowserHistory); - browserHistory.setRouteName( - arguments['location'], - state: arguments['state'], - ); - return true; - } - return false; + /// Overrides the default device pixel ratio. + /// + /// This is useful in tests to emulate screens of different dimensions. + void debugOverrideDevicePixelRatio(double value) { + _debugDevicePixelRatio = value; } - @override - ui.ViewConfiguration get viewConfiguration { - final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; - assert(engineDispatcher._windowConfigurations.containsKey(_windowId)); - return engineDispatcher._windowConfigurations[_windowId] ?? ui.ViewConfiguration(); - } + double? _debugDevicePixelRatio; @override ui.Size get physicalSize { @@ -211,55 +164,713 @@ class EngineFlutterWindow extends ui.Window { /// Overrides the value of [physicalSize] in tests. ui.Size? webOnlyDebugPhysicalSizeOverride; -} -typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?); + /// Handles the browser history integration to allow users to use the back + /// button, etc. + @visibleForTesting + BrowserHistory get browserHistory { + return _browserHistory ??= + MultiEntriesBrowserHistory(urlStrategy: _createDefaultUrlStrategy()); + } -/// A JavaScript hook to customize the URL strategy of a Flutter app. -// -// Keep this js name in sync with flutter_web_plugins. Find it at: -// https://github.com/flutter/flutter/blob/custom_location_strategy/packages/flutter_web_plugins/lib/src/navigation/js_url_strategy.dart -// -// TODO: Add integration test https://github.com/flutter/flutter/issues/66852 -@JS('_flutter_web_set_location_strategy') -external set _jsSetUrlStrategy(_JsSetUrlStrategy? newJsSetUrlStrategy); + BrowserHistory? _browserHistory; -UrlStrategy? _createDefaultUrlStrategy() { - return ui.debugEmulateFlutterTesterEnvironment - ? null - : const HashUrlStrategy(); -} + Future _useSingleEntryBrowserHistory() async { + if (_browserHistory is SingleEntryBrowserHistory) { + return; + } + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } -/// The Web implementation of [ui.Window]. -class EngineSingletonFlutterWindow extends EngineFlutterWindow { - EngineSingletonFlutterWindow(Object windowId, ui.PlatformDispatcher platformDispatcher) : super(windowId, platformDispatcher); + /// Lazily initialized when the `defaultRouteName` getter is invoked. + /// + /// The reason for the lazy initialization is to give enough time for the app to set [urlStrategy] + /// in `lib/src/ui/initialization.dart`. + String? _defaultRouteName; @override - double get devicePixelRatio => _debugDevicePixelRatio ?? EnginePlatformDispatcher.browserDevicePixelRatio; + String get defaultRouteName { + return _defaultRouteName ??= browserHistory.currentPath; + } - /// Overrides the default device pixel ratio. + @override + void scheduleFrame() { + if (scheduleFrameCallback == null) { + throw new Exception('scheduleFrameCallback must be initialized first.'); + } + scheduleFrameCallback!(); + } + + @override + ui.VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged; + ui.VoidCallback? _onTextScaleFactorChanged; + Zone? _onTextScaleFactorChangedZone; + @override + set onTextScaleFactorChanged(ui.VoidCallback? callback) { + _onTextScaleFactorChanged = callback; + _onTextScaleFactorChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnTextScaleFactorChanged() { + _invoke(_onTextScaleFactorChanged, _onTextScaleFactorChangedZone); + } + + @override + ui.VoidCallback? get onPlatformBrightnessChanged => + _onPlatformBrightnessChanged; + ui.VoidCallback? _onPlatformBrightnessChanged; + Zone? _onPlatformBrightnessChangedZone; + @override + set onPlatformBrightnessChanged(ui.VoidCallback? callback) { + _onPlatformBrightnessChanged = callback; + _onPlatformBrightnessChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformBrightnessChanged() { + _invoke(_onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone); + } + + @override + ui.VoidCallback? get onMetricsChanged => _onMetricsChanged; + ui.VoidCallback? _onMetricsChanged; + Zone _onMetricsChangedZone = Zone.root; + @override + set onMetricsChanged(ui.VoidCallback? callback) { + _onMetricsChanged = callback; + _onMetricsChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnMetricsChanged() { + if (window._onMetricsChanged != null) { + _invoke(_onMetricsChanged, _onMetricsChangedZone); + } + } + + @override + ui.VoidCallback? get onLocaleChanged => _onLocaleChanged; + ui.VoidCallback? _onLocaleChanged; + Zone? _onLocaleChangedZone; + @override + set onLocaleChanged(ui.VoidCallback? callback) { + _onLocaleChanged = callback; + _onLocaleChangedZone = Zone.current; + } + + /// The locale used when we fail to get the list from the browser. + static const _defaultLocale = const ui.Locale('en', 'US'); + + /// We use the first locale in the [locales] list instead of the browser's + /// built-in `navigator.language` because browsers do not agree on the + /// implementation. /// - /// This is useful in tests to emulate screens of different dimensions. - void debugOverrideDevicePixelRatio(double value) { - _debugDevicePixelRatio = value; + /// See also: + /// + /// * https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages, + /// which explains browser quirks in the implementation notes. + @override + ui.Locale get locale => _locales!.first; + + @override + List? get locales => _locales; + List? _locales = parseBrowserLanguages(); + + /// Sets locales to `null`. + /// + /// `null` is not a valid value for locales. This is only used for testing + /// locale update logic. + void debugResetLocales() { + _locales = null; } - double? _debugDevicePixelRatio; -} + // Called by DomRenderer when browser languages change. + void _updateLocales() { + _locales = parseBrowserLanguages(); + } + + static List parseBrowserLanguages() { + // TODO(yjbanov): find a solution for IE + var languages = html.window.navigator.languages; + if (languages == null || languages.isEmpty) { + // To make it easier for the app code, let's not leave the locales list + // empty. This way there's fewer corner cases for apps to handle. + return const [_defaultLocale]; + } + + final List locales = []; + for (final String language in languages) { + final List parts = language.split('-'); + if (parts.length > 1) { + locales.add(ui.Locale(parts.first, parts.last)); + } else { + locales.add(ui.Locale(language)); + } + } + + assert(locales.isNotEmpty); + return locales; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnLocaleChanged() { + _invoke(_onLocaleChanged, _onLocaleChangedZone); + } + + @override + ui.FrameCallback? get onBeginFrame => _onBeginFrame; + ui.FrameCallback? _onBeginFrame; + Zone? _onBeginFrameZone; + @override + set onBeginFrame(ui.FrameCallback? callback) { + _onBeginFrame = callback; + _onBeginFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnBeginFrame(Duration duration) { + _invoke1(_onBeginFrame, _onBeginFrameZone, duration); + } + + @override + ui.TimingsCallback? get onReportTimings => _onReportTimings; + ui.TimingsCallback? _onReportTimings; + Zone? _onReportTimingsZone; + @override + set onReportTimings(ui.TimingsCallback? callback) { + _onReportTimings = callback; + _onReportTimingsZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnReportTimings(List timings) { + _invoke1>( + _onReportTimings, _onReportTimingsZone, timings); + } + + @override + ui.VoidCallback? get onDrawFrame => _onDrawFrame; + ui.VoidCallback? _onDrawFrame; + Zone? _onDrawFrameZone; + @override + set onDrawFrame(ui.VoidCallback? callback) { + _onDrawFrame = callback; + _onDrawFrameZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnDrawFrame() { + _invoke(_onDrawFrame, _onDrawFrameZone); + } + + @override + ui.PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket; + ui.PointerDataPacketCallback? _onPointerDataPacket; + Zone? _onPointerDataPacketZone; + @override + set onPointerDataPacket(ui.PointerDataPacketCallback? callback) { + _onPointerDataPacket = callback; + _onPointerDataPacketZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPointerDataPacket(ui.PointerDataPacket packet) { + _invoke1( + _onPointerDataPacket, _onPointerDataPacketZone, packet); + } + + @override + ui.VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged; + ui.VoidCallback? _onSemanticsEnabledChanged; + Zone? _onSemanticsEnabledChangedZone; + @override + set onSemanticsEnabledChanged(ui.VoidCallback? callback) { + _onSemanticsEnabledChanged = callback; + _onSemanticsEnabledChangedZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsEnabledChanged() { + _invoke(_onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone); + } + + @override + ui.SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction; + ui.SemanticsActionCallback? _onSemanticsAction; + Zone? _onSemanticsActionZone; + @override + set onSemanticsAction(ui.SemanticsActionCallback? callback) { + _onSemanticsAction = callback; + _onSemanticsActionZone = Zone.current; + } + + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnSemanticsAction( + int id, ui.SemanticsAction action, ByteData? args) { + _invoke3( + _onSemanticsAction, _onSemanticsActionZone, id, action, args); + } + + @override + ui.VoidCallback? get onAccessibilityFeaturesChanged => + _onAccessibilityFeaturesChanged; + ui.VoidCallback? _onAccessibilityFeaturesChanged; + Zone? _onAccessibilityFeaturesChangedZone; + @override + set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) { + _onAccessibilityFeaturesChanged = callback; + _onAccessibilityFeaturesChangedZone = Zone.current; + } -/// A type of [FlutterView] that can be hosted inside of a [FlutterWindow]. -class EngineFlutterWindowView extends ui.FlutterWindow { - EngineFlutterWindowView._(this._viewId, this.platformDispatcher); + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnAccessibilityFeaturesChanged() { + _invoke( + _onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone); + } - final Object _viewId; + @override + ui.PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage; + ui.PlatformMessageCallback? _onPlatformMessage; + Zone? _onPlatformMessageZone; + @override + set onPlatformMessage(ui.PlatformMessageCallback? callback) { + _onPlatformMessage = callback; + _onPlatformMessageZone = Zone.current; + } - final ui.PlatformDispatcher platformDispatcher; + /// Engine code should use this method instead of the callback directly. + /// Otherwise zones won't work properly. + void invokeOnPlatformMessage(String name, ByteData? data, + ui.PlatformMessageResponseCallback callback) { + _invoke3( + _onPlatformMessage, + _onPlatformMessageZone, + name, + data, + callback, + ); + } @override - ui.ViewConfiguration get viewConfiguration { - final EnginePlatformDispatcher engineDispatcher = platformDispatcher as EnginePlatformDispatcher; - assert(engineDispatcher._windowConfigurations.containsKey(_viewId)); - return engineDispatcher._windowConfigurations[_viewId] ?? ui.ViewConfiguration(); + void sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + _sendPlatformMessage( + name, data, _zonedPlatformMessageResponseCallback(callback)); + } + + /// Wraps the given [callback] in another callback that ensures that the + /// original callback is called in the zone it was registered in. + static ui.PlatformMessageResponseCallback? + _zonedPlatformMessageResponseCallback( + ui.PlatformMessageResponseCallback? callback) { + if (callback == null) { + return null; + } + + // Store the zone in which the callback is being registered. + final Zone registrationZone = Zone.current; + + return (ByteData? data) { + registrationZone.runUnaryGuarded(callback, data); + }; + } + + void _sendPlatformMessage( + String name, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + // In widget tests we want to bypass processing of platform messages. + if (assertionsEnabled && ui.debugEmulateFlutterTesterEnvironment) { + return; + } + + if (_debugPrintPlatformMessages) { + print('Sent platform message on channel: "$name"'); + } + + if (assertionsEnabled && name == 'flutter/debug-echo') { + // Echoes back the data unchanged. Used for testing purpopses. + _replyToPlatformMessage(callback, data); + return; + } + + switch (name) { + /// This should be in sync with shell/common/shell.cc + case 'flutter/skia': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'Skia.setResourceCacheMaxBytes': + if (decoded.arguments is int) { + rasterizer?.setSkiaResourceCacheMaxBytes(decoded.arguments); + } + break; + } + + return; + case 'flutter/assets': + assert(ui.webOnlyAssetManager != null); // ignore: unnecessary_null_comparison + final String url = utf8.decode(data!.buffer.asUint8List()); + ui.webOnlyAssetManager.load(url).then((ByteData assetData) { + _replyToPlatformMessage(callback, assetData); + }, onError: (dynamic error) { + html.window.console + .warn('Error while trying to load an asset: $error'); + _replyToPlatformMessage(callback, null); + }); + return; + + case 'flutter/platform': + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + switch (decoded.method) { + case 'SystemNavigator.pop': + browserHistory.exit().then((_) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + }); + return; + case 'HapticFeedback.vibrate': + final String? type = decoded.arguments; + domRenderer.vibrate(_getHapticFeedbackDuration(type)); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setApplicationSwitcherDescription': + final Map arguments = decoded.arguments; + domRenderer.setTitle(arguments['label']); + domRenderer.setThemeColor(ui.Color(arguments['primaryColor'])); + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + return; + case 'SystemChrome.setPreferredOrientations': + final List? arguments = decoded.arguments; + domRenderer.setPreferredOrientation(arguments).then((bool success) { + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(success)); + }); + return; + case 'SystemSound.play': + // There are no default system sounds on web. + _replyToPlatformMessage( + callback, codec.encodeSuccessEnvelope(true)); + return; + case 'Clipboard.setData': + ClipboardMessageHandler().setDataMethodCall(decoded, callback); + return; + case 'Clipboard.getData': + ClipboardMessageHandler().getDataMethodCall(callback); + return; + } + break; + + // Dispatched by the bindings to delay service worker initialization. + case 'flutter/service_worker': + html.window.dispatchEvent(html.Event('flutter-first-frame')); + return; + + case 'flutter/textinput': + textEditing.channel.handleTextInput(data, callback); + return; + + case 'flutter/mousecursor': + const MethodCodec codec = StandardMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map? arguments = decoded.arguments; + switch (decoded.method) { + case 'activateSystemCursor': + MouseCursor.instance!.activateSystemCursor(arguments!['kind']); + } + return; + + case 'flutter/web_test_e2e': + const MethodCodec codec = JSONMethodCodec(); + _replyToPlatformMessage( + callback, + codec.encodeSuccessEnvelope( + _handleWebTestEnd2EndMessage(codec, data))); + return; + + case 'flutter/platform_views': + if (experimentalUseSkia) { + rasterizer!.surface.viewEmbedder + .handlePlatformViewCall(data, callback); + } else { + ui.handlePlatformViewCall(data!, callback!); + } + return; + + case 'flutter/accessibility': + // In widget tests we want to bypass processing of platform messages. + final StandardMessageCodec codec = StandardMessageCodec(); + accessibilityAnnouncements.handleMessage(codec, data); + _replyToPlatformMessage(callback, codec.encodeMessage(true)); + return; + + case 'flutter/navigation': + _handleNavigationMessage(data, callback).then((handled) { + if (!handled && callback != null) { + callback(null); + } + }); + // As soon as Flutter starts taking control of the app navigation, we + // should reset [_defaultRouteName] to "/" so it doesn't have any + // further effect after this point. + _defaultRouteName = '/'; + return; + } + + if (pluginMessageCallHandler != null) { + pluginMessageCallHandler!(name, data, callback); + return; + } + + // Passing [null] to [callback] indicates that the platform message isn't + // implemented. Look at [MethodChannel.invokeMethod] to see how [null] is + // handled. + _replyToPlatformMessage(callback, null); + } + + @visibleForTesting + Future debugInitializeHistory( + UrlStrategy? strategy, { + required bool useSingle, + }) async { + await _browserHistory?.tearDown(); + if (useSingle) { + _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); + } else { + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } + } + + @visibleForTesting + Future debugResetHistory() async { + await _browserHistory?.tearDown(); + _browserHistory = null; + } + + Future _handleNavigationMessage( + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) async { + const MethodCodec codec = JSONMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + final Map arguments = decoded.arguments; + + switch (decoded.method) { + case 'routeUpdated': + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + case 'routeInformationUpdated': + assert(browserHistory is MultiEntriesBrowserHistory); + browserHistory.setRouteName( + arguments['location'], + state: arguments['state'], + ); + _replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); + return true; + } + return false; + } + + int _getHapticFeedbackDuration(String? type) { + switch (type) { + case 'HapticFeedbackType.lightImpact': + return DomRenderer.vibrateLightImpact; + case 'HapticFeedbackType.mediumImpact': + return DomRenderer.vibrateMediumImpact; + case 'HapticFeedbackType.heavyImpact': + return DomRenderer.vibrateHeavyImpact; + case 'HapticFeedbackType.selectionClick': + return DomRenderer.vibrateSelectionClick; + default: + return DomRenderer.vibrateLongPress; + } + } + + /// In Flutter, platform messages are exchanged between threads so the + /// messages and responses have to be exchanged asynchronously. We simulate + /// that by adding a zero-length delay to the reply. + void _replyToPlatformMessage( + ui.PlatformMessageResponseCallback? callback, + ByteData? data, + ) { + Future.delayed(Duration.zero).then((_) { + if (callback != null) { + callback(data); + } + }); + } + + @override + ui.Brightness get platformBrightness => _platformBrightness; + ui.Brightness _platformBrightness = ui.Brightness.light; + + /// Updates [_platformBrightness] and invokes [onPlatformBrightnessChanged] + /// callback if [_platformBrightness] changed. + void _updatePlatformBrightness(ui.Brightness newPlatformBrightness) { + ui.Brightness previousPlatformBrightness = _platformBrightness; + _platformBrightness = newPlatformBrightness; + + if (previousPlatformBrightness != _platformBrightness && + onPlatformBrightnessChanged != null) { + invokeOnPlatformBrightnessChanged(); + } + } + + /// Reference to css media query that indicates the user theme preference on the web. + final html.MediaQueryList _brightnessMediaQuery = + html.window.matchMedia('(prefers-color-scheme: dark)'); + + /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. + /// + /// Updates the [_platformBrightness] with the new user preference. + html.EventListener? _brightnessMediaQueryListener; + + /// Set the callback function for listening changes in [_brightnessMediaQuery] value. + void _addBrightnessMediaQueryListener() { + _updatePlatformBrightness(_brightnessMediaQuery.matches + ? ui.Brightness.dark + : ui.Brightness.light); + + _brightnessMediaQueryListener = (html.Event event) { + final html.MediaQueryListEvent mqEvent = + event as html.MediaQueryListEvent; + _updatePlatformBrightness( + mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); + }; + _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); + registerHotRestartListener(() { + _removeBrightnessMediaQueryListener(); + }); + } + + void _addUrlStrategyListener() { + _jsSetUrlStrategy = allowInterop((JsUrlStrategy? jsStrategy) { + assert( + _browserHistory == null, + 'Cannot set URL strategy more than once.', + ); + final UrlStrategy? strategy = + jsStrategy == null ? null : CustomUrlStrategy.fromJs(jsStrategy); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + }); + registerHotRestartListener(() { + _jsSetUrlStrategy = null; + }); + } + + /// Remove the callback function for listening changes in [_brightnessMediaQuery] value. + void _removeBrightnessMediaQueryListener() { + _brightnessMediaQuery.removeListener(_brightnessMediaQueryListener); + _brightnessMediaQueryListener = null; + } + + @override + void render(ui.Scene scene) { + if (experimentalUseSkia) { + // "Build finish" and "raster start" happen back-to-back because we + // render on the same thread, so there's no overhead from hopping to + // another thread. + // + // CanvasKit works differently from the HTML renderer in that in HTML + // we update the DOM in SceneBuilder.build, which is these function calls + // here are CanvasKit-only. + _frameTimingsOnBuildFinish(); + _frameTimingsOnRasterStart(); + + final LayerScene layerScene = scene as LayerScene; + rasterizer!.draw(layerScene.layerTree); + } else { + final SurfaceScene surfaceScene = scene as SurfaceScene; + domRenderer.renderScene(surfaceScene.webOnlyRootElement); + } + _frameTimingsOnRasterFinish(); + } + + @visibleForTesting + late Rasterizer? rasterizer = + experimentalUseSkia ? Rasterizer(Surface(HtmlViewEmbedder())) : null; +} + +bool _handleWebTestEnd2EndMessage(MethodCodec codec, ByteData? data) { + final MethodCall decoded = codec.decodeMethodCall(data); + double ratio = double.parse(decoded.arguments); + switch (decoded.method) { + case 'setDevicePixelRatio': + window.debugOverrideDevicePixelRatio(ratio); + window.onMetricsChanged!(); + return true; + } + return false; +} + +/// Invokes [callback] inside the given [zone]. +void _invoke(void callback()?, Zone? zone) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(); + } else { + zone!.runGuarded(callback); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg]. +void _invoke1(void callback(A a)?, Zone? zone, A arg) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(arg); + } else { + zone!.runUnaryGuarded(callback, arg); + } +} + +/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3]. +void _invoke3(void callback(A1 a1, A2 a2, A3 a3)?, Zone? zone, + A1 arg1, A2 arg2, A3 arg3) { + if (callback == null) { + return; + } + + assert(zone != null); + + if (identical(zone, Zone.current)) { + callback(arg1, arg2, arg3); + } else { + zone!.runGuarded(() { + callback(arg1, arg2, arg3); + }); } } @@ -268,7 +879,7 @@ class EngineFlutterWindowView extends ui.FlutterWindow { /// `dart:ui` window delegates to this value. However, this value has a wider /// API surface, providing Web-specific functionality that the standard /// `dart:ui` version does not. -final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); +final EngineWindow window = EngineWindow(); /// The Web implementation of [ui.WindowPadding]. class WindowPadding implements ui.WindowPadding { diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart deleted file mode 100644 index 1ec3fdb09da82..0000000000000 --- a/lib/web_ui/lib/src/ui/platform_dispatcher.dart +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// @dart = 2.10 -part of ui; - -typedef VoidCallback = void Function(); -typedef FrameCallback = void Function(Duration duration); -typedef TimingsCallback = void Function(List timings); -typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); -typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); -typedef PlatformMessageResponseCallback = void Function(ByteData? data); -typedef PlatformMessageCallback = void Function( - String name, ByteData? data, PlatformMessageResponseCallback? callback); -typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration); - -abstract class PlatformDispatcher { - static PlatformDispatcher get instance => engine.EnginePlatformDispatcher.instance; - - PlatformConfiguration get configuration; - VoidCallback? get onPlatformConfigurationChanged; - set onPlatformConfigurationChanged(VoidCallback? callback); - - Iterable get views; - - VoidCallback? get onMetricsChanged; - set onMetricsChanged(VoidCallback? callback); - - FrameCallback? get onBeginFrame; - set onBeginFrame(FrameCallback? callback); - - VoidCallback? get onDrawFrame; - set onDrawFrame(VoidCallback? callback); - - PointerDataPacketCallback? get onPointerDataPacket; - set onPointerDataPacket(PointerDataPacketCallback? callback); - - TimingsCallback? get onReportTimings; - set onReportTimings(TimingsCallback? callback); - - void sendPlatformMessage( - String name, - ByteData? data, - PlatformMessageResponseCallback? callback, - ); - - PlatformMessageCallback? get onPlatformMessage; - set onPlatformMessage(PlatformMessageCallback? callback); - - void setIsolateDebugName(String name) {} - - ByteData? getPersistentIsolateData() => null; - - void scheduleFrame(); - - void render(Scene scene, [FlutterView view]); - - AccessibilityFeatures get accessibilityFeatures; - - VoidCallback? get onAccessibilityFeaturesChanged; - set onAccessibilityFeaturesChanged(VoidCallback? callback); - - void updateSemantics(SemanticsUpdate update); - - Locale get locale; - - List get locales => configuration.locales; - - Locale? computePlatformResolvedLocale(List supportedLocales); - - VoidCallback? get onLocaleChanged; - set onLocaleChanged(VoidCallback? callback); - - String get initialLifecycleState => 'AppLifecycleState.resumed'; - - bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat; - - double get textScaleFactor => configuration.textScaleFactor; - - VoidCallback? get onTextScaleFactorChanged; - set onTextScaleFactorChanged(VoidCallback? callback); - - Brightness get platformBrightness => configuration.platformBrightness; - - VoidCallback? get onPlatformBrightnessChanged; - set onPlatformBrightnessChanged(VoidCallback? callback); - - bool get semanticsEnabled => configuration.semanticsEnabled; - - VoidCallback? get onSemanticsEnabledChanged; - set onSemanticsEnabledChanged(VoidCallback? callback); - - SemanticsActionCallback? get onSemanticsAction; - set onSemanticsAction(SemanticsActionCallback? callback); - - String get defaultRouteName; -} - -class PlatformConfiguration { - const PlatformConfiguration({ - this.accessibilityFeatures = const AccessibilityFeatures._(0), - this.alwaysUse24HourFormat = false, - this.semanticsEnabled = false, - this.platformBrightness = Brightness.light, - this.textScaleFactor = 1.0, - this.locales = const [], - this.defaultRouteName = '/', - }); - - PlatformConfiguration copyWith({ - AccessibilityFeatures? accessibilityFeatures, - bool? alwaysUse24HourFormat, - bool? semanticsEnabled, - Brightness? platformBrightness, - double? textScaleFactor, - List? locales, - String? defaultRouteName, - }) { - return PlatformConfiguration( - accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures, - alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, - semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, - platformBrightness: platformBrightness ?? this.platformBrightness, - textScaleFactor: textScaleFactor ?? this.textScaleFactor, - locales: locales ?? this.locales, - defaultRouteName: defaultRouteName ?? this.defaultRouteName, - ); - } - - final AccessibilityFeatures accessibilityFeatures; - final bool alwaysUse24HourFormat; - final bool semanticsEnabled; - final Brightness platformBrightness; - final double textScaleFactor; - final List locales; - final String defaultRouteName; -} - -class ViewConfiguration { - const ViewConfiguration({ - this.window, - this.devicePixelRatio = 1.0, - this.geometry = Rect.zero, - this.visible = false, - this.viewInsets = WindowPadding.zero, - this.viewPadding = WindowPadding.zero, - this.systemGestureInsets = WindowPadding.zero, - this.padding = WindowPadding.zero, - }); - - ViewConfiguration copyWith({ - FlutterWindow? window, - double? devicePixelRatio, - Rect? geometry, - bool? visible, - WindowPadding? viewInsets, - WindowPadding? viewPadding, - WindowPadding? systemGestureInsets, - WindowPadding? padding, - }) { - return ViewConfiguration( - window: window ?? this.window, - devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, - geometry: geometry ?? this.geometry, - visible: visible ?? this.visible, - viewInsets: viewInsets ?? this.viewInsets, - viewPadding: viewPadding ?? this.viewPadding, - systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, - padding: padding ?? this.padding, - ); - } - - final FlutterWindow? window; - final double devicePixelRatio; - final Rect geometry; - final bool visible; - final WindowPadding viewInsets; - final WindowPadding viewPadding; - final WindowPadding systemGestureInsets; - final WindowPadding padding; - - @override - String toString() { - return '$runtimeType[window: $window, geometry: $geometry]'; - } -} - -enum FramePhase { - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish, -} - -class FrameTiming { - factory FrameTiming({ - required int vsyncStart, - required int buildStart, - required int buildFinish, - required int rasterStart, - required int rasterFinish, - }) { - return FrameTiming._([ - vsyncStart, - buildStart, - buildFinish, - rasterStart, - rasterFinish - ]); - } - - FrameTiming._(this._timestamps) - : assert(_timestamps.length == FramePhase.values.length); - - int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; - - Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); - - Duration get buildDuration => - _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); - - Duration get rasterDuration => - _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); - - Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); - - Duration get totalSpan => - _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); - - final List _timestamps; // in microseconds - - String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; - - @override - String toString() { - return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; - } -} - -enum AppLifecycleState { - resumed, - inactive, - paused, - detached, -} - -abstract class WindowPadding { - const factory WindowPadding._( - {required double left, - required double top, - required double right, - required double bottom}) = engine.WindowPadding; - - double get left; - double get top; - double get right; - double get bottom; - - static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); - - @override - String toString() { - return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; - } -} - -class Locale { - const Locale( - this._languageCode, [ - this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison - assert(_languageCode != ''), - scriptCode = null; - - const Locale.fromSubtags({ - String languageCode = 'und', - this.scriptCode, - String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison - assert(languageCode != ''), - _languageCode = languageCode, - assert(scriptCode != ''), - assert(countryCode != ''), - _countryCode = countryCode; - - String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; - final String _languageCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedLanguageSubtagMap = { - 'in': 'id', // Indonesian; deprecated 1989-01-01 - 'iw': 'he', // Hebrew; deprecated 1989-01-01 - 'ji': 'yi', // Yiddish; deprecated 1989-01-01 - 'jw': 'jv', // Javanese; deprecated 2001-08-13 - 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 - 'aam': 'aas', // Aramanik; deprecated 2015-02-12 - 'adp': 'dz', // Adap; deprecated 2015-02-12 - 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 - 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 - 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 - 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 - 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 - 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 - 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 - 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 - 'coy': 'pij', // Coyaima; deprecated 2016-05-30 - 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 - 'drh': 'khk', // Darkhat; deprecated 2010-03-11 - 'drw': 'prs', // Darwazi; deprecated 2010-03-11 - 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 - 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 - 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 - 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 - 'guv': 'duz', // Gey; deprecated 2016-05-30 - 'hrr': 'jal', // Horuru; deprecated 2012-08-12 - 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 - 'ilw': 'gal', // Talur; deprecated 2013-09-10 - 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 - 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 - 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 - 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 - 'krm': 'bmf', // Krim; deprecated 2017-02-23 - 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 - 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 - 'kwq': 'yam', // Kwak; deprecated 2015-02-12 - 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 - 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 - 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 - 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 - 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 - 'meg': 'cir', // Mea; deprecated 2013-09-10 - 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 - 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 - 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 - 'nad': 'xny', // Nijadali; deprecated 2016-05-30 - 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 - 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 - 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 - 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 - 'pcr': 'adx', // Panang; deprecated 2013-09-10 - 'pmc': 'huw', // Palumata; deprecated 2016-05-30 - 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 - 'ppa': 'bfy', // Pao; deprecated 2016-05-30 - 'ppr': 'lcq', // Piru; deprecated 2013-09-10 - 'pry': 'prt', // Pray 3; deprecated 2016-05-30 - 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 - 'sca': 'hle', // Sansu; deprecated 2012-08-12 - 'skk': 'oyb', // Sok; deprecated 2017-02-23 - 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 - 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 - 'thx': 'oyb', // The; deprecated 2015-02-12 - 'tie': 'ras', // Tingal; deprecated 2011-08-16 - 'tkk': 'twm', // Takpa; deprecated 2011-08-16 - 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 - 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 - 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 - 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 - 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 - 'uok': 'ema', // Uokha; deprecated 2015-02-12 - 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 - 'xia': 'acn', // Xiandao; deprecated 2013-09-10 - 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 - 'xsj': 'suj', // Subi; deprecated 2015-02-12 - 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 - 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 - 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 - 'yos': 'zom', // Yos; deprecated 2013-09-10 - 'yuu': 'yug', // Yugh; deprecated 2014-02-28 - }; - - final String? scriptCode; - - String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; - final String? _countryCode; - - // This map is generated by //flutter/tools/gen_locale.dart - // Mappings generated for language subtag registry as of 2019-02-27. - static const Map _deprecatedRegionSubtagMap = { - 'BU': 'MM', // Burma; deprecated 1989-12-05 - 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 - 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 - 'TP': 'TL', // East Timor; deprecated 2002-05-20 - 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 - 'ZR': 'CD', // Zaire; deprecated 1997-07-14 - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - return other is Locale - && other.languageCode == languageCode - && other.scriptCode == scriptCode - && other.countryCode == countryCode; - } - - @override - int get hashCode => hashValues(languageCode, scriptCode, countryCode); - - @override - String toString() => _rawToString('_'); - - // TODO(yjbanov): implement to match flutter native. - String toLanguageTag() => _rawToString('-'); - - String _rawToString(String separator) { - final StringBuffer out = StringBuffer(languageCode); - if (scriptCode != null) { - out.write('$separator$scriptCode'); - } - if (_countryCode != null) { - out.write('$separator$countryCode'); - } - return out.toString(); - } -} \ No newline at end of file diff --git a/lib/web_ui/lib/src/ui/text.dart b/lib/web_ui/lib/src/ui/text.dart index eaa9f449db7a4..abf00f154674b 100644 --- a/lib/web_ui/lib/src/ui/text.dart +++ b/lib/web_ui/lib/src/ui/text.dart @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Synced 2019-05-30T14:20:57.833907. // @dart = 2.10 part of ui; diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index d3519f19b13d0..fa58ff01e5537 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -1,259 +1,262 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Synced 2019-05-30T14:20:57.841444. // @dart = 2.10 part of ui; -abstract class FlutterView { - PlatformDispatcher get platformDispatcher; - ViewConfiguration get viewConfiguration; - double get devicePixelRatio => viewConfiguration.devicePixelRatio; - Rect get physicalGeometry => viewConfiguration.geometry; - Size get physicalSize => viewConfiguration.geometry.size; - WindowPadding get viewInsets => viewConfiguration.viewInsets; - WindowPadding get viewPadding => viewConfiguration.viewPadding; - WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; - WindowPadding get padding => viewConfiguration.padding; - void render(Scene scene) => platformDispatcher.render(scene, this); +typedef VoidCallback = void Function(); +typedef FrameCallback = void Function(Duration duration); +typedef TimingsCallback = void Function(List timings); +typedef PointerDataPacketCallback = void Function(PointerDataPacket packet); +typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args); +typedef PlatformMessageResponseCallback = void Function(ByteData? data); +typedef PlatformMessageCallback = void Function( + String name, ByteData? data, PlatformMessageResponseCallback? callback); + +enum AppLifecycleState { + resumed, + inactive, + paused, + detached, } -abstract class FlutterWindow extends FlutterView { - @override - PlatformDispatcher get platformDispatcher; - - @override - ViewConfiguration get viewConfiguration; -} - -abstract class SingletonFlutterWindow extends FlutterWindow { - VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged; - set onMetricsChanged(VoidCallback? callback) { - platformDispatcher.onMetricsChanged = callback; - } - - Locale? get locale => platformDispatcher.locale; - List? get locales => platformDispatcher.locales; - - Locale? computePlatformResolvedLocale(List supportedLocales) { - return platformDispatcher.computePlatformResolvedLocale(supportedLocales); - } - - VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged; - set onLocaleChanged(VoidCallback? callback) { - platformDispatcher.onLocaleChanged = callback; - } - - String get initialLifecycleState => platformDispatcher.initialLifecycleState; - - double get textScaleFactor => platformDispatcher.textScaleFactor; - bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; - - VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged; - set onTextScaleFactorChanged(VoidCallback? callback) { - platformDispatcher.onTextScaleFactorChanged = callback; - } - - Brightness get platformBrightness => platformDispatcher.platformBrightness; - - VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged; - set onPlatformBrightnessChanged(VoidCallback? callback) { - platformDispatcher.onPlatformBrightnessChanged = callback; - } - - FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame; - set onBeginFrame(FrameCallback? callback) { - platformDispatcher.onBeginFrame = callback; - } +abstract class WindowPadding { + const factory WindowPadding._({ + required double left, + required double top, + required double right, + required double bottom, + }) = engine.WindowPadding; - VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame; - set onDrawFrame(VoidCallback? callback) { - platformDispatcher.onDrawFrame = callback; - } + double get left; + double get top; + double get right; + double get bottom; + static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0); - TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings; - set onReportTimings(TimingsCallback? callback) { - platformDispatcher.onReportTimings = callback; - } - - PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket; - set onPointerDataPacket(PointerDataPacketCallback? callback) { - platformDispatcher.onPointerDataPacket = callback; + @override + String toString() { + return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)'; } +} - String get defaultRouteName => platformDispatcher.defaultRouteName; - - void scheduleFrame() => platformDispatcher.scheduleFrame(); - void render(Scene scene) => platformDispatcher.render(scene, this); - - bool get semanticsEnabled => platformDispatcher.semanticsEnabled; - - VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged; - set onSemanticsEnabledChanged(VoidCallback? callback) { - platformDispatcher.onSemanticsEnabledChanged = callback; - } +class Locale { + const Locale( + this._languageCode, [ + this._countryCode, + ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + assert(_languageCode != ''), + scriptCode = null; + const Locale.fromSubtags({ + String languageCode = 'und', + this.scriptCode, + String? countryCode, + }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + assert(languageCode != ''), + _languageCode = languageCode, + assert(scriptCode != ''), + assert(countryCode != ''), + _countryCode = countryCode; + String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode; + final String _languageCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedLanguageSubtagMap = { + 'in': 'id', // Indonesian; deprecated 1989-01-01 + 'iw': 'he', // Hebrew; deprecated 1989-01-01 + 'ji': 'yi', // Yiddish; deprecated 1989-01-01 + 'jw': 'jv', // Javanese; deprecated 2001-08-13 + 'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22 + 'aam': 'aas', // Aramanik; deprecated 2015-02-12 + 'adp': 'dz', // Adap; deprecated 2015-02-12 + 'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12 + 'ayx': 'nun', // Ayi (China); deprecated 2011-08-16 + 'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30 + 'bjd': 'drl', // Bandjigali; deprecated 2012-08-12 + 'ccq': 'rki', // Chaungtha; deprecated 2012-08-12 + 'cjr': 'mom', // Chorotega; deprecated 2010-03-11 + 'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12 + 'cmk': 'xch', // Chimakum; deprecated 2010-03-11 + 'coy': 'pij', // Coyaima; deprecated 2016-05-30 + 'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30 + 'drh': 'khk', // Darkhat; deprecated 2010-03-11 + 'drw': 'prs', // Darwazi; deprecated 2010-03-11 + 'gav': 'dev', // Gabutamon; deprecated 2010-03-11 + 'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12 + 'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30 + 'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12 + 'guv': 'duz', // Gey; deprecated 2016-05-30 + 'hrr': 'jal', // Horuru; deprecated 2012-08-12 + 'ibi': 'opa', // Ibilo; deprecated 2012-08-12 + 'ilw': 'gal', // Talur; deprecated 2013-09-10 + 'jeg': 'oyb', // Jeng; deprecated 2017-02-23 + 'kgc': 'tdf', // Kasseng; deprecated 2016-05-30 + 'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12 + 'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12 + 'krm': 'bmf', // Krim; deprecated 2017-02-23 + 'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30 + 'kvs': 'gdj', // Kunggara; deprecated 2016-05-30 + 'kwq': 'yam', // Kwak; deprecated 2015-02-12 + 'kxe': 'tvd', // Kakihum; deprecated 2015-02-12 + 'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30 + 'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30 + 'lii': 'raq', // Lingkhim; deprecated 2015-02-12 + 'lmm': 'rmx', // Lamam; deprecated 2014-02-28 + 'meg': 'cir', // Mea; deprecated 2013-09-10 + 'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11 + 'mwj': 'vaj', // Maligo; deprecated 2015-02-12 + 'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11 + 'nad': 'xny', // Nijadali; deprecated 2016-05-30 + 'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08 + 'nnx': 'ngv', // Ngong; deprecated 2015-02-12 + 'nts': 'pij', // Natagaimas; deprecated 2016-05-30 + 'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12 + 'pcr': 'adx', // Panang; deprecated 2013-09-10 + 'pmc': 'huw', // Palumata; deprecated 2016-05-30 + 'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12 + 'ppa': 'bfy', // Pao; deprecated 2016-05-30 + 'ppr': 'lcq', // Piru; deprecated 2013-09-10 + 'pry': 'prt', // Pray 3; deprecated 2016-05-30 + 'puz': 'pub', // Purum Naga; deprecated 2014-02-28 + 'sca': 'hle', // Sansu; deprecated 2012-08-12 + 'skk': 'oyb', // Sok; deprecated 2017-02-23 + 'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30 + 'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30 + 'thx': 'oyb', // The; deprecated 2015-02-12 + 'tie': 'ras', // Tingal; deprecated 2011-08-16 + 'tkk': 'twm', // Takpa; deprecated 2011-08-16 + 'tlw': 'weo', // South Wemale; deprecated 2012-08-12 + 'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30 + 'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30 + 'tnf': 'prs', // Tangshewi; deprecated 2010-03-11 + 'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12 + 'uok': 'ema', // Uokha; deprecated 2015-02-12 + 'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30 + 'xia': 'acn', // Xiandao; deprecated 2013-09-10 + 'xkh': 'waw', // Karahawyana; deprecated 2016-05-30 + 'xsj': 'suj', // Subi; deprecated 2015-02-12 + 'ybd': 'rki', // Yangbye; deprecated 2012-08-12 + 'yma': 'lrr', // Yamphe; deprecated 2012-08-12 + 'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12 + 'yos': 'zom', // Yos; deprecated 2013-09-10 + 'yuu': 'yug', // Yugh; deprecated 2014-02-28 + }; + final String? scriptCode; + String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode; + final String? _countryCode; + + // This map is generated by //flutter/tools/gen_locale.dart + // Mappings generated for language subtag registry as of 2019-02-27. + static const Map _deprecatedRegionSubtagMap = { + 'BU': 'MM', // Burma; deprecated 1989-12-05 + 'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30 + 'FX': 'FR', // Metropolitan France; deprecated 1997-07-14 + 'TP': 'TL', // East Timor; deprecated 2002-05-20 + 'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14 + 'ZR': 'CD', // Zaire; deprecated 1997-07-14 + }; - SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; - set onSemanticsAction(SemanticsActionCallback? callback) { - platformDispatcher.onSemanticsAction = callback; + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + return other is Locale + && other.languageCode == languageCode + && other.scriptCode == scriptCode + && other.countryCode == countryCode; } - AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures; - - VoidCallback? get onAccessibilityFeaturesChanged => - platformDispatcher.onAccessibilityFeaturesChanged; - set onAccessibilityFeaturesChanged(VoidCallback? callback) { - platformDispatcher.onAccessibilityFeaturesChanged = callback; - } + @override + int get hashCode => hashValues(languageCode, scriptCode, countryCode); - void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update); + @override + String toString() => _rawToString('_'); - void sendPlatformMessage( - String name, - ByteData? data, - PlatformMessageResponseCallback? callback, - ) { - platformDispatcher.sendPlatformMessage(name, data, callback); - } + // TODO(yjbanov): implement to match flutter native. + String toLanguageTag() => _rawToString('-'); - PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; - set onPlatformMessage(PlatformMessageCallback? callback) { - platformDispatcher.onPlatformMessage = callback; + String _rawToString(String separator) { + final StringBuffer out = StringBuffer(languageCode); + if (scriptCode != null) { + out.write('$separator$scriptCode'); + } + if (_countryCode != null) { + out.write('$separator$countryCode'); + } + return out.toString(); } - - void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } -// This class will go away entirely once references to it are removed from the -// framework. The many explicit overrides are an artifact of needing to add the -// same overrides to the one in dart:ui in order to get dartdoc to find the docs -// for them. -abstract class Window extends SingletonFlutterWindow { - @override +abstract class Window { double get devicePixelRatio; - - @override - Rect get physicalGeometry; - - @override Size get physicalSize; + WindowPadding get viewInsets => WindowPadding.zero; - @override - WindowPadding get viewInsets; - - @override - WindowPadding get viewPadding; - - @override - WindowPadding get systemGestureInsets; - - @override - WindowPadding get padding; - - @override - void render(Scene scene); - - @override - VoidCallback? get onMetricsChanged; - @override - set onMetricsChanged(VoidCallback? callback); - - @override - Locale? get locale => super.locale; - - @override - List? get locales => super.locales; - - @override - Locale? computePlatformResolvedLocale(List supportedLocales); - - @override - VoidCallback? get onLocaleChanged; - @override - set onLocaleChanged(VoidCallback? callback); - - @override - String get initialLifecycleState; + WindowPadding get viewPadding => WindowPadding.zero; - @override - double get textScaleFactor; - - @override - bool get alwaysUse24HourFormat; - - @override + WindowPadding get systemGestureInsets => WindowPadding.zero; + WindowPadding get padding => WindowPadding.zero; + double get textScaleFactor => _textScaleFactor; + double _textScaleFactor = 1.0; + bool get alwaysUse24HourFormat => _alwaysUse24HourFormat; + bool _alwaysUse24HourFormat = false; VoidCallback? get onTextScaleFactorChanged; - @override set onTextScaleFactorChanged(VoidCallback? callback); - - @override Brightness get platformBrightness; - - @override VoidCallback? get onPlatformBrightnessChanged; - @override set onPlatformBrightnessChanged(VoidCallback? callback); + VoidCallback? get onMetricsChanged; + set onMetricsChanged(VoidCallback? callback); + Locale? get locale; + List? get locales; + Locale? computePlatformResolvedLocale(List supportedLocales) { + // TODO(garyq): Implement on web. + return null; + } - @override + VoidCallback? get onLocaleChanged; + set onLocaleChanged(VoidCallback? callback); + void scheduleFrame(); FrameCallback? get onBeginFrame; - @override set onBeginFrame(FrameCallback? callback); - - @override - VoidCallback? get onDrawFrame; - @override - set onDrawFrame(VoidCallback? callback); - - @override TimingsCallback? get onReportTimings; - @override set onReportTimings(TimingsCallback? callback); - - @override + VoidCallback? get onDrawFrame; + set onDrawFrame(VoidCallback? callback); PointerDataPacketCallback? get onPointerDataPacket; - @override set onPointerDataPacket(PointerDataPacketCallback? callback); - - @override String get defaultRouteName; - - @override - void scheduleFrame(); - - @override - bool get semanticsEnabled; - - @override + bool get semanticsEnabled => engine.EngineSemanticsOwner.instance.semanticsEnabled; VoidCallback? get onSemanticsEnabledChanged; - @override set onSemanticsEnabledChanged(VoidCallback? callback); - - @override SemanticsActionCallback? get onSemanticsAction; - @override set onSemanticsAction(SemanticsActionCallback? callback); - - @override - AccessibilityFeatures get accessibilityFeatures; - - @override VoidCallback? get onAccessibilityFeaturesChanged; - @override set onAccessibilityFeaturesChanged(VoidCallback? callback); + PlatformMessageCallback? get onPlatformMessage; + set onPlatformMessage(PlatformMessageCallback? callback); + void updateSemantics(SemanticsUpdate update) { + engine.EngineSemanticsOwner.instance.updateSemantics(update); + } - @override - void updateSemantics(SemanticsUpdate update); + void sendPlatformMessage( + String name, + ByteData? data, + PlatformMessageResponseCallback? callback, + ); + AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures; + AccessibilityFeatures _accessibilityFeatures = AccessibilityFeatures._(0); + void render(Scene scene); - @override - void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback); + String get initialLifecycleState => 'AppLifecycleState.resumed'; - @override - PlatformMessageCallback? get onPlatformMessage; - @override - set onPlatformMessage(PlatformMessageCallback? callback); + void setIsolateDebugName(String name) {} - @override - void setIsolateDebugName(String name); + ByteData? getPersistentIsolateData() => null; } class AccessibilityFeatures { @@ -268,7 +271,6 @@ class AccessibilityFeatures { // A bitfield which represents each enabled feature. final int _index; - bool get accessibleNavigation => _kAccessibleNavigation & _index != 0; bool get invertColors => _kInvertColorsIndex & _index != 0; bool get disableAnimations => _kDisableAnimationsIndex & _index != 0; @@ -350,6 +352,7 @@ class PluginUtilities { } } +// TODO(flutter_web): probably dont implement this one. class IsolateNameServer { // This class is only a namespace, and should not be instantiated or // extended directly. @@ -368,4 +371,50 @@ class IsolateNameServer { } } +enum FramePhase { + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish, +} + +class FrameTiming { + factory FrameTiming({ + required int vsyncStart, + required int buildStart, + required int buildFinish, + required int rasterStart, + required int rasterFinish, + }) { + return FrameTiming._([ + vsyncStart, + buildStart, + buildFinish, + rasterStart, + rasterFinish + ]); + } + FrameTiming._(List timestamps) + : assert(timestamps.length == FramePhase.values.length), + _timestamps = timestamps; + + int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index]; + + Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]); + Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart); + Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart); + Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart); + Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart); + + final List _timestamps; // in microseconds + + String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms'; + + @override + String toString() { + return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})'; + } +} + Window get window => engine.window; diff --git a/lib/web_ui/lib/ui.dart b/lib/web_ui/lib/ui.dart index b616c223212d5..13d5ee2813b20 100644 --- a/lib/web_ui/lib/ui.dart +++ b/lib/web_ui/lib/ui.dart @@ -29,7 +29,6 @@ part 'src/ui/natives.dart'; part 'src/ui/painting.dart'; part 'src/ui/path.dart'; part 'src/ui/path_metrics.dart'; -part 'src/ui/platform_dispatcher.dart'; part 'src/ui/pointer.dart'; part 'src/ui/semantics.dart'; part 'src/ui/test_embedding.dart'; diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index 6bd2321768ae4..a47246c24894b 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -50,7 +50,7 @@ void _tests() { when(mockRasterizer.addPostFrameCallback(any)).thenAnswer((_) { addPostFrameCallbackCount++; }); - EnginePlatformDispatcher.instance.rasterizer = mockRasterizer; + window.rasterizer = mockRasterizer; // Trigger first create final TestSkiaObject testObject = TestSkiaObject(); diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index ff2f7674d4a88..f5be42ae61db7 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -292,7 +292,7 @@ void testMain() { expect(strategy.history, hasLength(1)); expect(strategy.currentEntry.state, _tagStateWithSerialCount('initial state', 0)); expect(strategy.currentEntry.url, '/home'); - await routeInformationUpdated('/page1', 'page1 state'); + await routeInfomrationUpdated('/page1', 'page1 state'); // Should have two history entries now. expect(strategy.history, hasLength(2)); expect(strategy.currentEntryIndex, 1); @@ -329,8 +329,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInformationUpdated('/page1', 'page1 state'); - await routeInformationUpdated('/page2', 'page2 state'); + await routeInfomrationUpdated('/page1', 'page1 state'); + await routeInfomrationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -426,8 +426,8 @@ void testMain() { ); await window.debugInitializeHistory(strategy, useSingle: false); - await routeInformationUpdated('/page1', 'page1 state'); - await routeInformationUpdated('/page2', 'page2 state'); + await routeInfomrationUpdated('/page1', 'page1 state'); + await routeInfomrationUpdated('/page2', 'page2 state'); // Make sure we are on page2. expect(strategy.history, hasLength(3)); @@ -522,7 +522,7 @@ Future routeUpdated(String routeName) { return completer.future; } -Future routeInformationUpdated(String location, dynamic state) { +Future routeInfomrationUpdated(String location, dynamic state) { final Completer completer = Completer(); window.sendPlatformMessage( 'flutter/navigation', diff --git a/lib/web_ui/test/engine/surface/platform_view_test.dart b/lib/web_ui/test/engine/surface/platform_view_test.dart index c806d67658a78..eeda48709da7e 100644 --- a/lib/web_ui/test/engine/surface/platform_view_test.dart +++ b/lib/web_ui/test/engine/surface/platform_view_test.dart @@ -15,7 +15,7 @@ import 'package:test/test.dart'; import '../../matchers.dart'; const MethodCodec codec = StandardMethodCodec(); -final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); +final EngineWindow window = EngineWindow(); void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index e61c9f1e42289..15e381440125a 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -31,7 +31,7 @@ void testMain() { expect(window.onTextScaleFactorChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnTextScaleFactorChanged(); + window.invokeOnTextScaleFactorChanged(); }); test('onPlatformBrightnessChanged preserves the zone', () { @@ -47,7 +47,7 @@ void testMain() { expect(window.onPlatformBrightnessChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnPlatformBrightnessChanged(); + window.invokeOnPlatformBrightnessChanged(); }); test('onMetricsChanged preserves the zone', () { @@ -63,7 +63,7 @@ void testMain() { expect(window.onMetricsChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnMetricsChanged(); + window.invokeOnMetricsChanged(); }); test('onLocaleChanged preserves the zone', () { @@ -79,7 +79,7 @@ void testMain() { expect(window.onLocaleChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnLocaleChanged(); + window.invokeOnLocaleChanged(); }); test('onBeginFrame preserves the zone', () { @@ -95,7 +95,7 @@ void testMain() { expect(window.onBeginFrame, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnBeginFrame(null); + window.invokeOnBeginFrame(null); }); test('onReportTimings preserves the zone', () { @@ -111,7 +111,7 @@ void testMain() { expect(window.onReportTimings, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnReportTimings(null); + window.invokeOnReportTimings(null); }); test('onDrawFrame preserves the zone', () { @@ -127,7 +127,7 @@ void testMain() { expect(window.onDrawFrame, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnDrawFrame(); + window.invokeOnDrawFrame(); }); test('onPointerDataPacket preserves the zone', () { @@ -143,7 +143,7 @@ void testMain() { expect(window.onPointerDataPacket, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(null); + window.invokeOnPointerDataPacket(null); }); test('onSemanticsEnabledChanged preserves the zone', () { @@ -159,7 +159,7 @@ void testMain() { expect(window.onSemanticsEnabledChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnSemanticsEnabledChanged(); + window.invokeOnSemanticsEnabledChanged(); }); test('onSemanticsAction preserves the zone', () { @@ -175,7 +175,7 @@ void testMain() { expect(window.onSemanticsAction, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnSemanticsAction(null, null, null); + window.invokeOnSemanticsAction(null, null, null); }); test('onAccessibilityFeaturesChanged preserves the zone', () { @@ -191,7 +191,7 @@ void testMain() { expect(window.onAccessibilityFeaturesChanged, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged(); + window.invokeOnAccessibilityFeaturesChanged(); }); test('onPlatformMessage preserves the zone', () { @@ -207,7 +207,7 @@ void testMain() { expect(window.onPlatformMessage, same(callback)); }); - EnginePlatformDispatcher.instance.invokeOnPlatformMessage(null, null, null); + window.invokeOnPlatformMessage(null, null, null); }); test('sendPlatformMessage preserves the zone', () async { @@ -290,8 +290,8 @@ void testMain() { // Trigger a change notification (reset locales because the notification // doesn't actually change the list of languages; the test only observes // that the list is populated again). - EnginePlatformDispatcher.instance.debugResetLocales(); - expect(window.locales, isEmpty); + window.debugResetLocales(); + expect(window.locales, null); expect(localeChangedCount, 0); html.window.dispatchEvent(html.Event('languagechange')); expect(window.locales, isNotEmpty); diff --git a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart index a9dca67448268..a072d025974fd 100644 --- a/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart @@ -227,8 +227,8 @@ void testMain() async { ); final SceneBuilder sb = SceneBuilder(); - sb.pushTransform(Matrix4.diagonal3Values(EnginePlatformDispatcher.browserDevicePixelRatio, - EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); + sb.pushTransform(Matrix4.diagonal3Values(EngineWindow.browserDevicePixelRatio, + EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); sb.pushTransform(Matrix4.rotationZ(math.pi / 2).toFloat64()); sb.pushOffset(0, -500); sb.pushClipRect(canvasSize); diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index d9f706f3ff9e5..04dce71d0b986 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -412,8 +412,8 @@ void _testCullRectComputation() { final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); builder.pushTransform(Matrix4.diagonal3Values( - EnginePlatformDispatcher.browserDevicePixelRatio, - EnginePlatformDispatcher.browserDevicePixelRatio, 1.0).toFloat64()); + EngineWindow.browserDevicePixelRatio, + EngineWindow.browserDevicePixelRatio, 1.0).toFloat64()); // TODO(yjbanov): see the TODO below. // final double screenWidth = html.window.innerWidth.toDouble(); diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index 3d164d1800401..ef0a755f550cf 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -71,7 +71,7 @@ void testMain() { expect(window.browserHistory.currentPath, '/'); // Perform some navigation operations. - routeInformationUpdated('/foo/bar', null); + routeInfomrationUpdated('/foo/bar', null); // Path should not be updated because URL strategy is disabled. expect(window.browserHistory.currentPath, '/'); }); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 36e4e1b4e74e1..7697d70e37c0f 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -188,7 +188,7 @@ bool RuntimeController::SetViewportMetrics(const ViewportMetrics& metrics) { platform_data_.viewport_metrics = metrics; if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - platform_configuration->get_window(0)->UpdateWindowMetrics(metrics); + platform_configuration->window()->UpdateWindowMetrics(metrics); return true; } @@ -310,7 +310,7 @@ bool RuntimeController::DispatchPointerDataPacket( if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { TRACE_EVENT1("flutter", "RuntimeController::DispatchPointerDataPacket", "mode", "basic"); - platform_configuration->get_window(0)->DispatchPointerDataPacket(packet); + platform_configuration->window()->DispatchPointerDataPacket(packet); return true; } diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 20e7abc532984..b3f1a41d3716f 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -147,7 +147,7 @@ class RuntimeController : public PlatformConfigurationClient { /// If the isolate is not running, these metrics will be saved and /// flushed to the isolate when it starts. /// - /// @param[in] metrics The window's viewport metrics. + /// @param[in] metrics The viewport metrics. /// /// @return If the window metrics were forwarded to the running isolate. /// diff --git a/shell/common/engine.h b/shell/common/engine.h index b35a7ff85ab07..c0e53c3d742a4 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -202,14 +202,13 @@ class Engine final : public RuntimeDelegate, /// @brief Notifies the shell of the name of the root isolate and its /// port when that isolate is launched, restarted (in the /// cold-restart scenario) or the application itself updates the - /// name of the root isolate (via - /// `PlatformDispatcher.setIsolateDebugName` in - /// `platform_dispatcher.dart`). The name of the isolate is - /// meaningless to the engine but is used in instrumentation and - /// tooling. Currently, this information is to update the - /// service protocol list of available root isolates running in - /// the VM and their names so that the appropriate isolate can - /// be selected in the tools for debugging and instrumentation. + /// name of the root isolate (via `Window.setIsolateDebugName` + /// in `window.dart`). The name of the isolate is meaningless to + /// the engine but is used in instrumentation and tooling. + /// Currently, this information is to update the service + /// protocol list of available root isolates running in the VM + /// and their names so that the appropriate isolate can be + /// selected in the tools for debugging and instrumentation. /// /// @param[in] isolate_name The isolate name /// @param[in] isolate_port The isolate port @@ -553,8 +552,8 @@ class Engine final : public RuntimeDelegate, /// "main.dart", the entrypoint is "main" and the port name /// "1234". Once launched, the isolate may re-christen itself /// using a name it selects via `setIsolateDebugName` in - /// `platform_dispatcher.dart`. This name is purely advisory and - /// only used by instrumentation and reporting purposes. + /// `window.dart`. This name is purely advisory and only used by + /// instrumentation and reporting purposes. /// /// @return The debug name of the root isolate. /// diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index 9c72ea87e0e42..492ddb8960d03 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -15,7 +15,7 @@ void nativeOnPointerDataPacket(List sequences) native 'NativeOnPointerDataP @pragma('vm:entry-point') void reportTimingsMain() { - PlatformDispatcher.instance.onReportTimings = (List timings) { + window.onReportTimings = (List timings) { List timestamps = []; for (FrameTiming t in timings) { for (FramePhase phase in FramePhase.values) { @@ -28,15 +28,15 @@ void reportTimingsMain() { @pragma('vm:entry-point') void onBeginFrameMain() { - PlatformDispatcher.instance.onBeginFrame = (Duration beginTime) { + window.onBeginFrame = (Duration beginTime) { nativeOnBeginFrame(beginTime.inMicroseconds); }; } @pragma('vm:entry-point') void onPointerDataPacketMain() { - PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket packet) { - List sequence = []; + window.onPointerDataPacket = (PointerDataPacket packet) { + List sequence= []; for (PointerData data in packet.data) { sequence.add(PointerChange.values.indexOf(data.change)); } @@ -62,7 +62,7 @@ void _reportMetrics(double devicePixelRatio, double width, double height) native @pragma('vm:entry-point') void dummyReportTimingsMain() { - PlatformDispatcher.instance.onReportTimings = (List timings) {}; + window.onReportTimings = (List timings) {}; } @pragma('vm:entry-point') @@ -102,7 +102,7 @@ void testSkiaResourceCacheSendsResponse() { "method": "Skia.setResourceCacheMaxBytes", "args": 10000 }'''; - PlatformDispatcher.instance.sendPlatformMessage( + window.sendPlatformMessage( 'flutter/skia', Uint8List.fromList(utf8.encode(jsonRequest)).buffer.asByteData(), callback, @@ -120,24 +120,17 @@ void canCreateImageFromDecompressedData() { (int i) => i % 4 < 2 ? 0x00 : 0xFF, )); + decodeImageFromPixels( - pixels, - imageWidth, - imageHeight, - PixelFormat.rgba8888, - (Image image) { - notifyWidthHeight(image.width, image.height); - }, - ); + pixels, imageWidth, imageHeight, PixelFormat.rgba8888, + (Image image) { + notifyWidthHeight(image.width, image.height); + }); } @pragma('vm:entry-point') void canAccessIsolateLaunchData() { - notifyMessage( - utf8.decode( - PlatformDispatcher.instance.getPersistentIsolateData().buffer.asUint8List(), - ), - ); + notifyMessage(utf8.decode(window.getPersistentIsolateData().buffer.asUint8List())); } void notifyMessage(String string) native 'NotifyMessage'; @@ -152,12 +145,9 @@ void sendFixtureMapping(List list) native 'SendFixtureMapping'; @pragma('vm:entry-point') void canDecompressImageFromAsset() { - decodeImageFromList( - Uint8List.fromList(getFixtureImage()), - (Image result) { - notifyWidthHeight(result.width, result.height); - }, - ); + decodeImageFromList(Uint8List.fromList(getFixtureImage()), (Image result) { + notifyWidthHeight(result.width, result.height); + }); } List getFixtureImage() native 'GetFixtureImage'; diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index aadfd6db0e683..17d0eaf6e50ae 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -5,6 +5,7 @@ #define FML_USED_ON_EMBEDDER #include "flutter/shell/common/shell_test.h" +#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/transform_layer.h" @@ -12,7 +13,6 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/runtime/dart_vm.h" -#include "flutter/shell/common/shell_test_platform_view.h" #include "flutter/shell/common/vsync_waiter_fallback.h" #include "flutter/testing/testing.h" diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 904d8611a4471..fbf0647eb1191 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -1327,7 +1327,7 @@ TEST_F(ShellTest, WaitForFirstFrameInlined) { DestroyShell(std::move(shell), std::move(task_runners)); } -static size_t GetRasterizerResourceCacheBytesSync(const Shell& shell) { +static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) { size_t bytes = 0; fml::AutoResetWaitableEvent latch; fml::TaskRunner::RunNowOrPostTask( diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 8c25da13d9e2d..48d9243cf86c2 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1168,6 +1168,7 @@ public void removeFlutterEngineAttachmentListener( .send(); } + // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI private void sendViewportMetricsToFlutter() { if (!isAttachedToFlutterEngine()) { Log.w( diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index d653a9eccb79d..3b8a61bf39c0c 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -6,6 +6,7 @@ #include #include + #include #include "unicode/uchar.h" @@ -35,9 +36,8 @@ namespace flutter { namespace { bool CheckException(JNIEnv* env) { - if (env->ExceptionCheck() == JNI_FALSE) { + if (env->ExceptionCheck() == JNI_FALSE) return true; - } jthrowable exception = env->ExceptionOccurred(); env->ExceptionClear(); @@ -1174,10 +1174,10 @@ void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView( } case clip_rect: { const SkRect& rect = (*iter)->GetRect(); - env->CallVoidMethod( - mutatorsStack, g_mutators_stack_push_cliprect_method, - static_cast(rect.left()), static_cast(rect.top()), - static_cast(rect.right()), static_cast(rect.bottom())); + env->CallVoidMethod(mutatorsStack, + g_mutators_stack_push_cliprect_method, + (int)rect.left(), (int)rect.top(), + (int)rect.right(), (int)rect.bottom()); break; } // TODO(cyanglaz): Implement other mutators. @@ -1301,16 +1301,16 @@ PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale( } fml::jni::ScopedJavaLocalRef j_locales_data = fml::jni::VectorToStringArray(env, supported_locales_data); - jobjectArray result = static_cast(env->CallObjectMethod( + jobjectArray result = (jobjectArray)env->CallObjectMethod( java_object.obj(), g_compute_platform_resolved_locale_method, - j_locales_data.obj())); + j_locales_data.obj()); FML_CHECK(CheckException(env)); int length = env->GetArrayLength(result); for (int i = 0; i < length; i++) { out->emplace_back(fml::jni::JavaStringToString( - env, static_cast(env->GetObjectArrayElement(result, i)))); + env, (jstring)env->GetObjectArrayElement(result, i))); } return out; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 99c8f75c419e4..ed7e1d79fd56d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -923,18 +923,18 @@ - (CGFloat)statusBarPadding { } - (void)viewDidLayoutSubviews { - CGRect viewBounds = self.view.bounds; + CGSize viewSize = self.view.bounds.size; CGFloat scale = [UIScreen mainScreen].scale; // Purposefully place this not visible. - _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0); + _scrollView.get().frame = CGRectMake(0.0, 0.0, viewSize.width, 0.0); _scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize); // First time since creation that the dimensions of its view is known. bool firstViewBoundsUpdate = !_viewportMetrics.physical_width; _viewportMetrics.device_pixel_ratio = scale; - _viewportMetrics.physical_width = viewBounds.size.width * scale; - _viewportMetrics.physical_height = viewBounds.size.height * scale; + _viewportMetrics.physical_width = viewSize.width * scale; + _viewportMetrics.physical_height = viewSize.height * scale; [self updateViewportPadding]; [self updateViewportMetrics]; @@ -1105,18 +1105,14 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification { } auto platformView = [_engine.get() platformView]; int32_t flags = 0; - if (UIAccessibilityIsInvertColorsEnabled()) { + if (UIAccessibilityIsInvertColorsEnabled()) flags |= static_cast(flutter::AccessibilityFeatureFlag::kInvertColors); - } - if (UIAccessibilityIsReduceMotionEnabled()) { + if (UIAccessibilityIsReduceMotionEnabled()) flags |= static_cast(flutter::AccessibilityFeatureFlag::kReduceMotion); - } - if (UIAccessibilityIsBoldTextEnabled()) { + if (UIAccessibilityIsBoldTextEnabled()) flags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); - } - if (UIAccessibilityDarkerSystemColorsEnabled()) { + if (UIAccessibilityDarkerSystemColorsEnabled()) flags |= static_cast(flutter::AccessibilityFeatureFlag::kHighContrast); - } #if TARGET_OS_SIMULATOR // There doesn't appear to be any way to determine whether the accessibility // inspector is enabled on the simulator. We conservatively always turn on the diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 997c42b2e8f19..b5356181e3bce 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -411,19 +411,16 @@ - (void)updateWindowMetrics { return; } NSView* view = _viewController.view; - CGRect scaledBounds = [view convertRectToBacking:view.bounds]; - CGSize scaledSize = scaledBounds.size; + CGSize scaledSize = [view convertRectToBacking:view.bounds].size; double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; - const FlutterWindowMetricsEvent windowMetricsEvent = { - .struct_size = sizeof(windowMetricsEvent), + const FlutterWindowMetricsEvent event = { + .struct_size = sizeof(event), .width = static_cast(scaledSize.width), .height = static_cast(scaledSize.height), .pixel_ratio = pixelRatio, - .left = static_cast(scaledBounds.origin.x), - .top = static_cast(scaledBounds.origin.y), }; - FlutterEngineSendWindowMetricsEvent(_engine, &windowMetricsEvent); + FlutterEngineSendWindowMetricsEvent(_engine, &event); } - (void)sendPointerEvent:(const FlutterPointerEvent&)event { diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index dfb880c0aaea5..06b45ac5372e1 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1,6 +1,8 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// FLUTTER_NOLINT +// FLUTTER_NOLINT #define FML_USED_ON_EMBEDDER #define RAPIDJSON_HAS_STDSTRING 1 @@ -659,9 +661,8 @@ FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { return kSuccess; } -void PopulateSnapshotMappingCallbacks( - const FlutterProjectArgs* args, - flutter::Settings& settings) { // NOLINT(google-runtime-references) +void PopulateSnapshotMappingCallbacks(const FlutterProjectArgs* args, + flutter::Settings& settings) { // There are no ownership concerns here as all mappings are owned by the // embedder and not the engine. auto make_mapping_callback = [](const uint8_t* mapping, size_t size) { diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index f2cf40f914b73..51d832bfe5397 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -461,10 +461,6 @@ typedef struct { size_t height; /// Scale factor for the physical screen. double pixel_ratio; - /// Horizontal physical location of the left side of the window on the screen. - size_t left; - /// Vertical physical location of the top of the window on the screen. - size_t top; } FlutterWindowMetricsEvent; /// The phase of the pointer event. diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index e662257977683..5dae4e494745c 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -34,7 +34,7 @@ void sayHiFromCustomEntrypoint3() native 'SayHiFromCustomEntrypoint3'; @pragma('vm:entry-point') void invokePlatformTaskRunner() { - PlatformDispatcher.instance.sendPlatformMessage('OhHi', null, null); + window.sendPlatformMessage('OhHi', null, null); } @@ -59,19 +59,19 @@ void notifySemanticsEnabled(bool enabled) native 'NotifySemanticsEnabled'; void notifyAccessibilityFeatures(bool reduceMotion) native 'NotifyAccessibilityFeatures'; void notifySemanticsAction(int nodeId, int action, List data) native 'NotifySemanticsAction'; -/// Returns a future that completes when -/// `PlatformDispatcher.instance.onSemanticsEnabledChanged` fires. +/// Returns a future that completes when `window.onSemanticsEnabledChanged` +/// fires. Future get semanticsChanged { final Completer semanticsChanged = Completer(); - PlatformDispatcher.instance.onSemanticsEnabledChanged = semanticsChanged.complete; + window.onSemanticsEnabledChanged = semanticsChanged.complete; return semanticsChanged.future; } -/// Returns a future that completes when -/// `PlatformDispatcher.instance.onAccessibilityFeaturesChanged` fires. +/// Returns a future that completes when `window.onAccessibilityFeaturesChanged` +/// fires. Future get accessibilityFeaturesChanged { final Completer featuresChanged = Completer(); - PlatformDispatcher.instance.onAccessibilityFeaturesChanged = featuresChanged.complete; + window.onAccessibilityFeaturesChanged = featuresChanged.complete; return featuresChanged.future; } @@ -84,7 +84,7 @@ class SemanticsActionData { Future get semanticsAction { final Completer actionReceived = Completer(); - PlatformDispatcher.instance.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { + window.onSemanticsAction = (int id, SemanticsAction action, ByteData args) { actionReceived.complete(SemanticsActionData(id, action, args)); }; return actionReceived.future; @@ -93,18 +93,18 @@ Future get semanticsAction { @pragma('vm:entry-point') void a11y_main() async { // ignore: non_constant_identifier_names // Return initial state (semantics disabled). - notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); + notifySemanticsEnabled(window.semanticsEnabled); // Await semantics enabled from embedder. await semanticsChanged; - notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); + notifySemanticsEnabled(window.semanticsEnabled); // Return initial state of accessibility features. - notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); // Await accessibility features changed from embedder. await accessibilityFeaturesChanged; - notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); + notifyAccessibilityFeatures(window.accessibilityFeatures.reduceMotion); // Fire semantics update. final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder() @@ -143,7 +143,7 @@ void a11y_main() async { // ignore: non_constant_identifier_names label: 'Archive', hint: 'archive message', ); - PlatformDispatcher.instance.updateSemantics(builder.build()); + window.updateSemantics(builder.build()); signalNativeTest(); // Await semantics action from embedder. @@ -153,13 +153,13 @@ void a11y_main() async { // ignore: non_constant_identifier_names // Await semantics disabled from embedder. await semanticsChanged; - notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); + notifySemanticsEnabled(window.semanticsEnabled); } @pragma('vm:entry-point') void platform_messages_response() { - PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { callback(data); }; signalNativeTest(); @@ -167,7 +167,7 @@ void platform_messages_response() { @pragma('vm:entry-point') void platform_messages_no_response() { - PlatformDispatcher.instance.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { var list = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); signalNativeMessage(utf8.decode(list)); // This does nothing because no one is listening on the other side. But complete the loop anyway @@ -179,7 +179,7 @@ void platform_messages_no_response() { @pragma('vm:entry-point') void null_platform_messages() { - PlatformDispatcher.instance.onPlatformMessage = + window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { // This checks if the platform_message null data is converted to Flutter null. signalNativeMessage((null == data).toString()); @@ -198,7 +198,7 @@ Picture CreateSimplePicture() { @pragma('vm:entry-point') void can_composite_platform_views() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pushOffset(1.0, 2.0); @@ -206,15 +206,15 @@ void can_composite_platform_views() { builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_opacity() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Root node @@ -236,24 +236,24 @@ void can_composite_platform_views_with_opacity() { builder.pop(); signalNativeTest(); // Signal 2 - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_with_opacity() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOpacity(127); builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); builder.pop(); // offset signalNativeTest(); // Signal 2 - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } Picture CreateColoredBox(Color color, Size size) { @@ -267,7 +267,7 @@ Picture CreateColoredBox(Color color, Size size) { @pragma('vm:entry-point') void can_composite_platform_views_with_known_scene() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color blue = Color.fromARGB(127, 0, 0, 255); Color gray = Color.fromARGB(127, 127, 127, 127); @@ -298,17 +298,17 @@ void can_composite_platform_views_with_known_scene() { builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_root_layer_only() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -319,17 +319,17 @@ void can_composite_platform_views_with_root_layer_only() { builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_composite_platform_views_with_platform_layer_on_bottom() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Size size = Size(50.0, 150.0); @@ -345,17 +345,17 @@ void can_composite_platform_views_with_platform_layer_on_bottom() { builder.pop(); builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); signalNativeTest(); // Signal 2 }; signalNativeTest(); // Signal 1 - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_render_scene_without_custom_compositor() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Color red = Color.fromARGB(127, 255, 0, 0); Color green = Color.fromARGB(127, 0, 255, 0); Color blue = Color.fromARGB(127, 0, 0, 255); @@ -380,9 +380,9 @@ void can_render_scene_without_custom_compositor() { builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } Picture CreateGradientBox(Size size) { @@ -417,7 +417,7 @@ Picture CreateGradientBox(Size size) { @pragma('vm:entry-point') void render_gradient() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); SceneBuilder builder = SceneBuilder(); @@ -428,14 +428,14 @@ void render_gradient() { builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void render_gradient_on_non_root_backing_store() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { Size size = Size(800.0, 600.0); Color red = Color.fromARGB(127, 255, 0, 0); @@ -452,14 +452,14 @@ void render_gradient_on_non_root_backing_store() { builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void verify_b141980393() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { // The platform view in the test case is screen sized but with margins of 31 // and 37 points from the top and bottom. double top_margin = 31.0; @@ -478,14 +478,14 @@ void verify_b141980393() { builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_display_platform_view_with_pixel_ratio() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushTransform(Float64List.fromList([ 2.0, 0.0, 0.0, 0.0, @@ -499,15 +499,15 @@ void can_display_platform_view_with_pixel_ratio() { builder.pop(); // offset builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(128, 255, 0, 0), Size(400.0, 300.0))); builder.pop(); // base - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void can_receive_locale_updates() { - PlatformDispatcher.instance.onLocaleChanged = (){ - signalNativeCount(PlatformDispatcher.instance.locales.length); + window.onLocaleChanged = (){ + signalNativeCount(window.locales.length); }; signalNativeTest(); } @@ -515,7 +515,7 @@ void can_receive_locale_updates() { // Verifies behavior tracked in https://github.com/flutter/flutter/issues/43732 @pragma('vm:entry-point') void verify_b143464703() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base @@ -542,14 +542,14 @@ void verify_b143464703() { builder.pop(); // 1 builder.pop(); // base - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void push_frames_over_and_over() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); builder.addPicture(Offset(0.0, 0.0), CreateColoredBox(Color.fromARGB(255, 128, 128, 128), Size(1024.0, 600.0))); @@ -557,17 +557,17 @@ void push_frames_over_and_over() { builder.addPlatformView(42, width: 1024.0, height: 540.0); builder.pop(); builder.pop(); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); signalNativeTest(); - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(800.0, 600.0))); @@ -581,14 +581,14 @@ void platform_view_mutators() { builder.pop(); // opacity builder.pop(); // base - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void platform_view_mutators_with_pixel_ratio() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushOffset(0.0, 0.0); // base builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); @@ -602,29 +602,29 @@ void platform_view_mutators_with_pixel_ratio() { builder.pop(); // opacity builder.pop(); // base - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void empty_scene() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { - PlatformDispatcher.instance.views.first.render(SceneBuilder().build()); + window.onBeginFrame = (Duration duration) { + window.render(SceneBuilder().build()); signalNativeTest(); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void scene_with_no_container() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); signalNativeTest(); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } Picture CreateArcEndCapsPicture() { @@ -646,29 +646,29 @@ Picture CreateArcEndCapsPicture() { @pragma('vm:entry-point') void arc_end_caps_correct() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPicture(Offset(0.0, 0.0), CreateArcEndCapsPicture()); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_clips() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(10.0, 10.0, 390.0, 290.0)); builder.addPlatformView(42, width: 400.0, height: 300.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(400.0, 300.0))); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void scene_builder_with_complex_clips() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 1024.0, 600.0)); @@ -679,9 +679,9 @@ void scene_builder_with_complex_clips() { builder.addPlatformView(42, width: 1024.0, height: 600.0); builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(1024.0, 600.0))); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } void sendObjectToNativeCode(dynamic object) native 'SendObjectToNativeCode'; @@ -695,43 +695,43 @@ void objects_can_be_posted() { @pragma('vm:entry-point') void empty_scene_posts_zero_layers_to_compositor() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); // Should not render anything. builder.pushClipRect(Rect.fromLTRB(0.0, 0.0, 300.0, 200.0)); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void compositor_can_post_only_platform_views() { - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); builder.addPlatformView(42, width: 300.0, height: 200.0); builder.addPlatformView(24, width: 300.0, height: 200.0); - PlatformDispatcher.instance.views.first.render(builder.build()); + window.render(builder.build()); }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } @pragma('vm:entry-point') void render_targets_are_recycled() { int frame_count = 0; - PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + window.onBeginFrame = (Duration duration) { SceneBuilder builder = SceneBuilder(); for (int i = 0; i < 10; i++) { builder.addPicture(Offset(0.0, 0.0), CreateGradientBox(Size(30.0, 20.0))); builder.addPlatformView(42 + i, width: 30.0, height: 20.0); } - PlatformDispatcher.instance.views.first.render(builder.build()); - PlatformDispatcher.instance.scheduleFrame(); + window.render(builder.build()); + window.scheduleFrame(); frame_count++; if (frame_count == 8) { signalNativeTest(); } }; - PlatformDispatcher.instance.scheduleFrame(); + window.scheduleFrame(); } void nativeArgumentsCallback(List args) native 'NativeArgumentsCallback'; diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index ab87370927149..21eda23623664 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -27,7 +27,6 @@ part '../../lib/ui/hooks.dart'; part '../../lib/ui/lerp.dart'; part '../../lib/ui/natives.dart'; part '../../lib/ui/painting.dart'; -part '../../lib/ui/platform_dispatcher.dart'; part '../../lib/ui/pointer.dart'; part '../../lib/ui/semantics.dart'; part '../../lib/ui/text.dart'; @@ -45,22 +44,15 @@ void main() { PlatformMessageCallback? originalOnPlatformMessage; VoidCallback? originalOnTextScaleFactorChanged; - Object? oldWindowId; - double? oldDevicePixelRatio; - Rect? oldGeometry; - - WindowPadding? oldPadding; - WindowPadding? oldInsets; - WindowPadding? oldSystemGestureInsets; + double? oldDPR; + Size? oldSize; + WindowPadding? oldPadding; + WindowPadding? oldInsets; + WindowPadding? oldSystemGestureInsets; void setUp() { - PlatformDispatcher.instance._viewConfigurations.clear(); - PlatformDispatcher.instance._views.clear(); - PlatformDispatcher.instance._viewConfigurations[0] = const ViewConfiguration(); - PlatformDispatcher.instance._views[0] = FlutterWindow._(0, PlatformDispatcher.instance); - oldWindowId = window._windowId; - oldDevicePixelRatio = window.devicePixelRatio; - oldGeometry = window.viewConfiguration.geometry; + oldDPR = window.devicePixelRatio; + oldSize = window.physicalSize; oldPadding = window.viewPadding; oldInsets = window.viewInsets; oldSystemGestureInsets = window.systemGestureInsets; @@ -77,35 +69,34 @@ void main() { originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged; } - tearDown() { - _updateWindowMetrics( - oldWindowId!, // window id - oldDevicePixelRatio!, // device pixel ratio - oldGeometry!.width, // width - oldGeometry!.height, // height - oldPadding!.top, // padding top - oldPadding!.right, // padding right - oldPadding!.bottom, // padding bottom - oldPadding!.left, // padding left - oldInsets!.top, // inset top - oldInsets!.right, // inset right - oldInsets!.bottom, // inset bottom - oldInsets!.left, // inset left - oldSystemGestureInsets!.top, // system gesture inset top - oldSystemGestureInsets!.right, // system gesture inset right - oldSystemGestureInsets!.bottom, // system gesture inset bottom - oldSystemGestureInsets!.left, // system gesture inset left - ); - window.onMetricsChanged = originalOnMetricsChanged; - window.onLocaleChanged = originalOnLocaleChanged; - window.onBeginFrame = originalOnBeginFrame; - window.onDrawFrame = originalOnDrawFrame; - window.onReportTimings = originalOnReportTimings; - window.onPointerDataPacket = originalOnPointerDataPacket; - window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; - window.onSemanticsAction = originalOnSemanticsAction; - window.onPlatformMessage = originalOnPlatformMessage; - window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; + void tearDown() { + _updateWindowMetrics( + oldDPR!, // DPR + oldSize!.width, // width + oldSize!.height, // height + oldPadding!.top, // padding top + oldPadding!.right, // padding right + oldPadding!.bottom, // padding bottom + oldPadding!.left, // padding left + oldInsets!.top, // inset top + oldInsets!.right, // inset right + oldInsets!.bottom, // inset bottom + oldInsets!.left, // inset left + oldSystemGestureInsets!.top, // system gesture inset top + oldSystemGestureInsets!.right, // system gesture inset right + oldSystemGestureInsets!.bottom, // system gesture inset bottom + oldSystemGestureInsets!.left, // system gesture inset left + ); + window.onMetricsChanged = originalOnMetricsChanged; + window.onLocaleChanged = originalOnLocaleChanged; + window.onBeginFrame = originalOnBeginFrame; + window.onDrawFrame = originalOnDrawFrame; + window.onReportTimings = originalOnReportTimings; + window.onPointerDataPacket = originalOnPointerDataPacket; + window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; + window.onSemanticsAction = originalOnSemanticsAction; + window.onPlatformMessage = originalOnPlatformMessage; + window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; } void test(String description, void Function() testFunction) { @@ -164,8 +155,7 @@ void main() { window.onMetricsChanged!(); _updateWindowMetrics( - 0, // window id - 0.1234, // device pixel ratio + 0.1234, // DPR 0.0, // width 0.0, // height 0.0, // padding top @@ -244,7 +234,7 @@ void main() { late Zone innerZone; late Zone runZone; - PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; + window._setNeedsReportTimings = (bool _) {}; runZoned(() { innerZone = Zone.current; @@ -275,7 +265,7 @@ void main() { _dispatchPointerDataPacket(testData); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); - expectIterablesEqual(data.data, PlatformDispatcher._unpackPointerDataPacket(testData).data); + expectIterablesEqual(data.data, _unpackPointerDataPacket(testData).data); }); test('onSemanticsEnabledChanged preserves callback zone', () { @@ -291,12 +281,11 @@ void main() { }; }); - final bool newValue = !window.semanticsEnabled; // needed? - _updateSemanticsEnabled(newValue); + _updateSemanticsEnabled(window._semanticsEnabled); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); expectNotEquals(enabled, null); - expectEquals(enabled, newValue); + expectEquals(enabled, window._semanticsEnabled); }); test('onSemanticsAction preserves callback zone', () { @@ -342,45 +331,47 @@ void main() { test('onTextScaleFactorChanged preserves callback zone', () { late Zone innerZone; - late Zone runZoneTextScaleFactor; - late Zone runZonePlatformBrightness; - late double? textScaleFactor; - late Brightness? platformBrightness; + late Zone runZone; + late double textScaleFactor; runZoned(() { innerZone = Zone.current; window.onTextScaleFactorChanged = () { - runZoneTextScaleFactor = Zone.current; + runZone = Zone.current; textScaleFactor = window.textScaleFactor; }; - window.onPlatformBrightnessChanged = () { - runZonePlatformBrightness = Zone.current; - platformBrightness = window.platformBrightness; - }; }); window.onTextScaleFactorChanged!(); - - _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); - expectNotEquals(runZoneTextScaleFactor, null); - expectIdentical(runZoneTextScaleFactor, innerZone); + _updateTextScaleFactor(0.5); + expectNotEquals(runZone, null); + expectIdentical(runZone, innerZone); expectEquals(textScaleFactor, 0.5); + }); + + test('onThemeBrightnessMode preserves callback zone', () { + late Zone innerZone; + late Zone runZone; + late Brightness platformBrightness; - textScaleFactor = null; - platformBrightness = null; + runZoned(() { + innerZone = Zone.current; + window.onPlatformBrightnessChanged = () { + runZone = Zone.current; + platformBrightness = window.platformBrightness; + }; + }); window.onPlatformBrightnessChanged!(); - _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); - expectNotEquals(runZonePlatformBrightness, null); - expectIdentical(runZonePlatformBrightness, innerZone); + _updatePlatformBrightness('dark'); + expectNotEquals(runZone, null); + expectIdentical(runZone, innerZone); expectEquals(platformBrightness, Brightness.dark); - }); test('Window padding/insets/viewPadding/systemGestureInsets', () { _updateWindowMetrics( - 0, // window id - 0, // screen id + 1.0, // DPR 800.0, // width 600.0, // height 50.0, // padding top @@ -403,8 +394,7 @@ void main() { expectEquals(window.systemGestureInsets.bottom, 0.0); _updateWindowMetrics( - 0, // window id - 0, // screen id + 1.0, // DPR 800.0, // width 600.0, // height 50.0, // padding top @@ -415,10 +405,10 @@ void main() { 0.0, // inset right 400.0, // inset bottom 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 44.0, // system gesture inset bottom - 0.0, // system gesture inset left + 0.0, // system gesture insets top + 0.0, // system gesture insets right + 44.0, // system gesture insets bottom + 0.0, // system gesture insets left ); expectEquals(window.viewInsets.bottom, 400.0); diff --git a/testing/scenario_app/lib/src/animated_color_square.dart b/testing/scenario_app/lib/src/animated_color_square.dart index b85f20d4d442b..f1e9865f55309 100644 --- a/testing/scenario_app/lib/src/animated_color_square.dart +++ b/testing/scenario_app/lib/src/animated_color_square.dart @@ -16,10 +16,10 @@ import 'scenario.dart'; class AnimatedColorSquareScenario extends Scenario { /// Creates the AnimatedColorSquare scenario. /// - /// The [dispatcher] parameter must not be null. - AnimatedColorSquareScenario(PlatformDispatcher dispatcher) - : assert(dispatcher != null), - super(dispatcher); + /// The [window] parameter must not be null. + AnimatedColorSquareScenario(Window window) + : assert(window != null), + super(window); static const double _squareSize = 200; /// Used to animate the red value in the color of the square. diff --git a/testing/scenario_app/lib/src/channel_util.dart b/testing/scenario_app/lib/src/channel_util.dart index 24f05f2fc0658..986494f5ff4e9 100644 --- a/testing/scenario_app/lib/src/channel_util.dart +++ b/testing/scenario_app/lib/src/channel_util.dart @@ -10,13 +10,13 @@ import 'package:meta/meta.dart'; /// Util method to replicate the behavior of a `MethodChannel` in the Flutter /// framework. void sendJsonMethodCall({ - @required PlatformDispatcher dispatcher, + @required Window window, @required String channel, @required String method, dynamic arguments, PlatformMessageResponseCallback callback, }) { - dispatcher.sendPlatformMessage( + window.sendPlatformMessage( channel, // This recreates a combination of OptionalMethodChannel, JSONMethodCodec, // and _DefaultBinaryMessenger in the framework. diff --git a/testing/scenario_app/lib/src/initial_route_reply.dart b/testing/scenario_app/lib/src/initial_route_reply.dart index ffabb8d072ced..c42e7608da93d 100644 --- a/testing/scenario_app/lib/src/initial_route_reply.dart +++ b/testing/scenario_app/lib/src/initial_route_reply.dart @@ -15,14 +15,14 @@ class InitialRouteReply extends Scenario { /// Creates the InitialRouteReply. /// /// The [window] parameter must not be null. - InitialRouteReply(PlatformDispatcher dispatcher) - : assert(dispatcher != null), - super(dispatcher); + InitialRouteReply(Window window) + : assert(window != null), + super(window); @override void onBeginFrame(Duration duration) { sendJsonMethodCall( - dispatcher: dispatcher, + window: window, channel: 'initial_route_test_channel', method: window.defaultRouteName, ); diff --git a/testing/scenario_app/lib/src/locale_initialization.dart b/testing/scenario_app/lib/src/locale_initialization.dart index 7d9faf6415f7a..6157de30be903 100644 --- a/testing/scenario_app/lib/src/locale_initialization.dart +++ b/testing/scenario_app/lib/src/locale_initialization.dart @@ -12,9 +12,9 @@ import 'scenario.dart'; /// Sends the recieved locale data back as semantics information. class LocaleInitialization extends Scenario { /// Constructor - LocaleInitialization(PlatformDispatcher dispatcher) - : assert(dispatcher != null), - super(dispatcher); + LocaleInitialization(Window window) + : assert(window != null), + super(window); int _tapCount = 0; @@ -82,11 +82,10 @@ class LocaleInitialization extends Scenario { /// Send changing information via semantics on each successive tap. @override void onPointerDataPacket(PointerDataPacket packet) { - String label = ''; + String label; switch(_tapCount) { case 1: { // Set label to string data we wish to pass on first frame. - label = '1'; break; } // Expand for other test cases. diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index fd6f5f47887e9..e9802438eb5e4 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -32,11 +32,11 @@ List _to64(num value) { class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -56,11 +56,11 @@ class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewNoOverlayIntersectionScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -84,11 +84,11 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatf class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewPartialIntersectionScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewPartialIntersectionScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier . @@ -112,11 +112,11 @@ class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatfor class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -128,7 +128,7 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla builder.pushOffset(0, 0); - _addPlatformViewToScene(builder, id, 500, 500); + _addPlatformViewtoScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -153,11 +153,11 @@ class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePla class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewOneOverlayTwoIntersectingOverlaysScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -169,7 +169,7 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit builder.pushOffset(0, 0); - _addPlatformViewToScene(builder, id, 500, 500); + _addPlatformViewtoScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -199,12 +199,12 @@ class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario wit class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.firstId, this.secondId }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, firstId); - createPlatformView(dispatcher, text, secondId); + /// The [window] parameter must not be null. + MultiPlatformViewWithoutOverlaysScenario(Window window, String text, { this.firstId, this.secondId }) + : assert(window != null), + super(window) { + createPlatformView(window, text, firstId); + createPlatformView(window, text, secondId); } /// The platform view identifier to use for the first platform view. @@ -220,10 +220,10 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewToScene(builder, firstId, 500, 500); + _addPlatformViewtoScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewToScene(builder, secondId, 500, 500); + _addPlatformViewtoScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -245,11 +245,11 @@ class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatfo class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewMaxOverlaysScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + /// The [window] parameter must not be null. + PlatformViewMaxOverlaysScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -261,7 +261,7 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce builder.pushOffset(0, 0); - _addPlatformViewToScene(builder, id, 500, 500); + _addPlatformViewtoScene(builder, id, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( @@ -296,12 +296,12 @@ class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewSce class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - MultiPlatformViewScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, 'platform view 1', firstId); - createPlatformView(dispatcher, 'platform view 2', secondId); + /// The [window] parameter must not be null. + MultiPlatformViewScenario(Window window, {this.firstId, this.secondId}) + : assert(window != null), + super(window) { + createPlatformView(window, 'platform view 1', firstId); + createPlatformView(window, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -317,7 +317,7 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewToScene(builder, firstId, 500, 500); + _addPlatformViewtoScene(builder, firstId, 500, 500); builder.pop(); finishBuilderByAddingPlatformViewAndPicture(builder, secondId); @@ -332,13 +332,13 @@ class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioM class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher dispatcher, {this.firstId, this.secondId}) - : assert(dispatcher != null), - super(dispatcher) { + /// The [window] parameter must not be null. + MultiPlatformViewBackgroundForegroundScenario(Window window, {this.firstId, this.secondId}) + : assert(window != null), + super(window) { _nextFrame = _firstFrame; - createPlatformView(dispatcher, 'platform view 1', firstId); - createPlatformView(dispatcher, 'platform view 2', secondId); + createPlatformView(window, 'platform view 1', firstId); + createPlatformView(window, 'platform view 2', secondId); } /// The platform view identifier to use for the first platform view. @@ -360,10 +360,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewToScene(builder, firstId, 500, 500); + _addPlatformViewtoScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewToScene(builder, secondId, 500, 500); + _addPlatformViewtoScene(builder, secondId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -386,10 +386,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP builder.pushOffset(0, 0); builder.pushOffset(0, 600); - _addPlatformViewToScene(builder, firstId, 500, 500); + _addPlatformViewtoScene(builder, firstId, 500, 500); builder.pop(); - _addPlatformViewToScene(builder, secondId, 500, 500); + _addPlatformViewtoScene(builder, secondId, 500, 500); final Scene scene = builder.build(); window.render(scene); scene.dispose(); @@ -419,10 +419,10 @@ class MultiPlatformViewBackgroundForegroundScenario extends Scenario with _BaseP /// Platform view with clip rect. class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Constructs a platform view with clip rect scenario. - PlatformViewClipRectScenario(PlatformDispatcher dispatcher, String text, { this.id }) - : assert(dispatcher != null), - super(dispatcher) { - createPlatformView(dispatcher, text, id); + PlatformViewClipRectScenario(Window window, String text, { this.id }) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); } /// The platform view identifier. @@ -440,8 +440,8 @@ class PlatformViewClipRectScenario extends Scenario with _BasePlatformViewScenar /// Platform view with clip rrect. class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipRRectScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) - : super(dispatcher, text, id: id); + PlatformViewClipRRectScenario(Window window, String text, { int id = 0 }) + : super(window, text, id: id); @override void onBeginFrame(Duration duration) { @@ -466,8 +466,8 @@ class PlatformViewClipRRectScenario extends PlatformViewScenario { /// Platform view with clip path. class PlatformViewClipPathScenario extends PlatformViewScenario { /// Constructs a platform view with clip rrect scenario. - PlatformViewClipPathScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) - : super(dispatcher, text, id: id); + PlatformViewClipPathScenario(Window window, String text, { int id = 0 }) + : super(window, text, id: id); @override void onBeginFrame(Duration duration) { @@ -490,8 +490,8 @@ class PlatformViewClipPathScenario extends PlatformViewScenario { /// Platform view with transform. class PlatformViewTransformScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewTransformScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) - : super(dispatcher, text, id: id); + PlatformViewTransformScenario(Window window, String text, { int id = 0 }) + : super(window, text, id: id); @override void onBeginFrame(Duration duration) { @@ -512,8 +512,8 @@ class PlatformViewTransformScenario extends PlatformViewScenario { /// Platform view with opacity. class PlatformViewOpacityScenario extends PlatformViewScenario { /// Constructs a platform view with transform scenario. - PlatformViewOpacityScenario(PlatformDispatcher dispatcher, String text, { int id = 0 }) - : super(dispatcher, text, id: id); + PlatformViewOpacityScenario(Window window, String text, { int id = 0 }) + : super(window, text, id: id); @override void onBeginFrame(Duration duration) { @@ -536,16 +536,16 @@ class PlatformViewForTouchIOSScenario extends Scenario VoidCallback _nextFrame; /// Creates the PlatformView scenario. /// - /// The [dispatcher] parameter must not be null. - PlatformViewForTouchIOSScenario(PlatformDispatcher dispatcher, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) - : assert(dispatcher != null), + /// The [window] parameter must not be null. + PlatformViewForTouchIOSScenario(Window window, String text, {int id = 0, bool accept, bool rejectUntilTouchesEnded = false}) + : assert(window != null), _accept = accept, _viewId = id, - super(dispatcher) { + super(window) { if (rejectUntilTouchesEnded) { - createPlatformView(dispatcher, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); + createPlatformView(window, text, id, viewType: 'scenarios/textPlatformView_blockPolicyUntilTouchesEnded'); } else { - createPlatformView(dispatcher, text, id); + createPlatformView(window, text, id); } _nextFrame = _firstFrame; } @@ -625,7 +625,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { /// It prepare a TextPlatformView so it can be added to the SceneBuilder in `onBeginFrame`. /// Call this method in the constructor of the platform view related scenarios /// to perform necessary set up. - void createPlatformView(PlatformDispatcher dispatcher, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { + void createPlatformView(Window window, String text, int id, {String viewType = 'scenarios/textPlatformView'}) { const int _valueTrue = 1; const int _valueInt32 = 3; const int _valueFloat64 = 6; @@ -691,7 +691,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ...utf8.encode(text), ]); - dispatcher.sendPlatformMessage( + window.sendPlatformMessage( 'flutter/platform_views', message.buffer.asByteData(), (ByteData response) { @@ -703,7 +703,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { ); } - void _addPlatformViewToScene( + void _addPlatformViewtoScene( SceneBuilder sceneBuilder, int viewId, double width, @@ -729,7 +729,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { Offset overlayOffset, }) { overlayOffset ??= const Offset(50, 50); - _addPlatformViewToScene( + _addPlatformViewtoScene( sceneBuilder, viewId, 500, diff --git a/testing/scenario_app/lib/src/poppable_screen.dart b/testing/scenario_app/lib/src/poppable_screen.dart index f53cce7890b66..6ecd655b02995 100644 --- a/testing/scenario_app/lib/src/poppable_screen.dart +++ b/testing/scenario_app/lib/src/poppable_screen.dart @@ -14,10 +14,10 @@ import 'scenario.dart'; class PoppableScreenScenario extends Scenario with PlatformEchoMixin { /// Creates the PoppableScreenScenario. /// - /// The [dispatcher] parameter must not be null. - PoppableScreenScenario(PlatformDispatcher dispatcher) - : assert(dispatcher != null), - super(dispatcher); + /// The [window] parameter must not be null. + PoppableScreenScenario(Window window) + : assert(window != null), + super(window); // Rect for the pop button. Only defined once onMetricsChanged is called. Rect _buttonRect; @@ -74,7 +74,7 @@ class PoppableScreenScenario extends Scenario with PlatformEchoMixin { void _pop() { sendJsonMethodCall( - dispatcher: dispatcher, + window: window, // 'flutter/platform' is the hardcoded name of the 'platform' // `SystemChannel` from the `SystemNavigator` API. // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/system_navigator.dart. diff --git a/testing/scenario_app/lib/src/scenario.dart b/testing/scenario_app/lib/src/scenario.dart index 94319e692ea0e..ccf2be02778de 100644 --- a/testing/scenario_app/lib/src/scenario.dart +++ b/testing/scenario_app/lib/src/scenario.dart @@ -8,31 +8,31 @@ import 'dart:ui'; /// A scenario to run for testing. abstract class Scenario { - /// Creates a new scenario using a specific FlutterWindow instance. - Scenario(this.dispatcher); + /// Creates a new scenario using a specific Window instance. + Scenario(this.window); /// The window used by this scenario. May be mocked. - final PlatformDispatcher dispatcher; + final Window window; /// [true] if a screenshot is taken in the next frame. bool _didScheduleScreenshot = false; /// Called by the program when a frame is ready to be drawn. /// - /// See [PlatformDispatcher.onBeginFrame] for more details. + /// See [Window.onBeginFrame] for more details. void onBeginFrame(Duration duration) {} /// Called by the program when the microtasks from [onBeginFrame] have been /// flushed. /// - /// See [PlatformDispatcher.onDrawFrame] for more details. + /// See [Window.onDrawFrame] for more details. void onDrawFrame() { Future.delayed(const Duration(seconds: 1), () { if (_didScheduleScreenshot) { - dispatcher.sendPlatformMessage('take_screenshot', null, null); + window.sendPlatformMessage('take_screenshot', null, null); } else { _didScheduleScreenshot = true; - dispatcher.scheduleFrame(); + window.scheduleFrame(); } }); } @@ -45,18 +45,18 @@ abstract class Scenario { /// Called by the program when the window metrics have changed. /// - /// See [PlatformDispatcher.onMetricsChanged]. + /// See [Window.onMetricsChanged]. void onMetricsChanged() {} /// Called by the program when a pointer event is received. /// - /// See [PlatformDispatcher.onPointerDataPacket]. + /// See [Window.onPointerDataPacket]. void onPointerDataPacket(PointerDataPacket packet) {} /// Called by the program when an engine side platform channel message is /// received. /// - /// See [PlatformDispatcher.onPlatformMessage]. + /// See [Window.onPlatformMessage]. void onPlatformMessage( String name, ByteData data, diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index 7431752d4bbe1..cc74066771e41 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -19,30 +19,30 @@ typedef ScenarioFactory = Scenario Function(); // ignore: public_member_api_docs int _viewId = 0; Map _scenarios = { - 'animated_color_square': () => AnimatedColorSquareScenario(PlatformDispatcher.instance), - 'locale_initialization': () => LocaleInitialization(PlatformDispatcher.instance), - 'platform_view': () => PlatformViewScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), - 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(PlatformDispatcher.instance, 'Hello from Scenarios (Platform View)', id: _viewId++), - 'platform_view_cliprect': () => PlatformViewClipRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRect', id: _viewId++), - 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(PlatformDispatcher.instance, 'PlatformViewClipRRect', id: _viewId++), - 'platform_view_clippath': () => PlatformViewClipPathScenario(PlatformDispatcher.instance, 'PlatformViewClipPath', id: _viewId++), - 'platform_view_transform': () => PlatformViewTransformScenario(PlatformDispatcher.instance, 'PlatformViewTransform', id: _viewId++), - 'platform_view_opacity': () => PlatformViewOpacityScenario(PlatformDispatcher.instance, 'PlatformViewOpacity', id: _viewId++), - 'platform_view_multiple': () => MultiPlatformViewScenario(PlatformDispatcher.instance, firstId: 6, secondId: _viewId++), - 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(PlatformDispatcher.instance, firstId: _viewId++, secondId: _viewId++), - 'poppable_screen': () => PoppableScreenScenario(PlatformDispatcher.instance), - 'platform_view_rotate': () => PlatformViewScenario(PlatformDispatcher.instance, 'Rotate Platform View', id: _viewId++), - 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false), - 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: true), - 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(PlatformDispatcher.instance, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), - 'tap_status_bar': () => TouchesScenario(PlatformDispatcher.instance), - 'text_semantics_focus': () => SendTextFocusSemantics(PlatformDispatcher.instance), - 'initial_route_reply': () => InitialRouteReply(PlatformDispatcher.instance), + 'animated_color_square': () => AnimatedColorSquareScenario(window), + 'locale_initialization': () => LocaleInitialization(window), + 'platform_view': () => PlatformViewScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_multiple_without_overlays': () => MultiPlatformViewWithoutOverlaysScenario(window, 'Hello from Scenarios (Platform View)', firstId: _viewId++, secondId: _viewId++), + 'platform_view_max_overlays': () => PlatformViewMaxOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: _viewId++), + 'platform_view_cliprect': () => PlatformViewClipRectScenario(window, 'PlatformViewClipRect', id: _viewId++), + 'platform_view_cliprrect': () => PlatformViewClipRRectScenario(window, 'PlatformViewClipRRect', id: _viewId++), + 'platform_view_clippath': () => PlatformViewClipPathScenario(window, 'PlatformViewClipPath', id: _viewId++), + 'platform_view_transform': () => PlatformViewTransformScenario(window, 'PlatformViewTransform', id: _viewId++), + 'platform_view_opacity': () => PlatformViewOpacityScenario(window, 'PlatformViewOpacity', id: _viewId++), + 'platform_view_multiple': () => MultiPlatformViewScenario(window, firstId: 6, secondId: _viewId++), + 'platform_view_multiple_background_foreground': () => MultiPlatformViewBackgroundForegroundScenario(window, firstId: _viewId++, secondId: _viewId++), + 'poppable_screen': () => PoppableScreenScenario(window), + 'platform_view_rotate': () => PlatformViewScenario(window, 'Rotate Platform View', id: _viewId++), + 'platform_view_gesture_reject_eager': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false), + 'platform_view_gesture_accept': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: true), + 'platform_view_gesture_reject_after_touches_ended': () => PlatformViewForTouchIOSScenario(window, 'platform view touch', id: _viewId++, accept: false, rejectUntilTouchesEnded: true), + 'tap_status_bar': () => TouchesScenario(window), + 'text_semantics_focus': () => SendTextFocusScemantics(window), + 'initial_route_reply': () => InitialRouteReply(window), }; Map _currentScenarioParams = {}; diff --git a/testing/scenario_app/lib/src/send_text_focus_semantics.dart b/testing/scenario_app/lib/src/send_text_focus_semantics.dart index 94997cf0701e4..529dafcea5416 100644 --- a/testing/scenario_app/lib/src/send_text_focus_semantics.dart +++ b/testing/scenario_app/lib/src/send_text_focus_semantics.dart @@ -11,9 +11,9 @@ import 'channel_util.dart'; import 'scenario.dart'; /// A scenario that sends back messages when touches are received. -class SendTextFocusSemantics extends Scenario { +class SendTextFocusScemantics extends Scenario { /// Constructor for `SendTextFocusScemantics`. - SendTextFocusSemantics(PlatformDispatcher dispatcher) : super(dispatcher); + SendTextFocusScemantics(Window window) : super(window); @override void onBeginFrame(Duration duration) { @@ -79,7 +79,7 @@ class SendTextFocusSemantics extends Scenario { // This mimics the framework which shows the FlutterTextInputView before // updating the TextInputSemanticsObject. sendJsonMethodCall( - dispatcher: dispatcher, + window: window, channel: 'flutter/textinput', method: 'TextInput.setClient', arguments: [ @@ -91,7 +91,7 @@ class SendTextFocusSemantics extends Scenario { ); sendJsonMethodCall( - dispatcher: dispatcher, + window: window, channel: 'flutter/textinput', method: 'TextInput.show', ); diff --git a/testing/scenario_app/lib/src/touches_scenario.dart b/testing/scenario_app/lib/src/touches_scenario.dart index 6eb35eebe9b28..dbbf50ff15581 100644 --- a/testing/scenario_app/lib/src/touches_scenario.dart +++ b/testing/scenario_app/lib/src/touches_scenario.dart @@ -10,7 +10,7 @@ import 'scenario.dart'; /// A scenario that sends back messages when touches are received. class TouchesScenario extends Scenario { /// Constructor for `TouchesScenario`. - TouchesScenario(PlatformDispatcher dispatcher) : super(dispatcher); + TouchesScenario(Window window) : super(window); @override void onPointerDataPacket(PointerDataPacket packet) { From d9a24814c5af6b5bf2d00b37dfc0e75ff05c85ae Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 13 Oct 2020 04:32:02 +0200 Subject: [PATCH 081/219] Add workaround for missing fl_method_xxx_response_get_type() symbols (#21405) --- shell/platform/linux/fl_method_response.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_method_response.cc b/shell/platform/linux/fl_method_response.cc index d4c760c213572..cd66bdf23ab2d 100644 --- a/shell/platform/linux/fl_method_response.cc +++ b/shell/platform/linux/fl_method_response.cc @@ -26,8 +26,11 @@ struct _FlMethodNotImplementedResponse { FlMethodResponse parent_instance; }; -// Added here to stop the compiler from optimising this function away. +// Added here to stop the compiler from optimising these functions away. G_MODULE_EXPORT GType fl_method_response_get_type(); +G_MODULE_EXPORT GType fl_method_success_response_get_type(); +G_MODULE_EXPORT GType fl_method_error_response_get_type(); +G_MODULE_EXPORT GType fl_method_not_implemented_response_get_type(); G_DEFINE_TYPE(FlMethodResponse, fl_method_response, G_TYPE_OBJECT) G_DEFINE_TYPE(FlMethodSuccessResponse, From 66344068899635154addec16005167113b9c1e9d Mon Sep 17 00:00:00 2001 From: Damian Wrobel Date: Tue, 13 Oct 2020 04:54:44 +0200 Subject: [PATCH 082/219] Support Wayland only (without X11 support in gdk) (#21218) Adds a support for compiling flutter engine when gdk does not have X11 backend. In such a configuration the generated gdkconfig.h header file looks like the following: /* gdkconfig.h * * This is a generated file. Please modify `configure.ac' */ #ifndef __GDKCONFIG_H__ #define __GDKCONFIG_H__ #if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION) #error "Only can be included directly." #endif #include G_BEGIN_DECLS #define GDK_WINDOWING_WAYLAND G_END_DECLS #endif /* __GDKCONFIG_H__ */ Additionally headers like are not available at all. Above configuration can be found on the most of the embedded systems. This patch enables compilation of X11 specific code only when gdk defines GDK_WINDOWING_X11. Signed-off-by: Damian Wrobel --- shell/platform/linux/fl_renderer_x11.cc | 4 ++++ shell/platform/linux/fl_renderer_x11.h | 5 +++++ shell/platform/linux/fl_view.cc | 26 ++++++++++++++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/shell/platform/linux/fl_renderer_x11.cc b/shell/platform/linux/fl_renderer_x11.cc index 5fa6e57978bb5..2c646b16ca223 100644 --- a/shell/platform/linux/fl_renderer_x11.cc +++ b/shell/platform/linux/fl_renderer_x11.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "fl_renderer_x11.h" +#ifdef GDK_WINDOWING_X11 + #include "flutter/shell/platform/linux/egl_utils.h" struct _FlRendererX11 { @@ -108,3 +110,5 @@ static void fl_renderer_x11_init(FlRendererX11* self) {} FlRendererX11* fl_renderer_x11_new() { return FL_RENDERER_X11(g_object_new(fl_renderer_x11_get_type(), nullptr)); } + +#endif // GDK_WINDOWING_X11 diff --git a/shell/platform/linux/fl_renderer_x11.h b/shell/platform/linux/fl_renderer_x11.h index 8c32dcd0bc31b..d8d3b60f25a6c 100644 --- a/shell/platform/linux/fl_renderer_x11.h +++ b/shell/platform/linux/fl_renderer_x11.h @@ -5,6 +5,9 @@ #ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_X11_H_ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_X11_H_ +#include + +#ifdef GDK_WINDOWING_X11 #include #include "flutter/shell/platform/linux/fl_renderer.h" @@ -35,4 +38,6 @@ FlRendererX11* fl_renderer_x11_new(); G_END_DECLS +#endif // GDK_WINDOWING_X11 + #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_X11_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index d38ef5aa5aa63..01711d125e3b8 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -5,7 +5,9 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h" #include +#ifdef GDK_WINDOWING_X11 #include +#endif #include #include "flutter/shell/platform/linux/fl_engine_private.h" @@ -131,17 +133,27 @@ static void fl_view_plugin_registry_iface_init( iface->get_registrar_for_plugin = fl_view_get_registrar_for_plugin; } +static FlRenderer* fl_view_get_renderer_for_display(GdkDisplay* display) { +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY(display)) { + return FL_RENDERER(fl_renderer_x11_new()); + } +#endif + + if (GDK_IS_WAYLAND_DISPLAY(display)) { + return FL_RENDERER(fl_renderer_wayland_new()); + } + + g_error("Unsupported GDK backend"); + + return nullptr; +} + static void fl_view_constructed(GObject* object) { FlView* self = FL_VIEW(object); GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(self)); - if (GDK_IS_X11_DISPLAY(display)) { - self->renderer = FL_RENDERER(fl_renderer_x11_new()); - } else if (GDK_IS_WAYLAND_DISPLAY(display)) { - self->renderer = FL_RENDERER(fl_renderer_wayland_new()); - } else { - g_error("Unsupported GDK backend"); - } + self->renderer = fl_view_get_renderer_for_display(display); self->engine = fl_engine_new(self->project, self->renderer); // Create system channel handlers. From a75f9e252c4088ecfa87f709805f06746ba820bb Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 00:42:02 -0400 Subject: [PATCH 083/219] Roll Skia from ab6e62c131e9 to f58db3c94da3 (6 revisions) (#21794) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index cf8f5fed06b0b..362a17320fd73 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'ab6e62c131e977abfb90c0b64a7efb63d44b93bb', + 'skia_revision': 'f58db3c94da3a849dda645ab48867cf923ae336e', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 37fc3bf428f2c..2bb6d703e5c59 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 4497174024f9e23872a78ab6764ef421 +Signature: 887d20ea6b9c5644791d217f014fb196 UNUSED LICENSES: From e4664bad9fa858ffd1559fa10e80e92706eaba44 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 00:52:02 -0400 Subject: [PATCH 084/219] Roll Fuchsia Mac SDK from FFpTJfmj1... to 8Cb2zG9e3... (#21795) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 362a17320fd73..e2dc2b33a29ad 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'FFpTJfmj1EGxY5dJDoGNmZ4dwub3GsxHTJHDtKGmQcMC' + 'version': '8Cb2zG9e3oz16jC5AWr0Izbs4aw6VAJZNz86zAfdSyAC' } ], 'condition': 'host_os == "mac"', From 6cdb2f69a4ef8402cf37e9e4279fba00942f9fe3 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 01:27:01 -0400 Subject: [PATCH 085/219] Roll Fuchsia Linux SDK from h-DeV4tgE... to gdo4mZ5oI... (#21797) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index e2dc2b33a29ad..1a770e6db2474 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'h-DeV4tgEk6jHzUlsoZri9lklKQJDEAk7UVXOEpNKFMC' + 'version': 'gdo4mZ5oIjOnOe5b2i_V0-JD4DlOuemCMcbgxx-Q5EoC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 2b6d1410e748c..7e8533c51b0f4 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 1298619133eae5b6bd80c2c69ae6c950 +Signature: 8228f7eacf4039e8da93d0a6a32c0396 UNUSED LICENSES: @@ -3222,6 +3222,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.weave/common.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.weave/weavestack.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.product.deprecatedconfiguration/wlan_deprecated_configuration.fidl FILE: ../../../fuchsia/sdk/linux/fidl/zx/zx_common.fidl +FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/handle_close_many.cc FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/trace.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/validate_string.cc FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp/include/lib/fidl/cpp/internal/bitset.h From d4fcfe68caa638a79d9cab44067bc9d476d3ca77 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 03:22:01 -0400 Subject: [PATCH 086/219] Roll Skia from f58db3c94da3 to 387fd62a1280 (3 revisions) (#21801) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 1a770e6db2474..8b5e8c7702060 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'f58db3c94da3a849dda645ab48867cf923ae336e', + 'skia_revision': '387fd62a1280a819b531108f12cd00db2b28517f', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 2bb6d703e5c59..512ead0252dc3 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 887d20ea6b9c5644791d217f014fb196 +Signature: ccaf82698b5fad6702a3c7e17c53fe37 UNUSED LICENSES: From a0076a799609d88729d5e8098187107420ed7772 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 09:27:01 -0400 Subject: [PATCH 087/219] Roll Skia from 387fd62a1280 to c89a7ee628db (1 revision) (#21803) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 8b5e8c7702060..531815b81c41b 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '387fd62a1280a819b531108f12cd00db2b28517f', + 'skia_revision': 'c89a7ee628db457b3b3f2ea32c60c76f8d66ce65', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 512ead0252dc3..fce49f64a3d2e 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: ccaf82698b5fad6702a3c7e17c53fe37 +Signature: 5d06a3b7f983a5002f241a737b6bda07 UNUSED LICENSES: From 30bad27f53313e1d22ff228a01c791addd68c9b2 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 10:47:01 -0400 Subject: [PATCH 088/219] Roll Skia from c89a7ee628db to fa8891164062 (1 revision) (#21804) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 531815b81c41b..6a662a0d98b18 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'c89a7ee628db457b3b3f2ea32c60c76f8d66ce65', + 'skia_revision': 'fa8891164062285332a3574c3a3bfca079939859', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index fce49f64a3d2e..314bc004bd2b0 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 5d06a3b7f983a5002f241a737b6bda07 +Signature: 67ea7daf7883d65ff22168492d8e470e UNUSED LICENSES: From d5c5a8ec6161fb510545287aefaa5562d208cbd2 Mon Sep 17 00:00:00 2001 From: Ferhat Date: Tue, 13 Oct 2020 08:53:28 -0700 Subject: [PATCH 089/219] [web] Fix Altgr keyboard crash (#21781) * Fix AltGr modifier crash * update integration test --- .../test_driver/text_editing_integration.dart | 15 +++++++++++++++ lib/web_ui/lib/src/engine/keyboard.dart | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart index 30acf5a3efb2c..49247d7d79cfb 100644 --- a/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart +++ b/e2etests/web/regular_integration_tests/test_driver/text_editing_integration.dart @@ -179,6 +179,21 @@ void main() { 'cancelable': true, }); + // Press and release AltGr key. + // Regression test for https://github.com/flutter/flutter/issues/58979. + dispatchKeyboardEvent(input, 'keydown', { + 'key': 'AltGraph', + 'code': 'AltRight', + 'bubbles': true, + 'cancelable': true, + }); + dispatchKeyboardEvent(input, 'keyup', { + 'key': 'AltGraph', + 'code': 'AltRight', + 'bubbles': true, + 'cancelable': true, + }); + // Press Tab. The focus should move to the next TextFormField. dispatchKeyboardEvent(input, 'keydown', { 'key': 'Tab', diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index 558e53f6f927a..c15886bd6b1e0 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -177,7 +177,7 @@ int _getMetaState(html.KeyboardEvent event) { if (event.getModifierState('Shift')) { metaState |= _modifierShift; } - if (event.getModifierState('Alt')) { + if (event.getModifierState('Alt') || event.getModifierState('AltGraph')) { metaState |= _modifierAlt; } if (event.getModifierState('Control')) { From 0332d5e6399d13d96cd6d8cb7c5ceed31d6607e2 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 12:07:02 -0400 Subject: [PATCH 090/219] Roll Skia from fa8891164062 to 01b93eabe25b (4 revisions) (#21805) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 6a662a0d98b18..658dcf17b2a23 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': 'fa8891164062285332a3574c3a3bfca079939859', + 'skia_revision': '01b93eabe25b8ce680ea7a8c7fb188fc6fe88d78', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 314bc004bd2b0..c861ae98335f4 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 67ea7daf7883d65ff22168492d8e470e +Signature: cdcafe46ee70bb575bae73818ff65028 UNUSED LICENSES: From 6ba6d812955cacc9fcb32d4a07293e5ec695195d Mon Sep 17 00:00:00 2001 From: Dan Field Date: Tue, 13 Oct 2020 09:36:29 -0700 Subject: [PATCH 091/219] Ocmock dylib (#21786) - Build OCMock as a dylib for iOS tests - Set install_name for ios_flutter_test and ocmock dylibs - Copy and sign dylibs during build process --- DEPS | 2 +- shell/platform/darwin/ios/BUILD.gn | 12 ++---- .../IosUnitTests.xcodeproj/project.pbxproj | 43 ++++++++++++++++++- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/DEPS b/DEPS index 658dcf17b2a23..0b09eaf961cdb 100644 --- a/DEPS +++ b/DEPS @@ -105,7 +105,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'f83d1d75216e97fb696434bca1cb9a4e7a570fb6', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'e5b7a41ce43f8a00d5fcb9c55cb16b9c7697e8aa', # Fuchsia compatibility # diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 9bf404552877f..fb0a845654d96 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -158,7 +158,6 @@ source_set("flutter_framework_source") { } } -ios_test_flutter_path = rebase_path("$root_out_dir/libios_test_flutter.dylib") platform_frameworks_path = rebase_path("$ios_sdk_path/../../Library/Frameworks/") @@ -181,15 +180,12 @@ source_set("ios_test_flutter_mrc") { "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/third_party/tonic", "//flutter/third_party/txt", - "//third_party/ocmock:ocmock", + "//third_party/ocmock:ocmock_shared", "//third_party/rapidjson", "//third_party/skia", ] } -# NOTE: This currently only supports simulator targets because of the install_name. -# TODO(54504): Switch the install_name and make the test runner copy the dynamic -# library into the testing bundle. shared_library("ios_test_flutter") { visibility = [ ":*" ] cflags = [ @@ -200,9 +196,9 @@ shared_library("ios_test_flutter") { ] ldflags = [ "-F$platform_frameworks_path", - "-Wl,-framework,XCTest", - "-Wl,-install_name,$ios_test_flutter_path", + "-Wl,-install_name,@rpath/Frameworks/libios_test_flutter.dylib", ] + libs = [ "XCTest.framework" ] configs -= [ "//build/config/gcc:symbol_visibility_hidden", "//build/config:symbol_visibility_hidden", @@ -223,7 +219,7 @@ shared_library("ios_test_flutter") { "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/third_party/tonic", "//flutter/third_party/txt", - "//third_party/ocmock:ocmock", + "//third_party/ocmock:ocmock_shared", "//third_party/rapidjson", "//third_party/skia", ] diff --git a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj index eeaef513460a9..dde07daccd170 100644 --- a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj +++ b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj @@ -27,6 +27,19 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 24EBCE9E2534C400008D1687 /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0AC232F424BA71D300A85907 /* SemanticsObjectTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SemanticsObjectTest.mm; sourceTree = ""; }; 0AC232F724BA71D300A85907 /* FlutterEngineTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterEngineTest.mm; sourceTree = ""; }; @@ -158,8 +171,10 @@ buildConfigurationList = 0D6AB6D222BB05E200EEE540 /* Build configuration list for PBXNativeTarget "IosUnitTests" */; buildPhases = ( 0D6AB6AD22BB05E100EEE540 /* Sources */, + 242904382534E32900F90C97 /* ShellScript */, 0D6AB6AE22BB05E100EEE540 /* Frameworks */, 0D6AB6AF22BB05E100EEE540 /* Resources */, + 24EBCE9E2534C400008D1687 /* Embed Libraries */, ); buildRules = ( ); @@ -246,6 +261,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 242904382534E32900F90C97 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -ex\nxcode_frameworks_dir=\"${TARGET_BUILD_DIR}/${PlugIns/IosUnitTestsTests.xctest/Frameworks/}\"\nmkdir -p \"${xcode_frameworks_dir}\"\nflutter_engine_rel_path=\"../../../../out/${FLUTTER_ENGINE}\"\ncp -f \"${flutter_engine_rel_path}/libios_test_flutter.dylib\" \"${xcode_frameworks_dir}\"\ncp -f \"${flutter_engine_rel_path}/libocmock_shared.dylib\" \"${xcode_frameworks_dir}\"\nif [[ -n \"${EXPANDED_CODE_SIGN_IDENTITY:-}\" ]]; then\n codesign --force --verbose --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" -- \"${xcode_frameworks_dir}/libocmock_shared.dylib\"\n codesign --force --verbose --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" -- \"${xcode_frameworks_dir}/libios_test_flutter.dylib\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 0D6AB6AD22BB05E100EEE540 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -475,9 +510,11 @@ ../../../../out/$FLUTTER_ENGINE, ../../../../out/$FLUTTER_ENGINE/obj/flutter/shell/platform/darwin/ios/, ../../../../out/$FLUTTER_ENGINE/obj/third_party/ocmock/, + "$(PROJECT_DIR)", ); OTHER_LDFLAGS = ( - "-locmock", + "-L../../../../out/$FLUTTER_ENGINE", + "-locmock_shared", "-ObjC", "-lios_test_flutter", ); @@ -517,9 +554,11 @@ ../../../../out/$FLUTTER_ENGINE, ../../../../out/$FLUTTER_ENGINE/obj/flutter/shell/platform/darwin/ios/, ../../../../out/$FLUTTER_ENGINE/obj/third_party/ocmock/, + "$(PROJECT_DIR)", ); OTHER_LDFLAGS = ( - "-locmock", + "-L../../../../out/$FLUTTER_ENGINE", + "-locmock_shared", "-ObjC", "-lios_test_flutter", ); From dc4ba54e47c0d271c6cec82030f4102b3fd2f252 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 12:47:02 -0400 Subject: [PATCH 092/219] Roll Dart SDK from e256855d07ba to a3b62f366529 (4 revisions) (#21808) --- DEPS | 2 +- ci/licenses_golden/licenses_third_party | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 0b09eaf961cdb..b1e632a2b0057 100644 --- a/DEPS +++ b/DEPS @@ -34,7 +34,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': 'e256855d07ba9cad5048f012f38a05446ca2fe09', + 'dart_revision': 'a3b62f366529dbe4e4e75b73124bc59581ce6428', # 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/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index a346f76afe6bf..ea6fbc454c55a 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 4f38a8a9ed9b09a6bd9f88ed98a9fe08 +Signature: d6984173aaddceb290a38b46fdf75c9c UNUSED LICENSES: @@ -8115,6 +8115,7 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.cc FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.h FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination_test.cc FILE: ../../../third_party/dart/runtime/vm/constants_base.h +FILE: ../../../third_party/dart/runtime/vm/datastream_test.cc FILE: ../../../third_party/dart/runtime/vm/dispatch_table.cc FILE: ../../../third_party/dart/runtime/vm/dispatch_table.h FILE: ../../../third_party/dart/runtime/vm/field_table.cc From 0793a8cf90e131405f9fa50c2c70f75d541882c2 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 13:17:01 -0400 Subject: [PATCH 093/219] Roll Dart SDK from e256855d07ba to a3b62f366529 (4 revisions) (#21809) From d9bd3e87daa4cce0fcc46ee13900881163e3cfa5 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 13:27:02 -0400 Subject: [PATCH 094/219] Roll Skia from 01b93eabe25b to 2e0c70dc9c3e (10 revisions) (#21810) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index b1e632a2b0057..10d5e5e17b6a0 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '01b93eabe25b8ce680ea7a8c7fb188fc6fe88d78', + 'skia_revision': '2e0c70dc9c3e99d509753b441b7ac2c2474ee74e', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index c861ae98335f4..13aff5461e678 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: cdcafe46ee70bb575bae73818ff65028 +Signature: c8e26574fb97088826a9b665c78a95e9 UNUSED LICENSES: From 7959d80c10d586a71f2671febe877bfba3d2d014 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 14:47:03 -0400 Subject: [PATCH 095/219] Roll Fuchsia Linux SDK from gdo4mZ5oI... to 41fVbRhb0... (#21814) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 10d5e5e17b6a0..3f3cb0c2c361d 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'gdo4mZ5oIjOnOe5b2i_V0-JD4DlOuemCMcbgxx-Q5EoC' + 'version': '41fVbRhb0uYVP0N76hQ6LEdYjWsf8i0h9_8m4_kmQqoC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 7e8533c51b0f4..efdecd5c2cf3a 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 8228f7eacf4039e8da93d0a6a32c0396 +Signature: 0e37b78cb41eb39347005863f24626d0 UNUSED LICENSES: From 0b72b87f162d1e7b757ea3f7a64f7eab747f1758 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Tue, 13 Oct 2020 16:35:14 -0700 Subject: [PATCH 096/219] Revert "Roll Fuchsia Linux SDK from gdo4mZ5oI... to 41fVbRhb0... (#21814)" (#21823) This reverts commit 7959d80c10d586a71f2671febe877bfba3d2d014. --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 3f3cb0c2c361d..10d5e5e17b6a0 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': '41fVbRhb0uYVP0N76hQ6LEdYjWsf8i0h9_8m4_kmQqoC' + 'version': 'gdo4mZ5oIjOnOe5b2i_V0-JD4DlOuemCMcbgxx-Q5EoC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index efdecd5c2cf3a..7e8533c51b0f4 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 0e37b78cb41eb39347005863f24626d0 +Signature: 8228f7eacf4039e8da93d0a6a32c0396 UNUSED LICENSES: From 99810261735aa9f7f2bd4d3105d3eba86a207d0f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 13 Oct 2020 17:52:02 -0700 Subject: [PATCH 097/219] Allow TalkBack navigation while a platform view is rendered (#21719) --- .../io/flutter/view/AccessibilityBridge.java | 40 +++++++-- .../flutter/view/AccessibilityBridgeTest.java | 84 +++++++++++++++++++ 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index cd50894516783..92cbe68b196f2 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; import io.flutter.Log; +import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate; import io.flutter.util.Predicate; @@ -541,12 +542,22 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { return null; } + // Generate accessibility node for platform views using a virtual display. + // + // In this case, register the accessibility node in the view embedder, + // so the accessibility tree can be mirrored as a subtree of the Flutter accessibility tree. + // This is in constrast to hybrid composition where the embeded view is in the view hiearchy, + // so it doesn't need to be mirrored. + // + // See the case down below for how hybrid composition is handled. if (semanticsNode.platformViewId != -1) { - // For platform views we delegate the node creation to the accessibility view embedder. View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId); - Rect bounds = semanticsNode.getGlobalRect(); - return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id, bounds); + boolean childUsesVirtualDisplay = !(embeddedView.getContext() instanceof FlutterActivity); + if (childUsesVirtualDisplay) { + Rect bounds = semanticsNode.getGlobalRect(); + return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id, bounds); + } } AccessibilityNodeInfo result = @@ -823,11 +834,28 @@ && shouldSetCollectionInfo(semanticsNode)) { } for (SemanticsNode child : semanticsNode.childrenInTraversalOrder) { - if (!child.hasFlag(Flag.IS_HIDDEN)) { - result.addChild(rootAccessibilityView, child.id); + if (child.hasFlag(Flag.IS_HIDDEN)) { + continue; + } + if (child.platformViewId != -1) { + View embeddedView = + platformViewsAccessibilityDelegate.getPlatformViewById(child.platformViewId); + + // Add the embeded view as a child of the current accessibility node if it's using + // hybrid composition. + // + // In this case, the view is in the Activity's view hierarchy, so it doesn't need to be + // mirrored. + // + // See the case above for how virtual displays are handled. + boolean childUsesHybridComposition = embeddedView.getContext() instanceof FlutterActivity; + if (childUsesHybridComposition) { + result.addChild(embeddedView); + continue; + } } + result.addChild(rootAccessibilityView, child.id); } - return result; } diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 81d87ae56afa8..673c1e48c95eb 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -5,6 +5,7 @@ package io.flutter.view; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -16,14 +17,17 @@ import static org.mockito.Mockito.when; import android.annotation.TargetApi; +import android.app.Activity; import android.content.ContentResolver; import android.content.Context; +import android.graphics.Rect; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate; import java.nio.ByteBuffer; @@ -33,6 +37,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -198,6 +203,81 @@ public void itHoverOverOutOfBoundsDoesNotCrash() { accessibilityBridge.onAccessibilityHoverEvent(MotionEvent.obtain(1, 1, 1, -10, -10, 0)); } + @Test + public void itProducesPlatformViewNodeForHybridComposition() { + PlatformViewsAccessibilityDelegate accessibilityDelegate = + mock(PlatformViewsAccessibilityDelegate.class); + + Context context = RuntimeEnvironment.application.getApplicationContext(); + View rootAccessibilityView = new View(context); + AccessibilityViewEmbedder accessibilityViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityBridge accessibilityBridge = + setUpBridge( + rootAccessibilityView, + /*accessibilityChannel=*/ null, + /*accessibilityManager=*/ null, + /*contentResolver=*/ null, + accessibilityViewEmbedder, + accessibilityDelegate); + + TestSemanticsNode root = new TestSemanticsNode(); + root.id = 0; + + TestSemanticsNode platformView = new TestSemanticsNode(); + platformView.id = 1; + platformView.platformViewId = 1; + root.addChild(platformView); + + TestSemanticsUpdate testSemanticsRootUpdate = root.toUpdate(); + accessibilityBridge.updateSemantics( + testSemanticsRootUpdate.buffer, testSemanticsRootUpdate.strings); + + TestSemanticsUpdate testSemanticsPlatformViewUpdate = platformView.toUpdate(); + accessibilityBridge.updateSemantics( + testSemanticsPlatformViewUpdate.buffer, testSemanticsPlatformViewUpdate.strings); + + View embeddedView = mock(View.class); + when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); + + when(embeddedView.getContext()).thenReturn(mock(FlutterActivity.class)); + + AccessibilityNodeInfo nodeInfo = mock(AccessibilityNodeInfo.class); + when(embeddedView.createAccessibilityNodeInfo()).thenReturn(nodeInfo); + + AccessibilityNodeInfo result = accessibilityBridge.createAccessibilityNodeInfo(0); + assertNotNull(result); + assertEquals(result.getChildCount(), 1); + assertEquals(result.getClassName(), "android.view.View"); + } + + @Test + public void itProducesPlatformViewNodeForVirtualDisplay() { + PlatformViewsAccessibilityDelegate accessibilityDelegate = + mock(PlatformViewsAccessibilityDelegate.class); + AccessibilityViewEmbedder accessibilityViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityBridge accessibilityBridge = + setUpBridge( + /*rootAccessibilityView=*/ null, + /*accessibilityChannel=*/ null, + /*accessibilityManager=*/ null, + /*contentResolver=*/ null, + accessibilityViewEmbedder, + accessibilityDelegate); + + TestSemanticsNode platformView = new TestSemanticsNode(); + platformView.platformViewId = 1; + + TestSemanticsUpdate testSemanticsUpdate = platformView.toUpdate(); + accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings); + + View embeddedView = mock(View.class); + when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); + when(embeddedView.getContext()).thenReturn(mock(Activity.class)); + + accessibilityBridge.createAccessibilityNodeInfo(0); + verify(accessibilityViewEmbedder).getRootNode(eq(embeddedView), eq(0), any(Rect.class)); + } + @Test public void releaseDropsChannelMessageHandler() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); @@ -317,6 +397,10 @@ void addFlag(AccessibilityBridge.Flag flag) { float right = 0.0f; float bottom = 0.0f; final List children = new ArrayList(); + + public void addChild(TestSemanticsNode child) { + children.add(child); + } // custom actions not supported. TestSemanticsUpdate toUpdate() { From 279c1cffbed7057aaea56ef030dcd2c9f04d8ced Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Tue, 13 Oct 2020 17:56:31 -0700 Subject: [PATCH 098/219] [ios] Create a standalone external view embedder on iOS (#21798) --- ci/licenses_golden/licenses_flutter | 2 + shell/platform/darwin/ios/BUILD.gn | 2 + .../darwin/ios/ios_external_view_embedder.h | 71 ++++++++++++++ .../darwin/ios/ios_external_view_embedder.mm | 96 +++++++++++++++++++ shell/platform/darwin/ios/ios_surface.h | 49 ++-------- shell/platform/darwin/ios/ios_surface.mm | 80 +--------------- shell/platform/darwin/ios/ios_surface_gl.mm | 2 +- .../platform/darwin/ios/ios_surface_metal.mm | 2 +- .../darwin/ios/ios_surface_software.mm | 2 +- 9 files changed, 186 insertions(+), 120 deletions(-) create mode 100644 shell/platform/darwin/ios/ios_external_view_embedder.h create mode 100644 shell/platform/darwin/ios/ios_external_view_embedder.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2553764df42c2..db2650aba010a 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1003,6 +1003,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_metal.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_metal.mm +FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_view_embedder.h +FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_view_embedder.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_render_target_gl.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.h diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index fb0a845654d96..793c97be93701 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -93,6 +93,8 @@ source_set("flutter_framework_source") { "ios_context_software.mm", "ios_external_texture_gl.h", "ios_external_texture_gl.mm", + "ios_external_view_embedder.h", + "ios_external_view_embedder.mm", "ios_render_target_gl.h", "ios_render_target_gl.mm", "ios_surface.h", diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h new file mode 100644 index 0000000000000..e717c2ef2a167 --- /dev/null +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_EXTERNAL_VIEW_EMBEDDER_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_EXTERNAL_VIEW_EMBEDDER_H_ + +#include "flutter/flow/embedded_views.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" + +namespace flutter { + +class IOSExternalViewEmbedder : public ExternalViewEmbedder { + public: + IOSExternalViewEmbedder( + FlutterPlatformViewsController* platform_views_controller, + std::shared_ptr context); + + // |ExternalViewEmbedder| + virtual ~IOSExternalViewEmbedder() override; + + private: + FlutterPlatformViewsController* platform_views_controller_; + std::shared_ptr ios_context_; + + // |ExternalViewEmbedder| + SkCanvas* GetRootCanvas() override; + + // |ExternalViewEmbedder| + void CancelFrame() override; + + // |ExternalViewEmbedder| + void BeginFrame( + SkISize frame_size, + GrDirectContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) override; + + // |ExternalViewEmbedder| + void PrerollCompositeEmbeddedView( + int view_id, + std::unique_ptr params) override; + + // |ExternalViewEmbedder| + PostPrerollResult PostPrerollAction( + fml::RefPtr raster_thread_merger) override; + + // |ExternalViewEmbedder| + std::vector GetCurrentCanvases() override; + + // |ExternalViewEmbedder| + SkCanvas* CompositeEmbeddedView(int view_id) override; + + // |ExternalViewEmbedder| + void SubmitFrame(GrDirectContext* context, + std::unique_ptr frame) override; + + // |ExternalViewEmbedder| + void EndFrame( + bool should_resubmit_frame, + fml::RefPtr raster_thread_merger) override; + + // |ExternalViewEmbedder| + bool SupportsDynamicThreadMerging() override; + + FML_DISALLOW_COPY_AND_ASSIGN(IOSExternalViewEmbedder); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_EXTERNAL_VIEW_EMBEDDER_H_ diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm new file mode 100644 index 0000000000000..8939b49116cbd --- /dev/null +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/ios/ios_external_view_embedder.h" + +namespace flutter { + +IOSExternalViewEmbedder::IOSExternalViewEmbedder( + FlutterPlatformViewsController* platform_views_controller, + std::shared_ptr context) + : platform_views_controller_(platform_views_controller), ios_context_(context) { + FML_CHECK(ios_context_); +} + +IOSExternalViewEmbedder::~IOSExternalViewEmbedder() = default; + +// |ExternalViewEmbedder| +SkCanvas* IOSExternalViewEmbedder::GetRootCanvas() { + // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the + // various overlays are controlled by this class. + return nullptr; +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::CancelFrame() { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::CancelFrame"); + FML_CHECK(platform_views_controller_); + platform_views_controller_->CancelFrame(); +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::BeginFrame( + SkISize frame_size, + GrDirectContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::BeginFrame"); + FML_CHECK(platform_views_controller_); + platform_views_controller_->SetFrameSize(frame_size); +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::PrerollCompositeEmbeddedView( + int view_id, + std::unique_ptr params) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::PrerollCompositeEmbeddedView"); + FML_CHECK(platform_views_controller_); + platform_views_controller_->PrerollCompositeEmbeddedView(view_id, std::move(params)); +} + +// |ExternalViewEmbedder| +PostPrerollResult IOSExternalViewEmbedder::PostPrerollAction( + fml::RefPtr raster_thread_merger) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::PostPrerollAction"); + FML_CHECK(platform_views_controller_); + PostPrerollResult result = platform_views_controller_->PostPrerollAction(raster_thread_merger); + return result; +} + +// |ExternalViewEmbedder| +std::vector IOSExternalViewEmbedder::GetCurrentCanvases() { + FML_CHECK(platform_views_controller_); + return platform_views_controller_->GetCurrentCanvases(); +} + +// |ExternalViewEmbedder| +SkCanvas* IOSExternalViewEmbedder::CompositeEmbeddedView(int view_id) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::CompositeEmbeddedView"); + FML_CHECK(platform_views_controller_); + return platform_views_controller_->CompositeEmbeddedView(view_id); +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::SubmitFrame(GrDirectContext* context, + std::unique_ptr frame) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::SubmitFrame"); + FML_CHECK(platform_views_controller_); + platform_views_controller_->SubmitFrame(std::move(context), ios_context_, std::move(frame)); + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::DidSubmitFrame"); +} + +// |ExternalViewEmbedder| +void IOSExternalViewEmbedder::EndFrame(bool should_resubmit_frame, + fml::RefPtr raster_thread_merger) { + TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::EndFrame"); + FML_CHECK(platform_views_controller_); + return platform_views_controller_->EndFrame(should_resubmit_frame, raster_thread_merger); +} + +// |ExternalViewEmbedder| +bool IOSExternalViewEmbedder::SupportsDynamicThreadMerging() { + return true; +} + +} // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index a01f9f15caeed..8935b7537bdae 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_H_ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/ios_external_view_embedder.h" #include @@ -22,18 +23,19 @@ namespace flutter { // mechanism which is still in a release preview. bool IsIosEmbeddedViewsPreviewEnabled(); -class IOSSurface : public ExternalViewEmbedder { +class IOSSurface { public: static std::unique_ptr Create( std::shared_ptr context, fml::scoped_nsobject layer, FlutterPlatformViewsController* platform_views_controller); - // |ExternalViewEmbedder| - virtual ~IOSSurface(); - std::shared_ptr GetContext() const; + std::shared_ptr GetSurfaceExternalViewEmbedder() const; + + virtual ~IOSSurface(); + virtual bool IsValid() const = 0; virtual void UpdateStorageSizeIfNecessary() = 0; @@ -51,45 +53,8 @@ class IOSSurface : public ExternalViewEmbedder { private: std::shared_ptr ios_context_; - FlutterPlatformViewsController* platform_views_controller_; - - // |ExternalViewEmbedder| - SkCanvas* GetRootCanvas() override; - - // |ExternalViewEmbedder| - void CancelFrame() override; - - // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, - GrDirectContext* context, - double device_pixel_ratio, - fml::RefPtr raster_thread_merger) override; + std::shared_ptr external_view_embedder_; - // |ExternalViewEmbedder| - void PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) override; - - // |ExternalViewEmbedder| - PostPrerollResult PostPrerollAction( - fml::RefPtr raster_thread_merger) override; - - // |ExternalViewEmbedder| - std::vector GetCurrentCanvases() override; - - // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; - - // |ExternalViewEmbedder| - void SubmitFrame(GrDirectContext* context, std::unique_ptr frame) override; - - // |ExternalViewEmbedder| - void EndFrame(bool should_resubmit_frame, - fml::RefPtr raster_thread_merger) override; - - // |ExternalViewEmbedder| - bool SupportsDynamicThreadMerging() override; - - public: FML_DISALLOW_COPY_AND_ASSIGN(IOSSurface); }; diff --git a/shell/platform/darwin/ios/ios_surface.mm b/shell/platform/darwin/ios/ios_surface.mm index 331cbeb8ac77e..49b418a94390d 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -53,8 +53,10 @@ IOSSurface::IOSSurface(std::shared_ptr ios_context, FlutterPlatformViewsController* platform_views_controller) - : ios_context_(std::move(ios_context)), platform_views_controller_(platform_views_controller) { + : ios_context_(std::move(ios_context)) { FML_DCHECK(ios_context_); + external_view_embedder_ = + std::make_shared(platform_views_controller, ios_context_); } IOSSurface::~IOSSurface() = default; @@ -63,80 +65,8 @@ return ios_context_; } -// |ExternalViewEmbedder| -SkCanvas* IOSSurface::GetRootCanvas() { - // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the - // various overlays are controlled by this class. - return nullptr; -} - -// |ExternalViewEmbedder| -void IOSSurface::CancelFrame() { - TRACE_EVENT0("flutter", "IOSSurface::CancelFrame"); - FML_CHECK(platform_views_controller_ != nullptr); - platform_views_controller_->CancelFrame(); -} - -// |ExternalViewEmbedder| -void IOSSurface::BeginFrame(SkISize frame_size, - GrDirectContext* context, - double device_pixel_ratio, - fml::RefPtr raster_thread_merger) { - TRACE_EVENT0("flutter", "IOSSurface::BeginFrame"); - FML_CHECK(platform_views_controller_ != nullptr); - platform_views_controller_->SetFrameSize(frame_size); -} - -// |ExternalViewEmbedder| -void IOSSurface::PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) { - TRACE_EVENT0("flutter", "IOSSurface::PrerollCompositeEmbeddedView"); - - FML_CHECK(platform_views_controller_ != nullptr); - platform_views_controller_->PrerollCompositeEmbeddedView(view_id, std::move(params)); -} - -// |ExternalViewEmbedder| -PostPrerollResult IOSSurface::PostPrerollAction( - fml::RefPtr raster_thread_merger) { - TRACE_EVENT0("flutter", "IOSSurface::PostPrerollAction"); - FML_CHECK(platform_views_controller_ != nullptr); - PostPrerollResult result = platform_views_controller_->PostPrerollAction(raster_thread_merger); - return result; -} - -// |ExternalViewEmbedder| -std::vector IOSSurface::GetCurrentCanvases() { - FML_CHECK(platform_views_controller_ != nullptr); - return platform_views_controller_->GetCurrentCanvases(); -} - -// |ExternalViewEmbedder| -SkCanvas* IOSSurface::CompositeEmbeddedView(int view_id) { - TRACE_EVENT0("flutter", "IOSSurface::CompositeEmbeddedView"); - FML_CHECK(platform_views_controller_ != nullptr); - return platform_views_controller_->CompositeEmbeddedView(view_id); -} - -// |ExternalViewEmbedder| -void IOSSurface::SubmitFrame(GrDirectContext* context, std::unique_ptr frame) { - TRACE_EVENT0("flutter", "IOSSurface::SubmitFrame"); - FML_CHECK(platform_views_controller_ != nullptr); - platform_views_controller_->SubmitFrame(std::move(context), ios_context_, std::move(frame)); - TRACE_EVENT0("flutter", "IOSSurface::DidSubmitFrame"); -} - -// |ExternalViewEmbedder| -void IOSSurface::EndFrame(bool should_resubmit_frame, - fml::RefPtr raster_thread_merger) { - TRACE_EVENT0("flutter", "IOSSurface::EndFrame"); - FML_CHECK(platform_views_controller_ != nullptr); - return platform_views_controller_->EndFrame(should_resubmit_frame, raster_thread_merger); -} - -// |ExternalViewEmbedder| -bool IOSSurface::SupportsDynamicThreadMerging() { - return true; +std::shared_ptr IOSSurface::GetSurfaceExternalViewEmbedder() const { + return external_view_embedder_; } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 3565365f73e7a..e114d5efee4a8 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -84,7 +84,7 @@ // |GPUSurfaceGLDelegate| ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { - return this; + return GetSurfaceExternalViewEmbedder().get(); } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index e7e4d12fcbfc8..ee240bd96a29b 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -55,7 +55,7 @@ // |GPUSurfaceDelegate| ExternalViewEmbedder* IOSSurfaceMetal::GetExternalViewEmbedder() { - return this; + return GetSurfaceExternalViewEmbedder().get(); } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index 1363bfea99fc2..7b5c2ee8ad723 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -124,7 +124,7 @@ // |GPUSurfaceSoftwareDelegate| ExternalViewEmbedder* IOSSurfaceSoftware::GetExternalViewEmbedder() { - return this; + return GetSurfaceExternalViewEmbedder().get(); } } // namespace flutter From adf5b59485a9b9b78c5183945ac5e2f52219aeed Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Tue, 13 Oct 2020 17:57:02 -0700 Subject: [PATCH 099/219] Add missing ninja call to analyze.sh so it can be run locally easily (#21782) --- ci/analyze.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/analyze.sh b/ci/analyze.sh index fd8d213fd71c2..7c5cc39194c0b 100755 --- a/ci/analyze.sh +++ b/ci/analyze.sh @@ -59,6 +59,7 @@ function analyze() ( ) echo "Analyzing dart:ui library..." +autoninja -C "$SRC_DIR/out/host_debug_unopt" generate_dart_ui analyze \ --options "$FLUTTER_DIR/analysis_options.yaml" \ --enable-experiment=non-nullable \ @@ -79,7 +80,7 @@ analyze \ echo "Analyzing testing/dart..." "$FLUTTER_DIR/tools/gn" --unoptimized -ninja -C "$SRC_DIR/out/host_debug_unopt" sky_engine sky_services +autoninja -C "$SRC_DIR/out/host_debug_unopt" sky_engine sky_services (cd "$FLUTTER_DIR/testing/dart" && "$PUB" get) analyze \ --packages="$FLUTTER_DIR/testing/dart/.dart_tool/package_config.json" \ From 8de1932abbf16a47ad0df28aa1205dff066588f3 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Tue, 13 Oct 2020 21:02:01 -0400 Subject: [PATCH 100/219] Roll Skia from 2e0c70dc9c3e to 7bbdde059685 (10 revisions) (#21816) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 10d5e5e17b6a0..3acf68cad4cde 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '2e0c70dc9c3e99d509753b441b7ac2c2474ee74e', + 'skia_revision': '7bbdde0596853c5ad88c8f2d0f959d33ee3b833c', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 13aff5461e678..48efe1f9ea0c9 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: c8e26574fb97088826a9b665c78a95e9 +Signature: 4ceb964aa279b571ea32c57f22278aed UNUSED LICENSES: From 2e3f1326d2e6990106b33e5a4ad13106d6c02c38 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 13 Oct 2020 18:40:55 -0700 Subject: [PATCH 101/219] Call PlatformView.dispose when removing hybrid composition platform views (#21790) Also force disposal of all hybrid platform views when shutting down the engine. Fixes https://github.com/flutter/flutter/issues/66764 --- .../platform/PlatformViewsController.java | 55 +++++++++++-------- .../platform/PlatformViewsControllerTest.java | 45 ++++----------- 2 files changed, 44 insertions(+), 56 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 05a6240e67092..b000a903c956d 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -14,6 +14,7 @@ import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -83,7 +84,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // The views returned by `PlatformView#getView()`. // // This only applies to hybrid composition. - private final SparseArray platformViews; + private final SparseArray platformViews; // The platform view parents that are appended to `FlutterView`. // If an entry in `platformViews` doesn't have an entry in this array, the platform view isn't @@ -143,32 +144,24 @@ public void createAndroidViewForPlatformView( } final PlatformView platformView = factory.create(context, request.viewId, createParams); - final View view = platformView.getView(); - if (view == null) { - throw new IllegalStateException( - "PlatformView#getView() returned null, but an Android view reference was expected."); - } - if (view.getParent() != null) { - throw new IllegalStateException( - "The Android view returned from PlatformView#getView() was already added to a parent view."); - } - platformViews.put(request.viewId, view); + platformViews.put(request.viewId, platformView); } @Override public void disposeAndroidViewForPlatformView(int viewId) { // Hybrid view. - final View platformView = platformViews.get(viewId); + final PlatformView platformView = platformViews.get(viewId); final FlutterMutatorView parentView = platformViewParent.get(viewId); if (platformView != null) { if (parentView != null) { - parentView.removeView(platformView); + parentView.removeView(platformView.getView()); } platformViews.remove(viewId); + platformView.dispose(); } if (parentView != null) { - ((FlutterView) flutterView).removeView(parentView); + ((ViewGroup) parentView.getParent()).removeView(parentView); platformViewParent.remove(viewId); } } @@ -311,8 +304,10 @@ public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { vdControllers.get(touch.viewId).dispatchTouchEvent(event); } else if (platformViews.get(viewId) != null) { final MotionEvent event = toMotionEvent(density, touch, /*usingVirtualDiplays=*/ false); - View view = platformViews.get(touch.viewId); - view.dispatchTouchEvent(event); + View view = platformViews.get(touch.viewId).getView(); + if (view != null) { + view.dispatchTouchEvent(event); + } } else { throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); } @@ -580,7 +575,7 @@ public void onPreEngineRestart() { public View getPlatformViewById(Integer id) { // Hybrid composition. if (platformViews.get(id) != null) { - return platformViews.get(id); + return platformViews.get(id).getView(); } VirtualDisplayController controller = vdControllers.get(id); if (controller == null) { @@ -690,6 +685,10 @@ private void flushAllViews() { controller.dispose(); } vdControllers.clear(); + + while (platformViews.size() > 0) { + channelHandler.disposeAndroidViewForPlatformView(platformViews.keyAt(0)); + } } private void initializeRootImageViewIfNeeded() { @@ -701,19 +700,27 @@ private void initializeRootImageViewIfNeeded() { @VisibleForTesting void initializePlatformViewIfNeeded(int viewId) { - final View view = platformViews.get(viewId); - if (view == null) { + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { throw new IllegalStateException( "Platform view hasn't been initialized from the platform view channel."); } if (platformViewParent.get(viewId) != null) { return; } + if (platformView.getView() == null) { + throw new IllegalStateException( + "PlatformView#getView() returned null, but an Android view reference was expected."); + } + if (platformView.getView().getParent() != null) { + throw new IllegalStateException( + "The Android view returned from PlatformView#getView() was already added to a parent view."); + } final FlutterMutatorView parentView = new FlutterMutatorView( context, context.getResources().getDisplayMetrics().density, androidTouchProcessor); platformViewParent.put(viewId, parentView); - parentView.addView(view); + parentView.addView(platformView.getView()); ((FlutterView) flutterView).addView(parentView); } @@ -740,9 +747,11 @@ public void onDisplayPlatformView( final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, viewHeight); - final View platformView = platformViews.get(viewId); - platformView.setLayoutParams(layoutParams); - platformView.bringToFront(); + final View view = platformViews.get(viewId).getView(); + if (view != null) { + view.setLayoutParams(layoutParams); + view.bringToFront(); + } currentFrameUsedPlatformViewIds.add(viewId); } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 0e585b3e508c4..62806032cd092 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -29,9 +29,7 @@ import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; -import io.flutter.plugin.common.FlutterException; import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugin.localization.LocalizationPlugin; import java.nio.ByteBuffer; @@ -262,7 +260,7 @@ public void createPlatformViewMessage__initializesAndroidView() { // Simulate create call from the framework. createPlatformView(jni, platformViewsController, platformViewId, "testType"); - verify(platformView, times(1)).getView(); + verify(viewFactory, times(1)).create(any(), eq(platformViewId), any()); } @Test @@ -286,21 +284,11 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { createPlatformView(jni, platformViewsController, platformViewId, "testType"); assertEquals(ShadowFlutterJNI.getResponses().size(), 1); - final ByteBuffer responseBuffer = ShadowFlutterJNI.getResponses().get(0); - responseBuffer.rewind(); - - StandardMethodCodec methodCodec = new StandardMethodCodec(new StandardMessageCodec()); - try { - methodCodec.decodeEnvelope(responseBuffer); - } catch (FlutterException exception) { - assertTrue( - exception - .getMessage() - .contains( - "PlatformView#getView() returned null, but an Android view reference was expected.")); - return; - } - assertFalse(true); + assertThrows( + IllegalStateException.class, + () -> { + platformViewsController.initializePlatformViewIfNeeded(platformViewId); + }); } @Test @@ -326,21 +314,11 @@ public void createPlatformViewMessage__throwsIfViewHasParent() { createPlatformView(jni, platformViewsController, platformViewId, "testType"); assertEquals(ShadowFlutterJNI.getResponses().size(), 1); - final ByteBuffer responseBuffer = ShadowFlutterJNI.getResponses().get(0); - responseBuffer.rewind(); - - StandardMethodCodec methodCodec = new StandardMethodCodec(new StandardMessageCodec()); - try { - methodCodec.decodeEnvelope(responseBuffer); - } catch (FlutterException exception) { - assertTrue( - exception - .getMessage() - .contains( - "The Android view returned from PlatformView#getView() was already added to a parent view.")); - return; - } - assertFalse(true); + assertThrows( + IllegalStateException.class, + () -> { + platformViewsController.initializePlatformViewIfNeeded(platformViewId); + }); } @Test @@ -381,6 +359,7 @@ public void disposeAndroidView__hybridComposition() { assertNotNull(androidView.getParent()); assertTrue(androidView.getParent() instanceof FlutterMutatorView); + verify(platformView, times(1)).dispose(); } @Test From 8772365537ee35af5813bd726640ef932dc5731c Mon Sep 17 00:00:00 2001 From: Felipe Archondo Date: Tue, 13 Oct 2020 22:11:24 -0400 Subject: [PATCH 102/219] [fuchsia] add intercept_all_input flag support (#21821) [fuchsia] add intercept_all_input flag support This change also fixes an issue where FlutterRunnerProductConfiguration crashes when a field is missing, when building with --unopt. Test: Added unit tests Bug: fxb/61466, fxb/61942 --- .../flutter_runner_product_configuration.cc | 24 ++++++++++++++----- .../flutter_runner_product_configuration.h | 2 ++ ..._runner_product_configuration_unittests.cc | 22 +++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc index c4f91e31925cd..bc2a49cf1f9e5 100644 --- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc +++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter_runner_product_configuration.h" +#include #include "rapidjson/document.h" @@ -17,15 +18,26 @@ FlutterRunnerProductConfiguration::FlutterRunnerProductConfiguration( return; // Parse out all values we're expecting. - if (auto& val = document["vsync_offset_in_us"]; val.IsInt()) { - vsync_offset_ = fml::TimeDelta::FromMicroseconds(val.GetInt()); + if (document.HasMember("vsync_offset_in_us")) { + auto& val = document["vsync_offset_in_us"]; + if (val.IsInt()) + vsync_offset_ = fml::TimeDelta::FromMicroseconds(val.GetInt()); } - if (auto& val = document["max_frames_in_flight"]; val.IsInt()) { - max_frames_in_flight_ = val.GetInt(); + if (document.HasMember("max_frames_in_flight")) { + auto& val = document["max_frames_in_flight"]; + if (val.IsInt()) + max_frames_in_flight_ = val.GetInt(); + } + if (document.HasMember("intercept_all_input")) { + auto& val = document["intercept_all_input"]; + if (val.IsBool()) + intercept_all_input_ = val.GetBool(); } #if defined(LEGACY_FUCHSIA_EMBEDDER) - if (auto& val = document["use_legacy_renderer"]; val.IsBool()) { - use_legacy_renderer_ = val.GetBool(); + if (document.HasMember("use_legacy_renderer")) { + auto& val = document["use_legacy_renderer"]; + if (val.IsBool()) + use_legacy_renderer_ = val.GetBool(); } #endif } diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h index 355f365042ef0..792f9fe25cd37 100644 --- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h +++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h @@ -16,6 +16,7 @@ class FlutterRunnerProductConfiguration { fml::TimeDelta get_vsync_offset() { return vsync_offset_; } uint64_t get_max_frames_in_flight() { return max_frames_in_flight_; } + bool get_intercept_all_input() { return intercept_all_input_; } #if defined(LEGACY_FUCHSIA_EMBEDDER) bool use_legacy_renderer() { return use_legacy_renderer_; } #endif @@ -23,6 +24,7 @@ class FlutterRunnerProductConfiguration { private: fml::TimeDelta vsync_offset_ = fml::TimeDelta::Zero(); uint64_t max_frames_in_flight_ = 3; + bool intercept_all_input_ = false; #if defined(LEGACY_FUCHSIA_EMBEDDER) bool use_legacy_renderer_ = true; #endif diff --git a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc index 1cdcd4a0a5353..2e287dbbdbf65 100644 --- a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc @@ -88,4 +88,26 @@ TEST_F(FlutterRunnerProductConfigurationTest, MissingMaxFramesInFlight) { minimum_reasonable_max_frames_in_flight); } +TEST_F(FlutterRunnerProductConfigurationTest, ValidInterceptAllInput) { + const std::string json_string = "{ \"intercept_all_input\" : true } "; + const uint64_t expected_intercept_all_input = true; + + FlutterRunnerProductConfiguration product_config = + FlutterRunnerProductConfiguration(json_string); + + EXPECT_EQ(expected_intercept_all_input, + product_config.get_intercept_all_input()); +} + +TEST_F(FlutterRunnerProductConfigurationTest, MissingInterceptAllInput) { + const std::string json_string = "{ \"intercept_all_input\" : } "; + const uint64_t expected_intercept_all_input = false; + + FlutterRunnerProductConfiguration product_config = + FlutterRunnerProductConfiguration(json_string); + + EXPECT_EQ(expected_intercept_all_input, + product_config.get_intercept_all_input()); +} + } // namespace flutter_runner_test From 3b2da6be9f32e63c00252b31b3c9da1d6eeda60c Mon Sep 17 00:00:00 2001 From: nturgut Date: Tue, 13 Oct 2020 19:26:34 -0700 Subject: [PATCH 103/219] [web] enabling firefox screenshot tests. adding to documentation (#21807) * enabling firefox screenshot tests. adding to documentation * test with correct goldens * update goldens SHA * change the goldens with flutter/goldens repo * do not run preparation step twice. this will cause test_results to be deleted --- .../web/regular_integration_tests/README.md | 8 +++-- .../lib/screenshot_support.dart | 35 ++++++++----------- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/dev/test_runner.dart | 12 ++++++- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/e2etests/web/regular_integration_tests/README.md b/e2etests/web/regular_integration_tests/README.md index 2e0aa9616c692..5e3501564680e 100644 --- a/e2etests/web/regular_integration_tests/README.md +++ b/e2etests/web/regular_integration_tests/README.md @@ -55,7 +55,7 @@ Future main() async { In order to run the tests follow these steps: -1. You can use two different approaches, using [felt](https://github.com/flutter/engine/blob/master/lib/web_ui/dev/README.md) tool will run all the tests, an update all the goldens. For running individual tests, we need to set UPDATE_GOLDENS environment variable. +1. You can use two different approaches, using [felt](https://github.com/flutter/engine/blob/master/lib/web_ui/dev/README.md) tool will run all the tests, hence update all the goldens. For running individual tests, we need to set UPDATE_GOLDENS environment variable. Screenshots are saved differently per browser, therefore do not forget to also update the screenshots for other browsers. Note that, LUCI is only running screenshot testing for integration tests on Firefox and Chrome. ``` felt test --integration-tests-only --update-screenshot-goldens @@ -65,6 +65,10 @@ felt test --integration-tests-only --update-screenshot-goldens UPDATE_GOLDENS=true flutter drive -v --target=test_driver/text_editing_integration.dart -d web-server --release --local-engine=host_debug_unopt ``` -2. The golden will be under `engine/src/flutter/lib/web_ui/.dart_tool/goldens/engine/web/` directory, you should create a PR for that file and merge it to `flutter/goldens`. +``` +UPDATE_GOLDENS=true flutter drive -v --target=test_driver/text_editing_integration.dart -d web-server --release --local-engine=host_debug_unopt --browser-name=firefox +``` + +2. The golden will be under `engine/src/flutter/lib/web_ui/.dart_tool/goldens/engine/web/` directory, you should create a PR for that file and merge it to `flutter/goldens`. For each browser the browser name would be appended to the end of the golden file such as: `screenshot_name-chrome.png` or `screenshot_name-firefox.png` 3. Get the commit SHA and replace the `revision` in this file: `engine/src/flutter/lib/web_ui/dev/goldens_lock.yaml` diff --git a/e2etests/web/regular_integration_tests/lib/screenshot_support.dart b/e2etests/web/regular_integration_tests/lib/screenshot_support.dart index 01d97067d0082..bfdf34ff0000d 100644 --- a/e2etests/web/regular_integration_tests/lib/screenshot_support.dart +++ b/e2etests/web/regular_integration_tests/lib/screenshot_support.dart @@ -53,8 +53,7 @@ Future runTestWithScreenshots( bool updateGoldens = false; // We are using an environment variable instead of an argument, since // this code is not invoked from the shell but from the `flutter drive` - // tool itself. Therefore we do not have control on the command line - // arguments. + // tool itself, we do not have control on the command line arguments. // Please read the README, further info on how to update the goldens. final String updateGoldensFlag = io.Platform.environment['UPDATE_GOLDENS']; // Validate if the environment variable is set correctly. @@ -71,25 +70,21 @@ Future runTestWithScreenshots( test.integrationDriver( driver: driver, onScreenshot: (String screenshotName, List screenshotBytes) async { - if (browser == 'chrome') { - final Image screenshot = decodePng(screenshotBytes); - final String result = compareImage( - screenshot, - updateGoldens, - '$screenshotName-$browser.png', - PixelComparison.fuzzy, - diffRateFailure, - forIntegrationTests: true, - write: updateGoldens, - ); - if (result == 'OK') { - return true; - } else { - io.stderr.writeln('ERROR: $result'); - return false; - } - } else { + final Image screenshot = decodePng(screenshotBytes); + final String result = compareImage( + screenshot, + updateGoldens, + '$screenshotName-$browser.png', + PixelComparison.fuzzy, + diffRateFailure, + forIntegrationTests: true, + write: updateGoldens, + ); + if (result == 'OK') { return true; + } else { + io.stderr.writeln('ERROR: $result'); + return false; } }, ); diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 579e6c5086add..66a641aed621b 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: da3fef0c0eb849dfbb14b09a088c5f7916677482 +revision: 672510dc52daa5b059081f6990582bccdb4ea48f diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index 74c8de6d89248..9538a380ec58b 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -103,6 +103,13 @@ class TestCommand extends Command with ArgUtils { /// How many dart2js build tasks are running at the same time. final Pool _pool = Pool(8); + /// Checks if test harness preparation (such as fetching the goldens, + /// creating test_results directory or starting ios-simulator) has been done. + /// + /// If unit tests already did these steps, integration tests do not have to + /// repeat them. + bool _testPreparationReady = false; + /// Check the flags to see what type of tests are requested. TestTypesRequested findTestType() { if (boolArg('unit-tests-only') && boolArg('integration-tests-only')) { @@ -158,7 +165,9 @@ class TestCommand extends Command with ArgUtils { } Future runIntegrationTests() async { - await _prepare(); + if(!_testPreparationReady) { + await _prepare(); + } return IntegrationTestsManager( browser, useSystemFlutter, doUpdateScreenshotGoldens) .runTests(); @@ -207,6 +216,7 @@ class TestCommand extends Command with ArgUtils { if (isSafariIOS) { await IosSafariArgParser.instance.initIosSimulator(); } + _testPreparationReady = true; } /// Builds all test targets that will be run. From 8fe0cb3b1596e0d1475d2e01a7610b72ca5b82a5 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Wed, 14 Oct 2020 09:44:45 -0700 Subject: [PATCH 104/219] Revert Linux Fuchsia SDK rolls to 10/8 (#21829) --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 3acf68cad4cde..d873882da5355 100644 --- a/DEPS +++ b/DEPS @@ -536,7 +536,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'gdo4mZ5oIjOnOe5b2i_V0-JD4DlOuemCMcbgxx-Q5EoC' + 'version': 'ZJHmp3INUrLtYTJzHkJ-mTGQ7F59bfv1usLDP7xS-XgC' } ], 'condition': 'host_os == "linux"', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 7e8533c51b0f4..9b55043c23ff9 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 8228f7eacf4039e8da93d0a6a32c0396 +Signature: e794e6ce4652a9fdd105bddcb6799529 UNUSED LICENSES: From 7f1f2171541cd9dfcc4b89da4f0dd404e4990675 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 14 Oct 2020 14:12:01 -0400 Subject: [PATCH 105/219] Roll Dart SDK from a3b62f366529 to 4226116043f5 (1 revision) (#21830) --- DEPS | 8 +- ci/licenses_golden/licenses_third_party | 287 ++++++++++++------------ sky/packages/sky_engine/LICENSE | 19 +- 3 files changed, 155 insertions(+), 159 deletions(-) diff --git a/DEPS b/DEPS index d873882da5355..06ee9dc7cb048 100644 --- a/DEPS +++ b/DEPS @@ -34,14 +34,14 @@ 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': 'a3b62f366529dbe4e4e75b73124bc59581ce6428', + 'dart_revision': '4226116043f5ea5301b11474e0a97706132c317b', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_args_tag': '1.6.0', 'dart_boringssl_gen_rev': '429ccb1877f7987a6f3988228bc2440e61293499', 'dart_boringssl_rev': '4dfd5af70191b068aebe567b8e29ce108cee85ce', - 'dart_collection_rev': '52e219581f72a3eac013d6f5550c580962677425', + 'dart_collection_rev': '7d44763d62f97698b15c08ee360d838dccb63c88', 'dart_dart_style_tag': '1.3.7', 'dart_http_retry_tag': '0.1.1', 'dart_http_throttle_tag': '1.0.2', @@ -66,7 +66,7 @@ vars = { 'dart_tflite_native_rev': '0.4.0+1', 'dart_typed_data_tag': 'f94fc57b8e8c0e4fe4ff6cfd8290b94af52d3719', 'dart_usage_tag': '3.4.0', - 'dart_watcher_rev': 'fc3c9aae5d31d707b3013b42634dde8d8a1161b4', + 'dart_watcher_rev': '5df2e364b0c3ec12b9ed9cf2eedb71f9ddf8b7cd', 'ocmock_tag': 'v3.7.1', @@ -175,7 +175,7 @@ deps = { Var('dart_git') + '/charcode.git@4a685faba42d86ebd9d661eadd1e79d0a1c34c43', 'src/third_party/dart/third_party/pkg/cli_util': - Var('dart_git') + '/cli_util.git@0.2.0', + Var('dart_git') + '/cli_util.git@335ed165887d0ec97c2a09173ebf22dcf56a6c4e', 'src/third_party/dart/third_party/pkg/collection': Var('dart_git') + '/collection.git' + '@' + Var('dart_collection_rev'), diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index ea6fbc454c55a..6b4459f794f3f 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: d6984173aaddceb290a38b46fdf75c9c +Signature: cfe1613fcec7f9ec3bc156a9992a9d2d UNUSED LICENSES: @@ -8025,149 +8025,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== -==================================================================================================== -LIBRARY: dart -LIBRARY: wasmer -ORIGIN: ../../../third_party/dart/benchmarks/Dynamic/dart/Dynamic.dart + ../../../third_party/dart/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../third_party/dart/benchmarks/Dynamic/dart/Dynamic.dart -FILE: ../../../third_party/dart/benchmarks/Dynamic/dart2/Dynamic.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/EventLoopLatencyJson.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/json_benchmark.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/latency.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/EventLoopLatencyJson.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/json_benchmark.dart -FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/latency.dart -FILE: ../../../third_party/dart/benchmarks/ListCopy/dart/ListCopy.dart -FILE: ../../../third_party/dart/benchmarks/ListCopy/dart2/ListCopy.dart -FILE: ../../../third_party/dart/benchmarks/MD5/dart/md5.dart -FILE: ../../../third_party/dart/benchmarks/MD5/dart2/md5.dart -FILE: ../../../third_party/dart/benchmarks/RuntimeType/dart/RuntimeType.dart -FILE: ../../../third_party/dart/benchmarks/RuntimeType/dart2/RuntimeType.dart -FILE: ../../../third_party/dart/benchmarks/SHA1/dart/sha1.dart -FILE: ../../../third_party/dart/benchmarks/SHA1/dart2/sha1.dart -FILE: ../../../third_party/dart/benchmarks/SHA256/dart/sha256.dart -FILE: ../../../third_party/dart/benchmarks/SHA256/dart2/sha256.dart -FILE: ../../../third_party/dart/benchmarks/SkeletalAnimation/dart/SkeletalAnimation.dart -FILE: ../../../third_party/dart/benchmarks/SkeletalAnimation/dart2/SkeletalAnimation.dart -FILE: ../../../third_party/dart/benchmarks/SkeletalAnimationSIMD/dart/SkeletalAnimationSIMD.dart -FILE: ../../../third_party/dart/benchmarks/SkeletalAnimationSIMD/dart2/SkeletalAnimationSIMD.dart -FILE: ../../../third_party/dart/benchmarks/TypedDataDuplicate/dart/TypedDataDuplicate.dart -FILE: ../../../third_party/dart/benchmarks/TypedDataDuplicate/dart2/TypedDataDuplicate.dart -FILE: ../../../third_party/dart/benchmarks/Utf8Decode/dart/Utf8Decode.dart -FILE: ../../../third_party/dart/benchmarks/Utf8Decode/dart2/Utf8Decode.dart -FILE: ../../../third_party/dart/benchmarks/Utf8Encode/dart/Utf8Encode.dart -FILE: ../../../third_party/dart/benchmarks/Utf8Encode/dart2/Utf8Encode.dart -FILE: ../../../third_party/dart/runtime/bin/dartdev_isolate.cc -FILE: ../../../third_party/dart/runtime/bin/dartdev_isolate.h -FILE: ../../../third_party/dart/runtime/bin/exe_utils.cc -FILE: ../../../third_party/dart/runtime/bin/exe_utils.h -FILE: ../../../third_party/dart/runtime/bin/file_win.h -FILE: ../../../third_party/dart/runtime/bin/platform_macos.h -FILE: ../../../third_party/dart/runtime/bin/platform_macos_test.cc -FILE: ../../../third_party/dart/runtime/include/dart_api_dl.c -FILE: ../../../third_party/dart/runtime/include/dart_api_dl.h -FILE: ../../../third_party/dart/runtime/include/dart_version.h -FILE: ../../../third_party/dart/runtime/include/internal/dart_api_dl_impl.h -FILE: ../../../third_party/dart/runtime/observatory/bin/heap_snapshot.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/process_snapshot.dart -FILE: ../../../third_party/dart/runtime/observatory_2/bin/heap_snapshot.dart -FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/process_snapshot.dart -FILE: ../../../third_party/dart/runtime/platform/leak_sanitizer.h -FILE: ../../../third_party/dart/runtime/platform/unaligned.h -FILE: ../../../third_party/dart/runtime/platform/undefined_behavior_sanitizer.h -FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/bin/main.dart -FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/lib/cquery_driver.dart -FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/lib/xref_extractor.dart -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/dispatch_table_generator.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/dispatch_table_generator.h -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler_tracer.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler_tracer.h -FILE: ../../../third_party/dart/runtime/vm/compiler/api/deopt_id.h -FILE: ../../../third_party/dart/runtime/vm/compiler/api/print_filter.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/api/print_filter.h -FILE: ../../../third_party/dart/runtime/vm/compiler/api/type_check_mode.h -FILE: ../../../third_party/dart/runtime/vm/compiler/assembler/assembler_base.h -FILE: ../../../third_party/dart/runtime/vm/compiler/backend/constant_propagator_test.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/backend/reachability_fence_test.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/abi.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/abi.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/call.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/call.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/callback.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/callback.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/frame_rebase.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/frame_rebase.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/marshaller.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/marshaller.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_calling_convention.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_calling_convention.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_location.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_location.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_type.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_type.h -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/recognized_method.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/recognized_method.h -FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/multiple_entrypoints_test.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/stub_code_compiler.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.h -FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination_test.cc -FILE: ../../../third_party/dart/runtime/vm/constants_base.h -FILE: ../../../third_party/dart/runtime/vm/datastream_test.cc -FILE: ../../../third_party/dart/runtime/vm/dispatch_table.cc -FILE: ../../../third_party/dart/runtime/vm/dispatch_table.h -FILE: ../../../third_party/dart/runtime/vm/field_table.cc -FILE: ../../../third_party/dart/runtime/vm/field_table.h -FILE: ../../../third_party/dart/runtime/vm/port_set.h -FILE: ../../../third_party/dart/runtime/vm/tagged_pointer.h -FILE: ../../../third_party/dart/runtime/vm/timeline_macos.cc -FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test.cc -FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test.h -FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_arm.cc -FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_arm64.cc -FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_x64.cc -FILE: ../../../third_party/dart/runtime/vm/visitor.cc -FILE: ../../../third_party/dart/samples/ffi/async/async_test.dart -FILE: ../../../third_party/dart/samples/ffi/async/sample_async_callback.dart -FILE: ../../../third_party/dart/samples/ffi/async/sample_native_port_call.dart -FILE: ../../../third_party/dart/samples/ffi/sample_ffi_functions_callbacks_closures.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/js_patch.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/ffi_struct_patch.dart -FILE: ../../../third_party/dart/sdk/lib/internal/lowering.dart -FILE: ../../../third_party/dart/sdk/lib/io/network_policy.dart -FILE: ../../../third_party/dart/third_party/wasmer/wasmer_wrapper.cc ----------------------------------------------------------------------------------------------------- -Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -==================================================================================================== - ==================================================================================================== LIBRARY: dart ORIGIN: ../../../third_party/dart/LICENSE @@ -8537,6 +8394,147 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: dart +ORIGIN: ../../../third_party/dart/benchmarks/Dynamic/dart/Dynamic.dart + ../../../third_party/dart/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/dart/benchmarks/Dynamic/dart/Dynamic.dart +FILE: ../../../third_party/dart/benchmarks/Dynamic/dart2/Dynamic.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/EventLoopLatencyJson.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/json_benchmark.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart/latency.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/EventLoopLatencyJson.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/json_benchmark.dart +FILE: ../../../third_party/dart/benchmarks/EventLoopLatencyJson/dart2/latency.dart +FILE: ../../../third_party/dart/benchmarks/ListCopy/dart/ListCopy.dart +FILE: ../../../third_party/dart/benchmarks/ListCopy/dart2/ListCopy.dart +FILE: ../../../third_party/dart/benchmarks/MD5/dart/md5.dart +FILE: ../../../third_party/dart/benchmarks/MD5/dart2/md5.dart +FILE: ../../../third_party/dart/benchmarks/RuntimeType/dart/RuntimeType.dart +FILE: ../../../third_party/dart/benchmarks/RuntimeType/dart2/RuntimeType.dart +FILE: ../../../third_party/dart/benchmarks/SHA1/dart/sha1.dart +FILE: ../../../third_party/dart/benchmarks/SHA1/dart2/sha1.dart +FILE: ../../../third_party/dart/benchmarks/SHA256/dart/sha256.dart +FILE: ../../../third_party/dart/benchmarks/SHA256/dart2/sha256.dart +FILE: ../../../third_party/dart/benchmarks/SkeletalAnimation/dart/SkeletalAnimation.dart +FILE: ../../../third_party/dart/benchmarks/SkeletalAnimation/dart2/SkeletalAnimation.dart +FILE: ../../../third_party/dart/benchmarks/SkeletalAnimationSIMD/dart/SkeletalAnimationSIMD.dart +FILE: ../../../third_party/dart/benchmarks/SkeletalAnimationSIMD/dart2/SkeletalAnimationSIMD.dart +FILE: ../../../third_party/dart/benchmarks/TypedDataDuplicate/dart/TypedDataDuplicate.dart +FILE: ../../../third_party/dart/benchmarks/TypedDataDuplicate/dart2/TypedDataDuplicate.dart +FILE: ../../../third_party/dart/benchmarks/Utf8Decode/dart/Utf8Decode.dart +FILE: ../../../third_party/dart/benchmarks/Utf8Decode/dart2/Utf8Decode.dart +FILE: ../../../third_party/dart/benchmarks/Utf8Encode/dart/Utf8Encode.dart +FILE: ../../../third_party/dart/benchmarks/Utf8Encode/dart2/Utf8Encode.dart +FILE: ../../../third_party/dart/runtime/bin/dartdev_isolate.cc +FILE: ../../../third_party/dart/runtime/bin/dartdev_isolate.h +FILE: ../../../third_party/dart/runtime/bin/exe_utils.cc +FILE: ../../../third_party/dart/runtime/bin/exe_utils.h +FILE: ../../../third_party/dart/runtime/bin/file_win.h +FILE: ../../../third_party/dart/runtime/bin/platform_macos.h +FILE: ../../../third_party/dart/runtime/bin/platform_macos_test.cc +FILE: ../../../third_party/dart/runtime/include/dart_api_dl.c +FILE: ../../../third_party/dart/runtime/include/dart_api_dl.h +FILE: ../../../third_party/dart/runtime/include/dart_version.h +FILE: ../../../third_party/dart/runtime/include/internal/dart_api_dl_impl.h +FILE: ../../../third_party/dart/runtime/observatory/bin/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/process_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/bin/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory_2/lib/src/elements/process_snapshot.dart +FILE: ../../../third_party/dart/runtime/platform/leak_sanitizer.h +FILE: ../../../third_party/dart/runtime/platform/unaligned.h +FILE: ../../../third_party/dart/runtime/platform/undefined_behavior_sanitizer.h +FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/bin/main.dart +FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/lib/cquery_driver.dart +FILE: ../../../third_party/dart/runtime/tools/wiki/xref_extractor/lib/xref_extractor.dart +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/dispatch_table_generator.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/dispatch_table_generator.h +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler_tracer.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler_tracer.h +FILE: ../../../third_party/dart/runtime/vm/compiler/api/deopt_id.h +FILE: ../../../third_party/dart/runtime/vm/compiler/api/print_filter.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/api/print_filter.h +FILE: ../../../third_party/dart/runtime/vm/compiler/api/type_check_mode.h +FILE: ../../../third_party/dart/runtime/vm/compiler/assembler/assembler_base.h +FILE: ../../../third_party/dart/runtime/vm/compiler/backend/constant_propagator_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/backend/reachability_fence_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/abi.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/abi.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/call.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/call.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/callback.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/callback.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/frame_rebase.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/frame_rebase.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/marshaller.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/marshaller.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_calling_convention.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_calling_convention.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_location.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_location.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_type.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/native_type.h +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/recognized_method.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/recognized_method.h +FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/kernel_binary_flowgraph_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/multiple_entrypoints_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/stub_code_compiler.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination.h +FILE: ../../../third_party/dart/runtime/vm/compiler/write_barrier_elimination_test.cc +FILE: ../../../third_party/dart/runtime/vm/constants_base.h +FILE: ../../../third_party/dart/runtime/vm/datastream_test.cc +FILE: ../../../third_party/dart/runtime/vm/dispatch_table.cc +FILE: ../../../third_party/dart/runtime/vm/dispatch_table.h +FILE: ../../../third_party/dart/runtime/vm/field_table.cc +FILE: ../../../third_party/dart/runtime/vm/field_table.h +FILE: ../../../third_party/dart/runtime/vm/port_set.h +FILE: ../../../third_party/dart/runtime/vm/tagged_pointer.h +FILE: ../../../third_party/dart/runtime/vm/timeline_macos.cc +FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test.cc +FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test.h +FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_arm.cc +FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_arm64.cc +FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_test_x64.cc +FILE: ../../../third_party/dart/runtime/vm/visitor.cc +FILE: ../../../third_party/dart/samples/ffi/async/async_test.dart +FILE: ../../../third_party/dart/samples/ffi/async/sample_async_callback.dart +FILE: ../../../third_party/dart/samples/ffi/async/sample_native_port_call.dart +FILE: ../../../third_party/dart/samples/ffi/sample_ffi_functions_callbacks_closures.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/js_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/ffi_struct_patch.dart +FILE: ../../../third_party/dart/sdk/lib/internal/lowering.dart +FILE: ../../../third_party/dart/sdk/lib/io/network_policy.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: dart ORIGIN: ../../../third_party/dart/client/dart.js + ../../../third_party/dart/LICENSE @@ -22359,7 +22357,6 @@ LIBRARY: wasmer ORIGIN: ../../../third_party/dart/third_party/wasmer/LICENSE TYPE: LicenseType.mit FILE: ../../../third_party/dart/third_party/wasmer/Cargo.toml -FILE: ../../../third_party/dart/third_party/wasmer/wasmer.hh FILE: ../../../third_party/dart/third_party/wasmer/wasmer.rs ---------------------------------------------------------------------------------------------------- MIT License diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE index 14714e389cd88..1bd8c4561a569 100644 --- a/sky/packages/sky_engine/LICENSE +++ b/sky/packages/sky_engine/LICENSE @@ -4503,13 +4503,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- dart -Copyright 2009 The Go Authors. All rights reserved. -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file --------------------------------------------------------------------------------- -dart - -Copyright 2012, the Dart project authors. +Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -4536,10 +4531,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- dart -wasmer -Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. +Copyright 2009 The Go Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file +-------------------------------------------------------------------------------- +dart + +Copyright 2012, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From 42814e0be0d3460e0f2bba680aadaecb41b4d447 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 14 Oct 2020 14:17:01 -0400 Subject: [PATCH 106/219] Roll Fuchsia Mac SDK from 8Cb2zG9e3... to SFNhlfVb_... (#21832) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 06ee9dc7cb048..a93b9200d80fa 100644 --- a/DEPS +++ b/DEPS @@ -516,7 +516,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': '8Cb2zG9e3oz16jC5AWr0Izbs4aw6VAJZNz86zAfdSyAC' + 'version': 'SFNhlfVb_3sGYAFy9FIH5Y3o3ZCd0f6NNPhI4kstCRoC' } ], 'condition': 'host_os == "mac"', From 2b97f0cf4de0d51123f567115a64e8b42afa8dec Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Wed, 14 Oct 2020 14:22:01 -0400 Subject: [PATCH 107/219] Roll Skia from 7bbdde059685 to 99446001182c (5 revisions) (#21834) --- DEPS | 2 +- ci/licenses_golden/licenses_skia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index a93b9200d80fa..3febd9e347bb8 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '7bbdde0596853c5ad88c8f2d0f959d33ee3b833c', + 'skia_revision': '99446001182ceb7a4fa3c4fbcb908bde38185766', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 48efe1f9ea0c9..22668e498dd57 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 4ceb964aa279b571ea32c57f22278aed +Signature: 1e59d8eb25114e6b03e47e82d70c9e95 UNUSED LICENSES: From 069b3cf8f093d44ec4bae1319cbfdc4f8b4753b6 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 14 Oct 2020 11:44:38 -0700 Subject: [PATCH 108/219] Fix the offset passed to minikin::GraphemeBreak::isGraphemeBreak (#21706) The character offset passed to isGraphemeBreak is relative to the beginning of the string (not relative to the text_start parameter). This caused bad results when searching for grapheme breaks beyond the first line of text (see https://github.com/flutter/flutter/issues/24802). This PR fixes the offset value. It also reverts the workaround applied in https://github.com/flutter/engine/pull/10063, which caused incorrect calculation of boundaries between graphemes within ligatures. --- third_party/txt/src/txt/paragraph_txt.cc | 45 +++---------- third_party/txt/src/txt/paragraph_txt.h | 8 +-- third_party/txt/tests/paragraph_unittests.cc | 66 +++++++++++--------- 3 files changed, 49 insertions(+), 70 deletions(-) diff --git a/third_party/txt/src/txt/paragraph_txt.cc b/third_party/txt/src/txt/paragraph_txt.cc index 780fd61808be4..1e4bcb88fac5e 100644 --- a/third_party/txt/src/txt/paragraph_txt.cc +++ b/third_party/txt/src/txt/paragraph_txt.cc @@ -194,11 +194,9 @@ static const float kDoubleDecorationSpacing = 3.0f; ParagraphTxt::GlyphPosition::GlyphPosition(double x_start, double x_advance, size_t code_unit_index, - size_t code_unit_width, - size_t cluster) + size_t code_unit_width) : code_units(code_unit_index, code_unit_index + code_unit_width), - x_pos(x_start, x_start + x_advance), - cluster(cluster) {} + x_pos(x_start, x_start + x_advance) {} void ParagraphTxt::GlyphPosition::Shift(double delta) { x_pos.Shift(delta); @@ -797,7 +795,6 @@ void ParagraphTxt::Layout(double width) { double run_x_offset = 0; double justify_x_offset = 0; - size_t cluster_unique_id = 0; std::vector paint_records; for (auto line_run_it = line_runs.begin(); line_run_it != line_runs.end(); @@ -945,7 +942,7 @@ void ParagraphTxt::Layout(double width) { offset < glyph_code_units.end; ++offset) { if (minikin::GraphemeBreak::isGraphemeBreak( layout_advances.data(), text_ptr, text_start, text_count, - offset)) { + text_start + offset)) { grapheme_code_unit_counts.push_back(code_unit_count); code_unit_count = 1; } else { @@ -958,10 +955,10 @@ void ParagraphTxt::Layout(double width) { float grapheme_advance = glyph_advance / grapheme_code_unit_counts.size(); - glyph_positions.emplace_back( - run_x_offset + glyph_x_offset, grapheme_advance, - run.start() + glyph_code_units.start, - grapheme_code_unit_counts[0], cluster_unique_id); + glyph_positions.emplace_back(run_x_offset + glyph_x_offset, + grapheme_advance, + run.start() + glyph_code_units.start, + grapheme_code_unit_counts[0]); // Compute positions for the additional graphemes in the ligature. for (size_t i = 1; i < grapheme_code_unit_counts.size(); ++i) { @@ -969,9 +966,8 @@ void ParagraphTxt::Layout(double width) { glyph_positions.back().x_pos.end, grapheme_advance, glyph_positions.back().code_units.start + grapheme_code_unit_counts[i - 1], - grapheme_code_unit_counts[i], cluster_unique_id); + grapheme_code_unit_counts[i]); } - cluster_unique_id++; bool at_word_start = false; bool at_word_end = false; @@ -1876,28 +1872,12 @@ Paragraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate( size_t x_index; const GlyphPosition* gp = nullptr; - const GlyphPosition* gp_cluster = nullptr; - bool is_cluster_corection = false; for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) { double glyph_end = (x_index < line_glyph_position.size() - 1) ? line_glyph_position[x_index + 1].x_pos.start : line_glyph_position[x_index].x_pos.end; - if (gp_cluster == nullptr || - gp_cluster->cluster != line_glyph_position[x_index].cluster) { - gp_cluster = &line_glyph_position[x_index]; - } if (dx < glyph_end) { - // Check if the glyph position is part of a cluster. If it is, - // we assign the cluster's root GlyphPosition to represent it. - if (gp_cluster->cluster == line_glyph_position[x_index].cluster) { - gp = gp_cluster; - // Detect if the matching GlyphPosition was non-root for the cluster. - if (gp_cluster != &line_glyph_position[x_index]) { - is_cluster_corection = true; - } - } else { - gp = &line_glyph_position[x_index]; - } + gp = &line_glyph_position[x_index]; break; } } @@ -1918,13 +1898,8 @@ Paragraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate( } double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2; - // We want to use the root cluster's start when the cluster - // was corrected. - // TODO(garyq): Detect if the position is in the middle of the cluster - // and properly assign the start/end positions. if ((direction == TextDirection::ltr && dx < glyph_center) || - (direction == TextDirection::rtl && dx >= glyph_center) || - is_cluster_corection) { + (direction == TextDirection::rtl && dx >= glyph_center)) { return PositionWithAffinity(gp->code_units.start, DOWNSTREAM); } else { return PositionWithAffinity(gp->code_units.end, UPSTREAM); diff --git a/third_party/txt/src/txt/paragraph_txt.h b/third_party/txt/src/txt/paragraph_txt.h index 4941bb87d3e28..0919130868211 100644 --- a/third_party/txt/src/txt/paragraph_txt.h +++ b/third_party/txt/src/txt/paragraph_txt.h @@ -260,17 +260,11 @@ class ParagraphTxt : public Paragraph { struct GlyphPosition { Range code_units; Range x_pos; - // Tracks the cluster that this glyph position belongs to. For example, in - // extended emojis, multiple glyph positions will have the same cluster. The - // cluster can be used as a key to distinguish between codepoints that - // contribute to the drawing of a single glyph. - size_t cluster; GlyphPosition(double x_start, double x_advance, size_t code_unit_index, - size_t code_unit_width, - size_t cluster); + size_t code_unit_width); void Shift(double delta); }; diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 8c806d70b3ba5..8f94d548e2a73 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -5211,26 +5211,6 @@ TEST_F(ParagraphTest, LINUX_ONLY(EmojiMultiLineRectsParagraph)) { } EXPECT_EQ(boxes.size(), 0ull); - // Ligature style indexing. - boxes = - paragraph->GetRectsForRange(0, 119, rect_height_style, rect_width_style); - for (size_t i = 0; i < boxes.size(); ++i) { - GetCanvas()->drawRect(boxes[i].rect, paint); - } - EXPECT_EQ(boxes.size(), 2ull); - EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0); - EXPECT_FLOAT_EQ(boxes[1].rect.right(), 334.61475); - - boxes = paragraph->GetRectsForRange(122, 132, rect_height_style, - rect_width_style); - paint.setColor(SK_ColorBLUE); - for (size_t i = 0; i < boxes.size(); ++i) { - GetCanvas()->drawRect(boxes[i].rect, paint); - } - EXPECT_EQ(boxes.size(), 1ull); - EXPECT_FLOAT_EQ(boxes[0].rect.left(), 357.95996); - EXPECT_FLOAT_EQ(boxes[0].rect.right(), 418.79901); - // GetPositionForCoordinates should not return inter-emoji positions. boxes = paragraph->GetRectsForRange( 0, paragraph->GetGlyphPositionAtCoordinate(610, 100).position, @@ -5241,9 +5221,7 @@ TEST_F(ParagraphTest, LINUX_ONLY(EmojiMultiLineRectsParagraph)) { } EXPECT_EQ(boxes.size(), 2ull); EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0); - // The following is expected to change to a higher value when - // rounding up is added to getGlyphPositionForCoordinate. - EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 622.53906); boxes = paragraph->GetRectsForRange( 0, paragraph->GetGlyphPositionAtCoordinate(580, 100).position, @@ -5265,13 +5243,45 @@ TEST_F(ParagraphTest, LINUX_ONLY(EmojiMultiLineRectsParagraph)) { } EXPECT_EQ(boxes.size(), 2ull); EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0); - // The following is expected to change to the 560.28516 value when - // rounding up is added to getGlyphPositionForCoordinate. - EXPECT_FLOAT_EQ(boxes[1].rect.right(), 498.03125); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516); ASSERT_TRUE(Snapshot()); } +TEST_F(ParagraphTest, LINUX_ONLY(LigatureCharacters)) { + const char* text = "Office"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_families = std::vector(1, "Roboto"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = BuildParagraph(builder); + paragraph->Layout(GetTestCanvasWidth()); + + // The "ffi" characters will be combined into one glyph in the Roboto font. + // Verify that the graphemes within the glyph have distinct boxes. + std::vector boxes = + paragraph->GetRectsForRange(1, 2, Paragraph::RectHeightStyle::kTight, + Paragraph::RectWidthStyle::kTight); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 9.625); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 13.608073); + + boxes = paragraph->GetRectsForRange(2, 4, Paragraph::RectHeightStyle::kTight, + Paragraph::RectWidthStyle::kTight); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 13.608073); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 21.574219); +} + TEST_F(ParagraphTest, HyphenBreakParagraph) { const char* text = "A " @@ -6409,8 +6419,8 @@ TEST_F(ParagraphTest, KhmerLineBreaker) { ASSERT_EQ(paragraph->glyph_lines_.size(), 3ull); EXPECT_EQ(paragraph->glyph_lines_[0].positions.size(), 7ul); - EXPECT_EQ(paragraph->glyph_lines_[1].positions.size(), 12ul); - EXPECT_EQ(paragraph->glyph_lines_[2].positions.size(), 7ul); + EXPECT_EQ(paragraph->glyph_lines_[1].positions.size(), 7ul); + EXPECT_EQ(paragraph->glyph_lines_[2].positions.size(), 3ul); ASSERT_TRUE(Snapshot()); } From d95a5dc6adc705d01dfea6bba2190f9772c4a4ab Mon Sep 17 00:00:00 2001 From: egdaniel Date: Wed, 14 Oct 2020 16:55:09 -0400 Subject: [PATCH 109/219] Update flutter to pass Skia the VkImageUsageFlags and Samples (#21842) Previously Skia did not require the clients to pass in the usage flags and Skia would just assumed they contained specific ones depending on how the client wrapped the VkImage. Now Skia allows the client to pass in the specific usage flags used so that Skia knows exactly what type of operations are legal without having to guess/assume what the client did. Also update to set the sample count as well while I'm in here. --- .../fuchsia/flutter/vulkan_surface.cc | 2 ++ vulkan/vulkan_swapchain.cc | 19 ++++++++++++++----- vulkan/vulkan_swapchain.h | 4 +++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/shell/platform/fuchsia/flutter/vulkan_surface.cc b/shell/platform/fuchsia/flutter/vulkan_surface.cc index ba697d915348f..a28b4dad7293c 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface.cc +++ b/shell/platform/fuchsia/flutter/vulkan_surface.cc @@ -344,6 +344,8 @@ bool VulkanSurface::SetupSkiaSurface(sk_sp context, image_info.fImageTiling = image_create_info.tiling; image_info.fImageLayout = image_create_info.initialLayout; image_info.fFormat = image_create_info.format; + image_info.fImageUsageFlags = image_create_info.usage; + image_info.fSampleCount = 1; image_info.fLevelCount = image_create_info.mipLevels; GrBackendRenderTarget sk_render_target(size.width(), size.height(), 0, diff --git a/vulkan/vulkan_swapchain.cc b/vulkan/vulkan_swapchain.cc index 888c1a74f659e..fbea31386ecb0 100644 --- a/vulkan/vulkan_swapchain.cc +++ b/vulkan/vulkan_swapchain.cc @@ -113,6 +113,10 @@ VulkanSwapchain::VulkanSwapchain(const VulkanProcTable& p_vk, VkSurfaceKHR surface_handle = surface.Handle(); + VkImageUsageFlags usage_flags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; + const VkSwapchainCreateInfoKHR create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, @@ -123,7 +127,7 @@ VulkanSwapchain::VulkanSwapchain(const VulkanProcTable& p_vk, .imageColorSpace = surface_format_.colorSpace, .imageExtent = capabilities_.currentExtent, .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageUsage = usage_flags, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, // Because of the exclusive sharing mode. .pQueueFamilyIndices = nullptr, @@ -151,7 +155,8 @@ VulkanSwapchain::VulkanSwapchain(const VulkanProcTable& p_vk, if (!CreateSwapchainImages(skia_context, format_infos[format_index].color_type_, - format_infos[format_index].color_space_)) { + format_infos[format_index].color_space_, + usage_flags)) { FML_DLOG(INFO) << "Could not create swapchain images."; return; } @@ -210,6 +215,7 @@ SkISize VulkanSwapchain::GetSize() const { sk_sp VulkanSwapchain::CreateSkiaSurface( GrDirectContext* gr_context, VkImage image, + VkImageUsageFlags usage_flags, const SkISize& size, SkColorType color_type, sk_sp color_space) const { @@ -227,6 +233,8 @@ sk_sp VulkanSwapchain::CreateSkiaSurface( image_info.fImageTiling = VK_IMAGE_TILING_OPTIMAL; image_info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; image_info.fFormat = surface_format_.format; + image_info.fImageUsageFlags = usage_flags; + image_info.fSampleCount = 1; image_info.fLevelCount = 1; // TODO(chinmaygarde): Setup the stencil buffer and the sampleCnt. @@ -246,7 +254,8 @@ sk_sp VulkanSwapchain::CreateSkiaSurface( bool VulkanSwapchain::CreateSwapchainImages(GrDirectContext* skia_context, SkColorType color_type, - sk_sp color_space) { + sk_sp color_space, + VkImageUsageFlags usage_flags) { std::vector images = GetImages(); if (images.size() == 0) { @@ -276,8 +285,8 @@ bool VulkanSwapchain::CreateSwapchainImages(GrDirectContext* skia_context, images_.emplace_back(std::move(vulkan_image)); // Populate the surface. - auto surface = CreateSkiaSurface(skia_context, image, surface_size, - color_type, color_space); + auto surface = CreateSkiaSurface(skia_context, image, usage_flags, + surface_size, color_type, color_space); if (surface == nullptr) { return false; diff --git a/vulkan/vulkan_swapchain.h b/vulkan/vulkan_swapchain.h index 570cbb1092078..9c8fc6717e9d8 100644 --- a/vulkan/vulkan_swapchain.h +++ b/vulkan/vulkan_swapchain.h @@ -79,10 +79,12 @@ class VulkanSwapchain { bool CreateSwapchainImages(GrDirectContext* skia_context, SkColorType color_type, - sk_sp color_space); + sk_sp color_space, + VkImageUsageFlags usage_flags); sk_sp CreateSkiaSurface(GrDirectContext* skia_context, VkImage image, + VkImageUsageFlags usage_flags, const SkISize& size, SkColorType color_type, sk_sp color_space) const; From df57e2152996a8311d3d10cbe48a159674fc2eb5 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Wed, 14 Oct 2020 14:27:02 -0700 Subject: [PATCH 110/219] [android] Refactor surface factory and wire in external view embedder (#21839) --- shell/platform/android/android_surface_gl.cc | 7 +-- shell/platform/android/android_surface_gl.h | 9 ++-- .../android/android_surface_software.cc | 7 +-- .../android/android_surface_software.h | 9 ++-- .../android/android_surface_vulkan.cc | 7 +-- .../platform/android/android_surface_vulkan.h | 10 ++-- .../android/external_view_embedder/BUILD.gn | 1 + .../external_view_embedder.cc | 3 +- .../external_view_embedder.h | 5 +- .../external_view_embedder_unittests.cc | 43 ++++++++++------ .../external_view_embedder/surface_pool.cc | 4 +- .../external_view_embedder/surface_pool.h | 2 +- .../surface_pool_unittests.cc | 51 +++++++++++-------- .../platform/android/platform_view_android.cc | 41 +++++++++++---- .../platform/android/platform_view_android.h | 20 ++++++++ .../android/surface/android_surface.h | 16 ++++-- 16 files changed, 152 insertions(+), 83 deletions(-) diff --git a/shell/platform/android/android_surface_gl.cc b/shell/platform/android/android_surface_gl.cc index c9a9a130c59c4..d7a630c345099 100644 --- a/shell/platform/android/android_surface_gl.cc +++ b/shell/platform/android/android_surface_gl.cc @@ -22,11 +22,8 @@ constexpr char kEmulatorRendererPrefix[] = AndroidSurfaceGL::AndroidSurfaceGL( std::shared_ptr android_context, std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory) - : external_view_embedder_( - std::make_unique(android_context, - jni_facade, - surface_factory)), + std::shared_ptr external_view_embedder) + : external_view_embedder_(external_view_embedder), android_context_( std::static_pointer_cast(android_context)), native_window_(nullptr), diff --git a/shell/platform/android/android_surface_gl.h b/shell/platform/android/android_surface_gl.h index 771fbfea41a80..ca9aead26b4c5 100644 --- a/shell/platform/android/android_surface_gl.h +++ b/shell/platform/android/android_surface_gl.h @@ -22,9 +22,10 @@ namespace flutter { class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, public AndroidSurface { public: - AndroidSurfaceGL(std::shared_ptr android_context, - std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory); + AndroidSurfaceGL( + std::shared_ptr android_context, + std::shared_ptr jni_facade, + std::shared_ptr external_view_embedder); ~AndroidSurfaceGL() override; @@ -69,7 +70,7 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, sk_sp GetGLInterface() const override; private: - const std::unique_ptr external_view_embedder_; + const std::shared_ptr external_view_embedder_; const std::shared_ptr android_context_; fml::RefPtr native_window_; diff --git a/shell/platform/android/android_surface_software.cc b/shell/platform/android/android_surface_software.cc index 126f127bd2b8c..ab116f79048fa 100644 --- a/shell/platform/android/android_surface_software.cc +++ b/shell/platform/android/android_surface_software.cc @@ -40,11 +40,8 @@ bool GetSkColorType(int32_t buffer_format, AndroidSurfaceSoftware::AndroidSurfaceSoftware( std::shared_ptr android_context, std::shared_ptr jni_facade, - AndroidSurface::Factory surface_factory) - : external_view_embedder_( - std::make_unique(android_context, - jni_facade, - surface_factory)) { + std::shared_ptr external_view_embedder) + : external_view_embedder_(external_view_embedder) { GetSkColorType(WINDOW_FORMAT_RGBA_8888, &target_color_type_, &target_alpha_type_); } diff --git a/shell/platform/android/android_surface_software.h b/shell/platform/android/android_surface_software.h index ec9fdb63f131d..fc54ce2091778 100644 --- a/shell/platform/android/android_surface_software.h +++ b/shell/platform/android/android_surface_software.h @@ -18,9 +18,10 @@ namespace flutter { class AndroidSurfaceSoftware final : public AndroidSurface, public GPUSurfaceSoftwareDelegate { public: - AndroidSurfaceSoftware(std::shared_ptr android_context, - std::shared_ptr jni_facade, - AndroidSurface::Factory surface_factory); + AndroidSurfaceSoftware( + std::shared_ptr android_context, + std::shared_ptr jni_facade, + std::shared_ptr external_view_embedder); ~AndroidSurfaceSoftware() override; @@ -56,7 +57,7 @@ class AndroidSurfaceSoftware final : public AndroidSurface, ExternalViewEmbedder* GetExternalViewEmbedder() override; private: - const std::unique_ptr external_view_embedder_; + const std::shared_ptr external_view_embedder_; sk_sp sk_surface_; fml::RefPtr native_window_; diff --git a/shell/platform/android/android_surface_vulkan.cc b/shell/platform/android/android_surface_vulkan.cc index 4ff32b70a12c5..f31faf3716493 100644 --- a/shell/platform/android/android_surface_vulkan.cc +++ b/shell/platform/android/android_surface_vulkan.cc @@ -15,11 +15,8 @@ namespace flutter { AndroidSurfaceVulkan::AndroidSurfaceVulkan( std::shared_ptr android_context, std::shared_ptr jni_facade, - AndroidSurface::Factory surface_factory) - : external_view_embedder_( - std::make_unique(android_context, - jni_facade, - surface_factory)), + std::shared_ptr external_view_embedder) + : external_view_embedder_(external_view_embedder), proc_table_(fml::MakeRefCounted()) {} AndroidSurfaceVulkan::~AndroidSurfaceVulkan() = default; diff --git a/shell/platform/android/android_surface_vulkan.h b/shell/platform/android/android_surface_vulkan.h index 27b7164e5ba91..b91248b96c76a 100644 --- a/shell/platform/android/android_surface_vulkan.h +++ b/shell/platform/android/android_surface_vulkan.h @@ -21,9 +21,10 @@ namespace flutter { class AndroidSurfaceVulkan : public AndroidSurface, public GPUSurfaceVulkanDelegate { public: - AndroidSurfaceVulkan(std::shared_ptr android_context, - std::shared_ptr jni_facade, - AndroidSurface::Factory surface_factory); + AndroidSurfaceVulkan( + std::shared_ptr android_context, + std::shared_ptr jni_facade, + std::shared_ptr external_view_embedder); ~AndroidSurfaceVulkan() override; @@ -56,8 +57,7 @@ class AndroidSurfaceVulkan : public AndroidSurface, fml::RefPtr vk() override; private: - const std::unique_ptr external_view_embedder_; - + const std::shared_ptr external_view_embedder_; fml::RefPtr proc_table_; fml::RefPtr native_window_; diff --git a/shell/platform/android/external_view_embedder/BUILD.gn b/shell/platform/android/external_view_embedder/BUILD.gn index a757cd0d2aab1..44bcdaddae280 100644 --- a/shell/platform/android/external_view_embedder/BUILD.gn +++ b/shell/platform/android/external_view_embedder/BUILD.gn @@ -44,6 +44,7 @@ executable("android_external_view_embedder_unittests") { "//flutter/flow", "//flutter/shell/gpu:gpu_surface_gl", "//flutter/shell/platform/android/jni:jni_mock", + "//flutter/shell/platform/android/surface", "//flutter/shell/platform/android/surface:surface_mock", "//flutter/testing", "//flutter/testing:dart", diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index a45a8ae897ed1..6ea00128f5112 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc @@ -5,13 +5,14 @@ #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" #include "flutter/fml/trace_event.h" +#include "flutter/shell/platform/android/surface/android_surface.h" namespace flutter { AndroidExternalViewEmbedder::AndroidExternalViewEmbedder( std::shared_ptr android_context, std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory) + std::shared_ptr surface_factory) : ExternalViewEmbedder(), android_context_(android_context), jni_facade_(jni_facade), diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.h b/shell/platform/android/external_view_embedder/external_view_embedder.h index adb5137e2edb1..b8cf718a5ad45 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.h +++ b/shell/platform/android/external_view_embedder/external_view_embedder.h @@ -12,6 +12,7 @@ #include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/external_view_embedder/surface_pool.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_surface.h" #include "third_party/skia/include/core/SkPictureRecorder.h" namespace flutter { @@ -31,7 +32,7 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { AndroidExternalViewEmbedder( std::shared_ptr android_context, std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory); + std::shared_ptr surface_factory); // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( @@ -93,7 +94,7 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { const std::shared_ptr jni_facade_; // Allows to create surfaces. - const AndroidSurface::Factory surface_factory_; + const std::shared_ptr surface_factory_; // Holds surfaces. Allows to recycle surfaces or allocate new ones. const std::unique_ptr surface_pool_; diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index f69645f33dcf2..dff6e685bb1fd 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -13,6 +13,7 @@ #include "flutter/shell/platform/android/surface/android_surface_mock.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "shell/platform/android/surface/android_surface.h" #include "third_party/skia/include/gpu/GrDirectContext.h" namespace flutter { @@ -21,6 +22,24 @@ namespace testing { using ::testing::ByMove; using ::testing::Return; +class TestAndroidSurfaceFactory : public AndroidSurfaceFactory { + public: + using TestSurfaceProducer = + std::function(void)>; + explicit TestAndroidSurfaceFactory(TestSurfaceProducer&& surface_producer) { + surface_producer_ = surface_producer; + } + + ~TestAndroidSurfaceFactory() override = default; + + std::unique_ptr CreateSurface() override { + return surface_producer_(); + } + + private: + TestSurfaceProducer surface_producer_; +}; + class SurfaceMock : public Surface { public: MOCK_METHOD(bool, IsValid, (), (override)); @@ -263,10 +282,8 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame) { auto window = fml::MakeRefCounted(nullptr); auto gr_context = GrDirectContext::MakeMock(nullptr); auto frame_size = SkISize::Make(1000, 1000); - auto surface_factory = - [gr_context, window, frame_size]( - std::shared_ptr android_context, - std::shared_ptr jni_facade) { + auto surface_factory = std::make_shared( + [gr_context, window, frame_size]() { auto surface_frame_1 = std::make_unique( SkSurface::MakeNull(1000, 1000), false, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { @@ -293,7 +310,7 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame) { EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); return android_surface_mock; - }; + }); auto embedder = std::make_unique( android_context, jni_mock, surface_factory); @@ -482,10 +499,8 @@ TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) { auto window = fml::MakeRefCounted(nullptr); auto gr_context = GrDirectContext::MakeMock(nullptr); auto frame_size = SkISize::Make(1000, 1000); - auto surface_factory = - [gr_context, window, frame_size]( - std::shared_ptr android_context, - std::shared_ptr jni_facade) { + auto surface_factory = std::make_shared( + [gr_context, window, frame_size]() { auto surface_frame_1 = std::make_unique( SkSurface::MakeNull(1000, 1000), false, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { @@ -505,7 +520,7 @@ TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) { EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); return android_surface_mock; - }; + }); auto embedder = std::make_unique( android_context, jni_mock, surface_factory); @@ -565,10 +580,8 @@ TEST(AndroidExternalViewEmbedder, DoesNotDestroyOverlayLayersOnSizeChange) { auto window = fml::MakeRefCounted(nullptr); auto gr_context = GrDirectContext::MakeMock(nullptr); auto frame_size = SkISize::Make(1000, 1000); - auto surface_factory = - [gr_context, window, frame_size]( - std::shared_ptr android_context, - std::shared_ptr jni_facade) { + auto surface_factory = std::make_shared( + [gr_context, window, frame_size]() { auto surface_frame_1 = std::make_unique( SkSurface::MakeNull(1000, 1000), false, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { @@ -588,7 +601,7 @@ TEST(AndroidExternalViewEmbedder, DoesNotDestroyOverlayLayersOnSizeChange) { EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); return android_surface_mock; - }; + }); auto embedder = std::make_unique( android_context, jni_mock, surface_factory); diff --git a/shell/platform/android/external_view_embedder/surface_pool.cc b/shell/platform/android/external_view_embedder/surface_pool.cc index eccd2d671da5c..b07a8d1ec27c6 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.cc +++ b/shell/platform/android/external_view_embedder/surface_pool.cc @@ -23,7 +23,7 @@ std::shared_ptr SurfacePool::GetLayer( GrDirectContext* gr_context, std::shared_ptr android_context, std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory) { + std::shared_ptr surface_factory) { // Destroy current layers in the pool if the frame size has changed. if (requested_frame_size_ != current_frame_size_) { DestroyLayers(jni_facade); @@ -33,7 +33,7 @@ std::shared_ptr SurfacePool::GetLayer( // Allocate a new surface if there isn't one available. if (available_layer_index_ >= layers_.size()) { std::unique_ptr android_surface = - surface_factory(android_context, jni_facade); + surface_factory->CreateSurface(); FML_CHECK(android_surface && android_surface->IsValid()) << "Could not create an OpenGL, Vulkan or Software surface to setup " diff --git a/shell/platform/android/external_view_embedder/surface_pool.h b/shell/platform/android/external_view_embedder/surface_pool.h index 44236747f9331..afdb6b6a35b9c 100644 --- a/shell/platform/android/external_view_embedder/surface_pool.h +++ b/shell/platform/android/external_view_embedder/surface_pool.h @@ -55,7 +55,7 @@ class SurfacePool { GrDirectContext* gr_context, std::shared_ptr android_context, std::shared_ptr jni_facade, - const AndroidSurface::Factory& surface_factory); + std::shared_ptr surface_factory); // Gets the layers in the pool that aren't currently used. // This method doesn't mark the layers as unused. diff --git a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc index 6940c8c75e048..7d393832eb1f1 100644 --- a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc +++ b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc @@ -18,6 +18,24 @@ namespace testing { using ::testing::ByMove; using ::testing::Return; +class TestAndroidSurfaceFactory : public AndroidSurfaceFactory { + public: + using TestSurfaceProducer = + std::function(void)>; + explicit TestAndroidSurfaceFactory(TestSurfaceProducer&& surface_producer) { + surface_producer_ = surface_producer; + } + + ~TestAndroidSurfaceFactory() override = default; + + std::unique_ptr CreateSurface() override { + return surface_producer_(); + } + + private: + TestSurfaceProducer surface_producer_; +}; + TEST(SurfacePool, GetLayer__AllocateOneLayer) { auto pool = std::make_unique(); @@ -33,14 +51,13 @@ TEST(SurfacePool, GetLayer__AllocateOneLayer) { 0, window)))); auto surface_factory = - [gr_context, window](std::shared_ptr android_context, - std::shared_ptr jni_facade) { + std::make_shared([gr_context, window]() { auto android_surface_mock = std::make_unique(); EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); auto layer = pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); @@ -64,14 +81,13 @@ TEST(SurfacePool, GetUnusedLayers) { 0, window)))); auto surface_factory = - [gr_context, window](std::shared_ptr android_context, - std::shared_ptr jni_facade) { + std::make_shared([gr_context, window]() { auto android_surface_mock = std::make_unique(); EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); auto layer = pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); ASSERT_EQ(0UL, pool->GetUnusedLayers().size()); @@ -97,10 +113,8 @@ TEST(SurfacePool, GetLayer__Recycle) { std::make_shared(AndroidRenderingAPI::kSoftware); auto gr_context_2 = GrDirectContext::MakeMock(nullptr); - auto surface_factory = - [gr_context_1, gr_context_2, window]( - std::shared_ptr android_context, - std::shared_ptr jni_facade) { + auto surface_factory = std::make_shared( + [gr_context_1, gr_context_2, window]() { auto android_surface_mock = std::make_unique(); // Allocate two GPU surfaces for each gr context. EXPECT_CALL(*android_surface_mock, @@ -111,7 +125,7 @@ TEST(SurfacePool, GetLayer__Recycle) { EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); auto layer_1 = pool->GetLayer(gr_context_1.get(), android_context, jni_mock, surface_factory); @@ -147,14 +161,13 @@ TEST(SurfacePool, GetLayer__AllocateTwoLayers) { 1, window)))); auto surface_factory = - [gr_context, window](std::shared_ptr android_context, - std::shared_ptr jni_facade) { + std::make_shared([gr_context, window]() { auto android_surface_mock = std::make_unique(); EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); auto layer_1 = pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); auto layer_2 = pool->GetLayer(gr_context.get(), android_context, jni_mock, @@ -185,14 +198,13 @@ TEST(SurfacePool, DestroyLayers) { 0, window)))); auto surface_factory = - [gr_context, window](std::shared_ptr android_context, - std::shared_ptr jni_facade) { + std::make_shared([gr_context, window]() { auto android_surface_mock = std::make_unique(); EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory); EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()); @@ -213,14 +225,13 @@ TEST(SurfacePool, DestroyLayers__frameSizeChanged) { auto window = fml::MakeRefCounted(nullptr); auto surface_factory = - [gr_context, window](std::shared_ptr android_context, - std::shared_ptr jni_facade) { + std::make_shared([gr_context, window]() { auto android_surface_mock = std::make_unique(); EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); return android_surface_mock; - }; + }); pool->SetFrameSize(SkISize::Make(10, 10)); EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()).Times(0); EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 2c887b638a060..7bde2a45144a3 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -14,6 +14,8 @@ #include "flutter/shell/platform/android/android_external_texture_gl.h" #include "flutter/shell/platform/android/android_surface_gl.h" #include "flutter/shell/platform/android/android_surface_software.h" +#include "shell/platform/android/external_view_embedder/external_view_embedder.h" +#include "shell/platform/android/surface/android_surface.h" #if SHELL_ENABLE_VULKAN #include "flutter/shell/platform/android/android_surface_vulkan.h" @@ -26,22 +28,35 @@ namespace flutter { -std::unique_ptr SurfaceFactory( - std::shared_ptr android_context, +AndroidSurfaceFactoryImpl::AndroidSurfaceFactoryImpl( + std::shared_ptr context, std::shared_ptr jni_facade) { - FML_CHECK(SurfaceFactory); - switch (android_context->RenderingApi()) { + android_context_ = context; + jni_facade_ = jni_facade; +} + +AndroidSurfaceFactoryImpl::~AndroidSurfaceFactoryImpl() = default; + +void AndroidSurfaceFactoryImpl::SetExternalViewEmbedder( + std::shared_ptr external_view_embedder) { + external_view_embedder_ = external_view_embedder; +} + +std::unique_ptr AndroidSurfaceFactoryImpl::CreateSurface() { + FML_CHECK(external_view_embedder_); + switch (android_context_->RenderingApi()) { case AndroidRenderingAPI::kSoftware: return std::make_unique( - android_context, jni_facade, SurfaceFactory); + android_context_, jni_facade_, external_view_embedder_); case AndroidRenderingAPI::kOpenGLES: - return std::make_unique(android_context, jni_facade, - SurfaceFactory); + return std::make_unique(android_context_, jni_facade_, + external_view_embedder_); case AndroidRenderingAPI::kVulkan: #if SHELL_ENABLE_VULKAN - return std::make_unique(android_context, jni_facade, - SurfaceFactory); + return std::make_unique( + android_context_, jni_facade_, external_view_embedder_); #endif // SHELL_ENABLE_VULKAN + default: return nullptr; } return nullptr; @@ -72,7 +87,13 @@ PlatformViewAndroid::PlatformViewAndroid( FML_CHECK(android_context && android_context->IsValid()) << "Could not create an Android context."; - android_surface_ = SurfaceFactory(std::move(android_context), jni_facade); + surface_factory_ = + std::make_shared(android_context, jni_facade); + surface_factory_->SetExternalViewEmbedder( + std::make_shared(android_context, jni_facade, + surface_factory_)); + + android_surface_ = surface_factory_->CreateSurface(); FML_CHECK(android_surface_ && android_surface_->IsValid()) << "Could not create an OpenGL, Vulkan or Software surface to setup " "rendering."; diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 618b0413a572f..317784c729a96 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -22,6 +22,25 @@ namespace flutter { +class AndroidSurfaceFactoryImpl : public AndroidSurfaceFactory { + public: + AndroidSurfaceFactoryImpl(std::shared_ptr context, + std::shared_ptr jni_facade); + + ~AndroidSurfaceFactoryImpl() override; + + std::unique_ptr CreateSurface() override; + + void SetExternalViewEmbedder( + std::shared_ptr external_view_embedder); + + private: + std::shared_ptr android_context_; + std::shared_ptr jni_facade_; + std::shared_ptr external_view_embedder_; + +}; + class PlatformViewAndroid final : public PlatformView { public: static bool Register(JNIEnv* env); @@ -80,6 +99,7 @@ class PlatformViewAndroid final : public PlatformView { private: const std::shared_ptr jni_facade_; + std::shared_ptr surface_factory_; PlatformViewAndroidDelegate platform_view_android_delegate_; diff --git a/shell/platform/android/surface/android_surface.h b/shell/platform/android/surface/android_surface.h index 98c4f629414ae..1fd4847fae27b 100644 --- a/shell/platform/android/surface/android_surface.h +++ b/shell/platform/android/surface/android_surface.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_H_ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_H_ +#include "flutter/flow/embedded_views.h" #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" #include "flutter/shell/platform/android/context/android_context.h" @@ -14,12 +15,10 @@ namespace flutter { +class AndroidExternalViewEmbedder; + class AndroidSurface { public: - using Factory = std::function( - std::shared_ptr android_context, - std::shared_ptr jni_facade)>; - virtual ~AndroidSurface(); virtual bool IsValid() const = 0; @@ -38,6 +37,15 @@ class AndroidSurface { virtual bool SetNativeWindow(fml::RefPtr window) = 0; }; +class AndroidSurfaceFactory { + public: + AndroidSurfaceFactory() = default; + + virtual ~AndroidSurfaceFactory() = default; + + virtual std::unique_ptr CreateSurface() = 0; +}; + } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_H_ From ce75dda492e693711729a3d3223caa3a04c2d1a9 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 14 Oct 2020 14:33:42 -0700 Subject: [PATCH 111/219] Upgrade to latest process runner, fix commands that throw to fail test (#21827) This fixes the lint script to fail when the clang-tidy command itself fails to execute, and print the exception that was raised. --- ci/bin/lint.dart | 16 ++++++++++------ ci/pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ci/bin/lint.dart b/ci/bin/lint.dart index 04ff295acd5de..321bd3d9989b3 100644 --- a/ci/bin/lint.dart +++ b/ci/bin/lint.dart @@ -11,7 +11,7 @@ import 'dart:async' show Completer; import 'dart:convert' show jsonDecode, utf8, LineSplitter; -import 'dart:io' show File, exit, Directory, FileSystemEntity, Platform, stderr; +import 'dart:io' show File, exit, Directory, FileSystemEntity, Platform, stderr, exitCode; import 'package:args/args.dart'; import 'package:path/path.dart' as path; @@ -223,7 +223,6 @@ void main(List arguments) async { 'commands associated with them and can be lint checked.'); } - int exitCode = 0; final List jobs = []; for (Command command in changedFileBuildCommands) { final String relativePath = path.relative(command.file.path, from: repoPath.parent.path); @@ -245,16 +244,21 @@ void main(List arguments) async { final ProcessPool pool = ProcessPool(); await for (final WorkerJob job in pool.startWorkers(jobs)) { - if (job.result?.stdout.isEmpty ?? true) { + if (job.result?.exitCode == 0) { continue; } - print('❌ Failures for ${job.name}:'); - print(job.result.stdout); + if (job.result == null) { + print('\n❗ A clang-tidy job failed to run, aborting:\n${job.exception}'); + exitCode = 1; + break; + } else { + print('❌ Failures for ${job.name}:'); + print(job.result.stdout); + } exitCode = 1; } print('\n'); if (exitCode == 0) { print('No lint problems found.'); } - exit(exitCode); } diff --git a/ci/pubspec.yaml b/ci/pubspec.yaml index eba8dd49bfbbf..1ef6eaaa58cf1 100644 --- a/ci/pubspec.yaml +++ b/ci/pubspec.yaml @@ -8,7 +8,7 @@ dependencies: args: ^1.6.0 path: ^1.7.0 isolate: ^2.0.3 - process_runner: ^3.0.0 + process_runner: ^3.1.0 environment: sdk: '>=2.8.0 <3.0.0' From c7e5d546f2dbc49771b330135b39e6cfdc9734b1 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 14 Oct 2020 15:02:02 -0700 Subject: [PATCH 112/219] Prevent a race between SurfaceTexture.release and updateTexImage (#21777) --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/android/BUILD.gn | 1 + .../flutter/embedding/engine/FlutterJNI.java | 7 +- .../engine/renderer/FlutterRenderer.java | 23 ++++--- .../renderer/SurfaceTextureWrapper.java | 69 +++++++++++++++++++ .../android/io/flutter/view/FlutterView.java | 21 +++--- .../android/platform_view_android_jni_impl.cc | 22 +++--- tools/android_lint/baseline.xml | 22 ++++++ 8 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index db2650aba010a..b1018822b05e9 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -766,6 +766,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 0c22c91301648..5ccfd56d91aed 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -182,6 +182,7 @@ android_java_sources = [ "io/flutter/embedding/engine/renderer/FlutterRenderer.java", "io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java", "io/flutter/embedding/engine/renderer/RenderSurface.java", + "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", "io/flutter/embedding/engine/systemchannels/LifecycleChannel.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 59fa7132e3008..5e9864c345bdc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -23,6 +23,7 @@ import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; +import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; @@ -581,14 +582,14 @@ public void setAccessibilityFeatures(int flags) { * within Flutter's UI. */ @UiThread - public void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture) { + public void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeRegisterTexture(nativePlatformViewId, textureId, surfaceTexture); + nativeRegisterTexture(nativePlatformViewId, textureId, textureWrapper); } private native void nativeRegisterTexture( - long nativePlatformViewId, long textureId, @NonNull SurfaceTexture surfaceTexture); + long nativePlatformViewId, long textureId, @NonNull SurfaceTextureWrapper textureWrapper); /** * Call this method to inform Flutter that a texture previously registered with {@link diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 0aa11c4f2aacf..20a72e81fa623 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -99,18 +99,18 @@ public SurfaceTextureEntry createSurfaceTexture() { final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); Log.v(TAG, "New SurfaceTexture ID: " + entry.id()); - registerTexture(entry.id(), surfaceTexture); + registerTexture(entry.id(), entry.textureWrapper()); return entry; } final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { private final long id; - @NonNull private final SurfaceTexture surfaceTexture; + @NonNull private final SurfaceTextureWrapper textureWrapper; private boolean released; SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) { this.id = id; - this.surfaceTexture = surfaceTexture; + this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -118,12 +118,12 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur // and also the engine code check for platform thread in // Shell::OnPlatformViewMarkTextureFrameAvailable), // so we explicitly pass a Handler for the current thread. - this.surfaceTexture.setOnFrameAvailableListener(onFrameListener, new Handler()); + this.surfaceTexture().setOnFrameAvailableListener(onFrameListener, new Handler()); } else { // Android documentation states that the listener can be called on an arbitrary thread. // But in practice, versions of Android that predate the newer API will call the listener // on the thread where the SurfaceTexture was constructed. - this.surfaceTexture.setOnFrameAvailableListener(onFrameListener); + this.surfaceTexture().setOnFrameAvailableListener(onFrameListener); } } @@ -142,10 +142,15 @@ public void onFrameAvailable(@NonNull SurfaceTexture texture) { } }; + @NonNull + public SurfaceTextureWrapper textureWrapper() { + return textureWrapper; + } + @Override @NonNull public SurfaceTexture surfaceTexture() { - return surfaceTexture; + return textureWrapper.surfaceTexture(); } @Override @@ -159,7 +164,7 @@ public void release() { return; } Log.v(TAG, "Releasing a SurfaceTexture (" + id + ")."); - surfaceTexture.release(); + textureWrapper.release(); unregisterTexture(id); released = true; } @@ -298,8 +303,8 @@ public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) } // TODO(mattcarroll): describe the native behavior that this invokes - private void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture) { - flutterJNI.registerTexture(textureId, surfaceTexture); + private void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) { + flutterJNI.registerTexture(textureId, textureWrapper); } // TODO(mattcarroll): describe the native behavior that this invokes diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java new file mode 100644 index 0000000000000..478d997a5efad --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java @@ -0,0 +1,69 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine.renderer; + +import android.graphics.SurfaceTexture; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +/** + * A wrapper for a SurfaceTexture that tracks whether the texture has been released. + * + *