From 794cbd56ad6183a7f008939bad1d21838fc5c817 Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 17 Apr 2023 14:07:01 -0400 Subject: [PATCH 1/7] Roll Fuchsia Linux SDK from atix5Ek_OOxH-uoPA... to Cy5LG4U2InaFLkJGz... (#41275) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter-engine Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose 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/main/autoroll/README.md --- DEPS | 2 +- ci/licenses_golden/licenses_fuchsia | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 1267bec1a24f0..79453dbd443f0 100644 --- a/DEPS +++ b/DEPS @@ -846,7 +846,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'atix5Ek_OOxH-uoPAtImlDrqlyi6stw7XQRXg6ByqBMC' + 'version': 'Cy5LG4U2InaFLkJGz3IR2Dh8iSIqlAkeU72cGahWsLgC' } ], 'condition': 'host_os == "linux" and not download_fuchsia_sdk', diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index e4c47a8aacefe..ecdbe1c7b7ccc 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: bec53ed866291a0346b27e57d6ed488e +Signature: 2237822d508caf12c6d1bc6ff7c583b4 ==================================================================================================== LIBRARY: fuchsia_sdk From a4f1cbd17008a1d672774f554f41c39fa4bd9d1f Mon Sep 17 00:00:00 2001 From: skia-flutter-autoroll Date: Mon, 17 Apr 2023 15:06:37 -0400 Subject: [PATCH 2/7] Roll Dart SDK from 786a70d8ef6b to a335e6724332 (1 revision) (#41278) https://dart.googlesource.com/sdk.git/+log/786a70d8ef6b..a335e6724332 2023-04-17 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.1.0-12.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 aam@google.com,dart-vm-team@google.com,jacksongardner@google.com,rmistry@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter Engine: https://github.com/flutter/flutter/issues/new/choose 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/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 79453dbd443f0..906df5b3fe8fa 100644 --- a/DEPS +++ b/DEPS @@ -47,7 +47,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '786a70d8ef6b15937db763f9deea108f98d2a548', + 'dart_revision': 'a335e6724332400693e23808845ae0533730552d', # 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 2a2e63128b0455b3300c1edb01a47b3d3632a86e Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 17 Apr 2023 12:57:31 -0700 Subject: [PATCH 3/7] [Impeller] Remove ContentContextOptions declarations from AnonymousContents (#41256) This is just a copy paste error... --- impeller/entity/contents/anonymous_contents.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/impeller/entity/contents/anonymous_contents.h b/impeller/entity/contents/anonymous_contents.h index be68c66680ba5..6f9b68bb29c9c 100644 --- a/impeller/entity/contents/anonymous_contents.h +++ b/impeller/entity/contents/anonymous_contents.h @@ -12,11 +12,6 @@ namespace impeller { -ContentContextOptions OptionsFromPass(const RenderPass& pass); - -ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass, - const Entity& entity); - class AnonymousContents final : public Contents { public: static std::shared_ptr Make(RenderProc render_proc, From c55cd11a52123894e72000460fa9ea5ebc41cad4 Mon Sep 17 00:00:00 2001 From: fzyzcjy <5236035+fzyzcjy@users.noreply.github.com> Date: Tue, 18 Apr 2023 04:44:19 +0800 Subject: [PATCH 4/7] [Impeller] Make `DoMakeRasterSnapshot` output timeline event. (#41197) In Skia, there is such a timeline event in `SnapshotControllerSkia::DoMakeRasterSnapshot`. Therefore, since Impeller wants to mimic Skia and this event does take a long time sometimes, it seems reasonable to add this. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- shell/common/snapshot_controller_impeller.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/common/snapshot_controller_impeller.cc b/shell/common/snapshot_controller_impeller.cc index c9bafd9c0e27a..82226996a119b 100644 --- a/shell/common/snapshot_controller_impeller.cc +++ b/shell/common/snapshot_controller_impeller.cc @@ -33,6 +33,7 @@ sk_sp SnapshotControllerImpeller::MakeRasterSnapshot( sk_sp SnapshotControllerImpeller::DoMakeRasterSnapshot( const sk_sp& display_list, SkISize size) { + TRACE_EVENT0("flutter", __FUNCTION__); impeller::DisplayListDispatcher dispatcher; display_list->Dispatch(dispatcher); impeller::Picture picture = dispatcher.EndRecordingAsPicture(); From 2c6a93677661761275728e8e883d52937ffb9ea6 Mon Sep 17 00:00:00 2001 From: Akash Khunt Date: Tue, 18 Apr 2023 02:51:56 +0530 Subject: [PATCH 5/7] null check added to avoid NPE while calling FlutterView.detachFromFlutterEngine() (#41082) Issue fixed by this PR: https://github.com/flutter/flutter/issues/110138 This PR fixes an issue which is causing app crash when User tries to navigate to a new instance **FlutterFragment** (whose old instance is already present in the fragment backstack) in an Activity (which was restored to saved state after being killed in background due to memory pressure). Detailed case to reproduce the crash and identify its' root cause: Setup: Let's say we've an Activity1 which has a bottom nav bar with 3 tabs. Each of this 3 tabs are **FlutterFragment** i.e. Fragment1, Fragment2 & Fragment3 and all of them are using separate **FlutterEngine** but all of them will be cached. e.g. Multiple instances of **Fragment1** will be going to use same cached **FlutterEngine1**. 1. When User opens the app, Fragment1 gets added to fragment backstack 2. Then User navigates to Fragment2 (gets added to backstack as well) 3. Then User navigates to Fragment3 (gets added to backstack as well) 4. Then User puts the app in background. Due to memory pressure OS/platform kills the Activity1 and all 3 FlutterFragments while the app is in background. 5. Then after sometime User tries to bring the app to foreground from the app stack. Since Activity1 was killed by the OS/platform the app process will try to restore the Activity1 in the same state it was before it got killed. This leads to all 3 fragments present in backstack to get instantiated and then the **onAttach()** gets called for all 3, but only Fragment3 gets **onCreateView()** lifecycle event as it was the top most visible Fragment before the FragmentManager saved the state and app went into background. All 3 FlutterFragment goes through following function calls. FlutterFragment.onAttach() -> FlutterActivityAndFragmentDelegate.onAttach(). There is a one-to-one mapping between **FlutterFragment <-> FlutterActivityAndFragmentDelegate** 1 FlutterActivityAndFragmentDelegate.onAttach() -> FlutterEngineConnectionRegistry.attachToActivity(). There is a one-to-one mapping between **FlutterEngine <-> FlutterEngineConnectionRegistry**. _**NOTE**: THIS IS VERY IMPORTANT POINT TO KEEP IN MIND TO UNDERSTAND THE ROOT CAAUSE OF THIS CRASH._ 2 Since all the 3 **FlutterFragment** were just instantiated on activity restore **exclusiveActivity** will be null and exclusiveActivity will be assigned the host **FlutterActivityAndFragmentDelegate**. 3 6. Then FlutterFragment.onCreateView() will be called only for Fragment3 and it will be visible without an issue as its' state gets restored properly. 7. Then if User tries to navigate to Fragment2 via instantiating new instance of it (this means that now there will be two instances of Fragment2 in the backstack), then there will be crash as it'll go through following function calls. FlutterFragment.onAttach() -> FlutterActivityAndFragmentDelegate.onAttach(). 1 FlutterActivityAndFragmentDelegate.onAttach() -> FlutterEngineConnectionRegistry.attachToActivity(). 2 THIS IS WHERE THE CRASH STARTS. Since this is the second instance of Fragment2 and both instances are going to use the same cached **FlutterEngine** and hence same **FlutterEngineConnectionRegistry**, this time around **exclusiveActivity** will be non-null as it was assigned during step 5. And since exclusiveActivity will be be non null it'll try to detach from the old **ExclusiveAppComponent** via calling **exclusiveActivity.detachFromFlutterEngine()**. 7 FlutterActivityAndFragmentDelegate.detachFromFlutterEngine() -> FlutterFragment.detachFromFlutterEngine() 4 FlutterFragment.detachFromFlutterEngine() -> FlutterActivityAndFragmentDelegate.onDestroyView(). This ideally should not be called if the hosts' **FlutterFragments.onCreateView()** was not called in the first place. Also since the previous author has added **// Redundant calls are ok.** comment, I'm guessing that this is just a fallback cleanup. 5 THIS IS WHERE THE CRASH HAPPENS. FlutterActivityAndFragmentDelegate.onDestroyView() -> FlutterView.detachFromFlutterEngine(). Since the lifecycle of older instance of this FlutterFragment2 was capped at onAttach(), it's **FlutterView** property will be null and calling FlutterView.detachFromFlutterEngine() will throw NPE. 6 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../android/FlutterActivityAndFragmentDelegate.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 0dbb4573e9373..9a9f4d9a6a19c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -678,8 +678,13 @@ void onDestroyView() { flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener); activePreDrawListener = null; } - flutterView.detachFromFlutterEngine(); - flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener); + + // flutterView can be null in instances where a delegate.onDestroyView is called without + // onCreateView being called. See https://github.com/flutter/engine/pull/41082 for more detail. + if (flutterView != null) { + flutterView.detachFromFlutterEngine(); + flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener); + } } void onSaveInstanceState(@Nullable Bundle bundle) { From 1c1dfdabd0b06891c96c0044f7213a999c8998a9 Mon Sep 17 00:00:00 2001 From: hellohuanlin <41930132+hellohuanlin@users.noreply.github.com> Date: Mon, 17 Apr 2023 14:41:51 -0700 Subject: [PATCH 6/7] [rotation_distortion] Use "delayed swap" solution to reduce rotation distortion (#40730) The "size interpolation" solution didn't go well (more context [here](https://github.com/flutter/engine/pull/40412#issuecomment-1485938933)). Then a new solution came to my mind, and I call it **"delayed swap"**: In the originally behavior, we swap the width/height immediately before the rotation, resulting in roughly ~4x distortion in the beginning. With "delayed swap" solution, we **swap the width/height right in the middle of the rotation** (i.e. delay the swap for half of the transition duration). This new "delayed swap" solution gives us the same benefit as the "snapshot" solution: - reducing ~4x distortion to ~2x - most distorted frames occur in the middle of rotation when it's moving the fastest, making it hard to notice And it fixes the drawback of "snapshot" solution: - it works well with dynamic content like animation or video - it doesn't have a ~0.5 second penalty when taking the snapshot Looks pretty good on flutter gallery: https://user-images.githubusercontent.com/41930132/228383137-7cd09982-89a9-4c83-bf55-9431de708278.mp4 *List which issues are fixed by this PR. You must list at least one issue.* Fixes https://github.com/flutter/flutter/issues/16322 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../framework/Source/FlutterViewController.mm | 82 +++++++++++++---- .../Source/FlutterViewControllerTest.mm | 88 +++++++++++++++++-- 2 files changed, 150 insertions(+), 20 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 5f628675f87e8..1831b4d2f546b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -61,6 +61,11 @@ @interface FlutterViewController () )coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + // We delay the viewport metrics update for half of rotation transition duration, to address + // a bug with distorted aspect ratio. + // See: https://github.com/flutter/flutter/issues/16322 + // + // This approach does not fully resolve all distortion problem. But instead, it reduces the + // rotation distortion roughly from 4x to 2x. The most distorted frames occur in the middle + // of the transition when it is rotating the fastest, making it hard to notice. + + NSTimeInterval transitionDuration = coordinator.transitionDuration; + // Do not delay viewport metrics update if zero transition duration. + if (transitionDuration == 0) { + return; + } + + _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, + static_cast(transitionDuration / 2.0 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + // `viewWillTransitionToSize` is only called after the previous rotation is + // complete. So there won't be race condition for this flag. + _shouldIgnoreViewportMetricsUpdatesDuringRotation = NO; + [self updateViewportMetricsIfNeeded]; + }); +} + - (void)flushOngoingTouches { if (_engine && _ongoingTouches.get().count > 0) { auto packet = std::make_unique(_ongoingTouches.get().count); @@ -1226,7 +1260,10 @@ - (void)invalidateTouchRateCorrectionVSyncClient { #pragma mark - Handle view resizing -- (void)updateViewportMetrics { +- (void)updateViewportMetricsIfNeeded { + if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) { + return; + } if ([_engine.get() viewController] == self) { [_engine.get() updateViewportMetrics:_viewportMetrics]; } @@ -1243,11 +1280,9 @@ - (void)viewDidLayoutSubviews { // 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; - - [self updateViewportPadding]; - [self updateViewportMetrics]; + [self setViewportMetricsSize]; + [self setViewportMetricsPaddings]; + [self updateViewportMetricsIfNeeded]; // There is no guarantee that UIKit will layout subviews when the application is active. Creating // the surface when inactive will cause GPU accesses from the background. Only wait for the first @@ -1276,16 +1311,33 @@ - (void)viewDidLayoutSubviews { } - (void)viewSafeAreaInsetsDidChange { - [self updateViewportPadding]; - [self updateViewportMetrics]; + [self setViewportMetricsPaddings]; + [self updateViewportMetricsIfNeeded]; [super viewSafeAreaInsetsDidChange]; } -// Updates _viewportMetrics physical padding. +// Set _viewportMetrics physical size. +- (void)setViewportMetricsSize { + UIScreen* mainScreen = [self mainScreenIfViewLoaded]; + if (!mainScreen) { + return; + } + + CGFloat scale = mainScreen.scale; + _viewportMetrics.physical_width = self.view.bounds.size.width * scale; + _viewportMetrics.physical_height = self.view.bounds.size.height * scale; +} + +// Set _viewportMetrics physical paddings. // -// Viewport padding represents the iOS safe area insets. -- (void)updateViewportPadding { - CGFloat scale = [UIScreen mainScreen].scale; +// Viewport paddings represent the iOS safe area insets. +- (void)setViewportMetricsPaddings { + UIScreen* mainScreen = [self mainScreenIfViewLoaded]; + if (!mainScreen) { + return; + } + + CGFloat scale = mainScreen.scale; _viewportMetrics.physical_padding_top = self.view.safeAreaInsets.top * scale; _viewportMetrics.physical_padding_left = self.view.safeAreaInsets.left * scale; _viewportMetrics.physical_padding_right = self.view.safeAreaInsets.right * scale; @@ -1609,7 +1661,7 @@ - (void)setupKeyboardAnimationVsyncClient { flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = flutterViewController.get() .keyboardAnimationView.layer.presentationLayer.frame.origin.y; - [flutterViewController updateViewportMetrics]; + [flutterViewController updateViewportMetricsIfNeeded]; } } else { fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - @@ -1617,7 +1669,7 @@ - (void)setupKeyboardAnimationVsyncClient { flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; - [flutterViewController updateViewportMetrics]; + [flutterViewController updateViewportMetricsIfNeeded]; } }; flutter::Shell& shell = [_engine.get() shell]; @@ -1646,7 +1698,7 @@ - (void)ensureViewportMetricsIsCorrect { if (_viewportMetrics.physical_view_inset_bottom != self.targetViewInsetBottom) { // Make sure the `physical_view_inset_bottom` is the target value. _viewportMetrics.physical_view_inset_bottom = self.targetViewInsetBottom; - [self updateViewportMetrics]; + [self updateViewportMetricsIfNeeded]; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 594a264ddbe6c..b02109c236672 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -124,7 +124,7 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences; - (void)handlePressEvent:(FlutterUIPressProxy*)press nextAction:(void (^)())next API_AVAILABLE(ios(13.4)); - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer; -- (void)updateViewportMetrics; +- (void)updateViewportMetricsIfNeeded; - (void)onUserSettingsChanged:(NSNotification*)notification; - (void)applicationWillTerminate:(NSNotification*)notification; - (void)goToApplicationLifecycle:(nonnull NSString*)state; @@ -834,7 +834,7 @@ - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController { OCMReject([lifecycleChannel sendMessage:@"AppLifecycleState.inactive"]); } -- (void)testUpdateViewportMetricsDoesntInvokeEngineWhenNotTheViewController { +- (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine @@ -845,12 +845,12 @@ - (void)testUpdateViewportMetricsDoesntInvokeEngineWhenNotTheViewController { nibName:nil bundle:nil]; mockEngine.viewController = viewControllerB; - [viewControllerA updateViewportMetrics]; + [viewControllerA updateViewportMetricsIfNeeded]; flutter::ViewportMetrics viewportMetrics; OCMVerify(never(), [mockEngine updateViewportMetrics:viewportMetrics]); } -- (void)testUpdateViewportMetricsDoesInvokeEngineWhenIsTheViewController { +- (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine @@ -859,7 +859,85 @@ - (void)testUpdateViewportMetricsDoesInvokeEngineWhenIsTheViewController { mockEngine.viewController = viewController; flutter::ViewportMetrics viewportMetrics; OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs(); - [viewController updateViewportMetrics]; + [viewController updateViewportMetricsIfNeeded]; + OCMVerifyAll(mockEngine); +} + +- (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation { + FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); + [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); + mockEngine.viewController = viewController; + + id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator)); + OCMStub([mockCoordinator transitionDuration]).andReturn(0.5); + + // Mimic the device rotation. + [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator]; + // Should not trigger the engine call when during rotation. + [viewController updateViewportMetricsIfNeeded]; + + OCMVerify(never(), [mockEngine updateViewportMetrics:flutter::ViewportMetrics()]); +} + +- (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration { + FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); + [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); + mockEngine.viewController = viewController; + + // Mimic the device rotation with non-zero transition duration. + NSTimeInterval transitionDuration = 0.5; + id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator)); + OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration); + + flutter::ViewportMetrics viewportMetrics; + OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs(); + + [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator]; + // Should not immediately call the engine (this request should be ignored). + [viewController updateViewportMetricsIfNeeded]; + OCMVerify(never(), [mockEngine updateViewportMetrics:flutter::ViewportMetrics()]); + + // Should delay the engine call for half of the transition duration. + // Wait for additional transitionDuration to allow updateViewportMetrics calls if any. + XCTWaiterResult result = [XCTWaiter + waitForExpectations:@[ [self expectationWithDescription:@"Waiting for rotation duration"] ] + timeout:transitionDuration]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); + + OCMVerifyAll(mockEngine); +} + +- (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration { + FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); + [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([viewControllerMock mainScreenIfViewLoaded]).andReturn(UIScreen.mainScreen); + mockEngine.viewController = viewController; + + // Mimic the device rotation with zero transition duration. + id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator)); + OCMStub([mockCoordinator transitionDuration]).andReturn(0); + + flutter::ViewportMetrics viewportMetrics; + OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs(); + + // Should immediately trigger the engine call, without delay. + [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator]; + [viewController updateViewportMetricsIfNeeded]; + OCMVerifyAll(mockEngine); } From 20034a8d62c43ad3340a3ac09e36d01cf1d91d67 Mon Sep 17 00:00:00 2001 From: godofredoc Date: Mon, 17 Apr 2023 14:47:49 -0700 Subject: [PATCH 7/7] Reland "Migrate mac_host_engine to engine v2 builds." (#41279) Reverts flutter/engine#41233 The issue this PR was reverted was not replicable. On top of that we added extra precautions to ensure global generators are using left over binaries from out folder. https://flutter-review.googlesource.com/c/recipes/+/41960 --- .ci.yaml | 1 + ci/builders/mac_host_engine.json | 39 +++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 5da50639ec06f..3800087efda15 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -332,6 +332,7 @@ targets: timeout: 60 - name: Mac Host Engine + bringup: true recipe: engine/engine properties: gclient_variables: >- diff --git a/ci/builders/mac_host_engine.json b/ci/builders/mac_host_engine.json index 9be8803d1ab73..5bc7dc7a508ab 100644 --- a/ci/builders/mac_host_engine.json +++ b/ci/builders/mac_host_engine.json @@ -10,7 +10,8 @@ "out/host_debug/zip_archives/darwin-x64/FlutterEmbedder.framework.zip", "out/host_debug/zip_archives/dart-sdk-darwin-x64.zip" ], - "name": "host_debug" + "name": "host_debug", + "realm": "production" } ], "drone_dimensions": [ @@ -67,7 +68,8 @@ "include_paths": [ "out/host_profile/zip_archives/darwin-x64-profile/artifacts.zip" ], - "name": "host_profile" + "name": "host_profile", + "realm": "production" } ], "drone_dimensions": [ @@ -122,7 +124,8 @@ "out/host_release/zip_archives/darwin-x64-release/artifacts.zip", "out/host_release/zip_archives/darwin-x64/font-subset.zip" ], - "name": "host_release" + "name": "host_release", + "realm": "production" } ], "drone_dimensions": [ @@ -184,7 +187,8 @@ "out/mac_debug_arm64/zip_archives/darwin-arm64/artifacts.zip", "out/mac_debug_arm64/zip_archives/dart-sdk-darwin-arm64.zip" ], - "name": "mac_debug_arm64" + "name": "mac_debug_arm64", + "realm": "production" } ], "drone_dimensions": [ @@ -225,7 +229,8 @@ "include_paths": [ "out/mac_profile_arm64/zip_archives/darwin-arm64-profile/artifacts.zip" ], - "name": "mac_profile_arm64" + "name": "mac_profile_arm64", + "realm": "production" } ], "drone_dimensions": [ @@ -264,7 +269,8 @@ "out/mac_release_arm64/zip_archives/darwin-arm64/font-subset.zip", "out/mac_release_arm64/zip_archives/darwin-arm64-release/artifacts.zip" ], - "name": "mac_release_arm64" + "name": "mac_release_arm64", + "realm": "production" } ], "drone_dimensions": [ @@ -392,31 +398,38 @@ "archives": [ { "source": "out/release/framework/FlutterMacOS.dSYM.zip", - "destination": "darwin-x64-release/FlutterMacOS.dSYM.zip" + "destination": "darwin-x64-release/FlutterMacOS.dSYM.zip", + "realm": "production" }, { "source": "out/debug/framework/FlutterMacOS.framework.zip", - "destination": "darwin-x64/FlutterMacOS.framework.zip" + "destination": "darwin-x64/FlutterMacOS.framework.zip", + "realm": "production" }, { "source": "out/profile/framework/FlutterMacOS.framework.zip", - "destination": "darwin-x64-profile/FlutterMacOS.framework.zip" + "destination": "darwin-x64-profile/FlutterMacOS.framework.zip", + "realm": "production" }, { "source": "out/release/framework/FlutterMacOS.framework.zip", - "destination": "darwin-x64-release/FlutterMacOS.framework.zip" + "destination": "darwin-x64-release/FlutterMacOS.framework.zip", + "realm": "production" }, { "source": "out/debug/snapshot/gen_snapshot.zip", - "destination": "darwin-x64/gen_snapshot.zip" + "destination": "darwin-x64/gen_snapshot.zip", + "realm": "production" }, { "source": "out/profile/snapshot/gen_snapshot.zip", - "destination": "darwin-x64-profile/gen_snapshot.zip" + "destination": "darwin-x64-profile/gen_snapshot.zip", + "realm": "production" }, { "source": "out/release/snapshot/gen_snapshot.zip", - "destination": "darwin-x64-release/gen_snapshot.zip" + "destination": "darwin-x64-release/gen_snapshot.zip", + "realm": "production" } ] }