Flutter iOS Embedder
platform_views_controller.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include "flow/surface_frame.h"
8 #include "flutter/flow/view_slicer.h"
9 #include "flutter/fml/make_copyable.h"
10 #include "fml/synchronization/count_down_latch.h"
11 
15 
16 namespace {
17 
18 // The number of frames the rasterizer task runner will continue
19 // to run on the platform thread after no platform view is rendered.
20 //
21 // Note: this is an arbitrary number.
22 static const int kDefaultMergedLeaseDuration = 10;
23 
24 static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
25 
26 // Converts a SkMatrix to CATransform3D.
27 //
28 // Certain fields are ignored in CATransform3D since SkMatrix is 3x3 and CATransform3D is 4x4.
29 CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) {
30  // Skia only supports 2D transform so we don't map z.
31  CATransform3D transform = CATransform3DIdentity;
32  transform.m11 = matrix.getScaleX();
33  transform.m21 = matrix.getSkewX();
34  transform.m41 = matrix.getTranslateX();
35  transform.m14 = matrix.getPerspX();
36 
37  transform.m12 = matrix.getSkewY();
38  transform.m22 = matrix.getScaleY();
39  transform.m42 = matrix.getTranslateY();
40  transform.m24 = matrix.getPerspY();
41  return transform;
42 }
43 
44 // Reset the anchor of `layer` to match the transform operation from flow.
45 //
46 // The position of the `layer` should be unchanged after resetting the anchor.
47 void ResetAnchor(CALayer* layer) {
48  // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz.
49  layer.anchorPoint = CGPointZero;
50  layer.position = CGPointZero;
51 }
52 
53 CGRect GetCGRectFromSkRect(const SkRect& clipSkRect) {
54  return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
55  clipSkRect.fBottom - clipSkRect.fTop);
56 }
57 
58 // Determines if the `clip_rect` from a clipRect mutator contains the
59 // `platformview_boundingrect`.
60 //
61 // `clip_rect` is in its own coordinate space. The rect needs to be transformed by
62 // `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
63 //
64 // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
65 // space where the PlatformView is displayed.
66 bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect,
67  const SkRect& platformview_boundingrect,
68  const SkMatrix& transform_matrix) {
69  SkRect transformed_rect = transform_matrix.mapRect(clip_rect);
70  return transformed_rect.contains(platformview_boundingrect);
71 }
72 
73 // Determines if the `clipRRect` from a clipRRect mutator contains the
74 // `platformview_boundingrect`.
75 //
76 // `clip_rrect` is in its own coordinate space. The rrect needs to be transformed by
77 // `transform_matrix` to be in the coordinate space where the PlatformView is displayed.
78 //
79 // `platformview_boundingrect` is the final bounding rect of the PlatformView in the coordinate
80 // space where the PlatformView is displayed.
81 bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
82  const SkRect& platformview_boundingrect,
83  const SkMatrix& transform_matrix) {
84  SkVector upper_left = clip_rrect.radii(SkRRect::Corner::kUpperLeft_Corner);
85  SkVector upper_right = clip_rrect.radii(SkRRect::Corner::kUpperRight_Corner);
86  SkVector lower_right = clip_rrect.radii(SkRRect::Corner::kLowerRight_Corner);
87  SkVector lower_left = clip_rrect.radii(SkRRect::Corner::kLowerLeft_Corner);
88  SkScalar transformed_upper_left_x = transform_matrix.mapRadius(upper_left.x());
89  SkScalar transformed_upper_left_y = transform_matrix.mapRadius(upper_left.y());
90  SkScalar transformed_upper_right_x = transform_matrix.mapRadius(upper_right.x());
91  SkScalar transformed_upper_right_y = transform_matrix.mapRadius(upper_right.y());
92  SkScalar transformed_lower_right_x = transform_matrix.mapRadius(lower_right.x());
93  SkScalar transformed_lower_right_y = transform_matrix.mapRadius(lower_right.y());
94  SkScalar transformed_lower_left_x = transform_matrix.mapRadius(lower_left.x());
95  SkScalar transformed_lower_left_y = transform_matrix.mapRadius(lower_left.y());
96  SkRect transformed_clip_rect = transform_matrix.mapRect(clip_rrect.rect());
97  SkRRect transformed_rrect;
98  SkVector corners[] = {{transformed_upper_left_x, transformed_upper_left_y},
99  {transformed_upper_right_x, transformed_upper_right_y},
100  {transformed_lower_right_x, transformed_lower_right_y},
101  {transformed_lower_left_x, transformed_lower_left_y}};
102  transformed_rrect.setRectRadii(transformed_clip_rect, corners);
103  return transformed_rrect.contains(platformview_boundingrect);
104 }
105 
106 } // namespace
107 
108 namespace flutter {
109 
110 // Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
112 
114  : layer_pool_(std::make_unique<OverlayLayerPool>()),
115  weak_factory_(std::make_unique<fml::WeakPtrFactory<PlatformViewsController>>(this)) {
116  mask_view_pool_.reset(
117  [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity]);
118 };
119 
121  const fml::RefPtr<fml::TaskRunner>& platform_task_runner) {
122  platform_task_runner_ = platform_task_runner;
123 }
124 
125 fml::WeakPtr<flutter::PlatformViewsController> PlatformViewsController::GetWeakPtr() {
126  return weak_factory_->GetWeakPtr();
127 }
128 
129 void PlatformViewsController::SetFlutterView(UIView* flutter_view) {
130  flutter_view_.reset(flutter_view);
131 }
132 
134  UIViewController<FlutterViewResponder>* flutter_view_controller) {
135  flutter_view_controller_.reset(flutter_view_controller);
136 }
137 
138 UIViewController<FlutterViewResponder>* PlatformViewsController::GetFlutterViewController() {
139  return flutter_view_controller_.get();
140 }
141 
143  if ([[call method] isEqualToString:@"create"]) {
144  OnCreate(call, result);
145  } else if ([[call method] isEqualToString:@"dispose"]) {
146  OnDispose(call, result);
147  } else if ([[call method] isEqualToString:@"acceptGesture"]) {
148  OnAcceptGesture(call, result);
149  } else if ([[call method] isEqualToString:@"rejectGesture"]) {
150  OnRejectGesture(call, result);
151  } else {
153  }
154 }
155 
156 void PlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult result) {
157  NSDictionary<NSString*, id>* args = [call arguments];
158 
159  int64_t viewId = [args[@"id"] longLongValue];
160  NSString* viewTypeString = args[@"viewType"];
161  std::string viewType(viewTypeString.UTF8String);
162 
163  if (platform_views_.count(viewId) != 0) {
164  result([FlutterError errorWithCode:@"recreating_view"
165  message:@"trying to create an already created view"
166  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
167  return;
168  }
169 
170  NSObject<FlutterPlatformViewFactory>* factory = factories_[viewType].get();
171  if (factory == nil) {
172  result([FlutterError
173  errorWithCode:@"unregistered_view_type"
174  message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a "
175  @"PlatformView with an unregistered type: < %@ >",
176  viewTypeString]
177  details:@"If you are the author of the PlatformView, make sure `registerViewFactory` "
178  @"is invoked.\n"
179  @"See: "
180  @"https://docs.flutter.cn/development/platform-integration/"
181  @"platform-views#on-the-platform-side-1 for more details.\n"
182  @"If you are not the author of the PlatformView, make sure to call "
183  @"`GeneratedPluginRegistrant.register`."]);
184  return;
185  }
186 
187  id params = nil;
188  if ([factory respondsToSelector:@selector(createArgsCodec)]) {
189  NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
190  if (codec != nil && args[@"params"] != nil) {
191  FlutterStandardTypedData* paramsData = args[@"params"];
192  params = [codec decode:paramsData.data];
193  }
194  }
195 
196  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero
197  viewIdentifier:viewId
198  arguments:params];
199  UIView* platform_view = [embedded_view view];
200  // Set a unique view identifier, so the platform view can be identified in unit tests.
201  platform_view.accessibilityIdentifier =
202  [NSString stringWithFormat:@"platform_view[%lld]", viewId];
203 
204  FlutterTouchInterceptingView* touch_interceptor = [[FlutterTouchInterceptingView alloc]
205  initWithEmbeddedView:platform_view
206  platformViewsController:GetWeakPtr()
207  gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies_[viewType]];
208 
209  ChildClippingView* clipping_view = [[ChildClippingView alloc] initWithFrame:CGRectZero];
210  [clipping_view addSubview:touch_interceptor];
211 
212  platform_views_.emplace(
213  viewId, PlatformViewData{
214  .view = fml::scoped_nsobject<NSObject<FlutterPlatformView>>(embedded_view), //
215  .touch_interceptor =
216  fml::scoped_nsobject<FlutterTouchInterceptingView>(touch_interceptor), //
217  .root_view = fml::scoped_nsobject<UIView>(clipping_view) //
218  });
219 
220  result(nil);
221 }
222 
223 void PlatformViewsController::OnDispose(FlutterMethodCall* call, FlutterResult result) {
224  NSNumber* arg = [call arguments];
225  int64_t viewId = [arg longLongValue];
226 
227  if (platform_views_.count(viewId) == 0) {
228  result([FlutterError errorWithCode:@"unknown_view"
229  message:@"trying to dispose an unknown"
230  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
231  return;
232  }
233  // We wait for next submitFrame to dispose views.
234  views_to_dispose_.insert(viewId);
235  result(nil);
236 }
237 
238 void PlatformViewsController::OnAcceptGesture(FlutterMethodCall* call, FlutterResult result) {
239  NSDictionary<NSString*, id>* args = [call arguments];
240  int64_t viewId = [args[@"id"] longLongValue];
241 
242  if (platform_views_.count(viewId) == 0) {
243  result([FlutterError errorWithCode:@"unknown_view"
244  message:@"trying to set gesture state for an unknown view"
245  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
246  return;
247  }
248 
249  FlutterTouchInterceptingView* view = platform_views_[viewId].touch_interceptor.get();
250  [view releaseGesture];
251 
252  result(nil);
253 }
254 
255 void PlatformViewsController::OnRejectGesture(FlutterMethodCall* call, FlutterResult result) {
256  NSDictionary<NSString*, id>* args = [call arguments];
257  int64_t viewId = [args[@"id"] longLongValue];
258 
259  if (platform_views_.count(viewId) == 0) {
260  result([FlutterError errorWithCode:@"unknown_view"
261  message:@"trying to set gesture state for an unknown view"
262  details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
263  return;
264  }
265 
266  FlutterTouchInterceptingView* view = platform_views_[viewId].touch_interceptor.get();
267  [view blockGesture];
268 
269  result(nil);
270 }
271 
273  NSObject<FlutterPlatformViewFactory>* factory,
274  NSString* factoryId,
275  FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) {
276  std::string idString([factoryId UTF8String]);
277  FML_CHECK(factories_.count(idString) == 0);
278  factories_[idString] = fml::scoped_nsobject<NSObject<FlutterPlatformViewFactory>>(factory);
279  gesture_recognizers_blocking_policies_[idString] = gestureRecognizerBlockingPolicy;
280 }
281 
282 void PlatformViewsController::BeginFrame(SkISize frame_size) {
283  ResetFrameState();
284  frame_size_ = frame_size;
285 }
286 
288  ResetFrameState();
289 }
290 
292  const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger,
293  bool impeller_enabled) {
294  // TODO(jonahwilliams): remove this once Software backend is removed for iOS Sim.
295 #ifdef FML_OS_IOS_SIMULATOR
296  const bool merge_threads = true;
297 #else
298  const bool merge_threads = !impeller_enabled;
299 #endif // FML_OS_IOS_SIMULATOR
300 
301  if (merge_threads) {
302  if (composition_order_.empty()) {
303  return PostPrerollResult::kSuccess;
304  }
305  if (!raster_thread_merger->IsMerged()) {
306  // The raster thread merger may be disabled if the rasterizer is being
307  // created or teared down.
308  //
309  // In such cases, the current frame is dropped, and a new frame is attempted
310  // with the same layer tree.
311  //
312  // Eventually, the frame is submitted once this method returns `kSuccess`.
313  // At that point, the raster tasks are handled on the platform thread.
314  CancelFrame();
315  return PostPrerollResult::kSkipAndRetryFrame;
316  }
317  // If the post preroll action is successful, we will display platform views in the current
318  // frame. In order to sync the rendering of the platform views (quartz) with skia's rendering,
319  // We need to begin an explicit CATransaction. This transaction needs to be submitted
320  // after the current frame is submitted.
321  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
322  }
323  return PostPrerollResult::kSuccess;
324 }
325 
327  bool should_resubmit_frame,
328  const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger,
329  bool impeller_enabled) {
330 #if FML_OS_IOS_SIMULATOR
331  bool run_check = true;
332 #else
333  bool run_check = !impeller_enabled;
334 #endif // FML_OS_IOS_SIMULATOR
335  if (run_check && should_resubmit_frame) {
336  raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
337  }
338 }
339 
341  const std::shared_ptr<const DlImageFilter>& filter,
342  const SkRect& filter_rect) {
343  for (int64_t id : visited_platform_views_) {
344  EmbeddedViewParams params = current_composition_params_[id];
345  params.PushImageFilter(filter, filter_rect);
346  current_composition_params_[id] = params;
347  }
348 }
349 
351  int64_t view_id,
352  std::unique_ptr<EmbeddedViewParams> params) {
353  SkRect view_bounds = SkRect::Make(frame_size_);
354  std::unique_ptr<EmbedderViewSlice> view;
355  view = std::make_unique<DisplayListEmbedderViewSlice>(view_bounds);
356  slices_.insert_or_assign(view_id, std::move(view));
357 
358  composition_order_.push_back(view_id);
359 
360  if (current_composition_params_.count(view_id) == 1 &&
361  current_composition_params_[view_id] == *params.get()) {
362  // Do nothing if the params didn't change.
363  return;
364  }
365  current_composition_params_[view_id] = EmbeddedViewParams(*params.get());
366  views_to_recomposite_.insert(view_id);
367 }
368 
370  return composition_order_.size();
371 }
372 
374  return layer_pool_->size();
375 }
376 
378  return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView];
379 }
380 
382  int64_t view_id) {
383  if (platform_views_.empty()) {
384  return nil;
385  }
386  return platform_views_[view_id].touch_interceptor.get();
387 }
388 
390  for (auto const& [id, platform_view_data] : platform_views_) {
391  UIView* root_view = (UIView*)platform_view_data.root_view.get();
392  if (root_view.flt_hasFirstResponderInViewHierarchySubtree) {
393  return id;
394  }
395  }
396  return -1;
397 }
398 
399 void PlatformViewsController::ClipViewSetMaskView(UIView* clipView) {
400  FML_DCHECK([[NSThread currentThread] isMainThread]);
401  if (clipView.maskView) {
402  return;
403  }
404  UIView* flutterView = flutter_view_.get();
405  CGRect frame =
406  CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
407  CGRectGetWidth(flutterView.bounds), CGRectGetHeight(flutterView.bounds));
408  clipView.maskView = [mask_view_pool_.get() getMaskViewWithFrame:frame];
409 }
410 
411 // This method is only called when the `embedded_view` needs to be re-composited at the current
412 // frame. See: `CompositeWithParams` for details.
413 void PlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
414  UIView* embedded_view,
415  const SkRect& bounding_rect) {
416  if (flutter_view_ == nullptr) {
417  return;
418  }
419 
420  ResetAnchor(embedded_view.layer);
421  ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
422 
423  SkMatrix transformMatrix;
424  NSMutableArray* blurFilters = [[NSMutableArray alloc] init];
425  FML_DCHECK(!clipView.maskView ||
426  [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
427  if (clipView.maskView) {
428  [mask_view_pool_.get() insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
429  clipView.maskView = nil;
430  }
431  CGFloat screenScale = [UIScreen mainScreen].scale;
432  auto iter = mutators_stack.Begin();
433  while (iter != mutators_stack.End()) {
434  switch ((*iter)->GetType()) {
435  case kTransform: {
436  transformMatrix.preConcat((*iter)->GetMatrix());
437  break;
438  }
439  case kClipRect: {
440  if (ClipRectContainsPlatformViewBoundingRect((*iter)->GetRect(), bounding_rect,
441  transformMatrix)) {
442  break;
443  }
444  ClipViewSetMaskView(clipView);
445  [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
446  matrix:transformMatrix];
447  break;
448  }
449  case kClipRRect: {
450  if (ClipRRectContainsPlatformViewBoundingRect((*iter)->GetRRect(), bounding_rect,
451  transformMatrix)) {
452  break;
453  }
454  ClipViewSetMaskView(clipView);
455  [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
456  matrix:transformMatrix];
457  break;
458  }
459  case kClipPath: {
460  // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
461  // rect. See `ClipRRectContainsPlatformViewBoundingRect`.
462  // https://github.com/flutter/flutter/issues/118650
463  ClipViewSetMaskView(clipView);
464  [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
465  matrix:transformMatrix];
466  break;
467  }
468  case kOpacity:
469  embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
470  break;
471  case kBackdropFilter: {
472  // Only support DlBlurImageFilter for BackdropFilter.
473  if (!canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) {
474  break;
475  }
476  CGRect filterRect = GetCGRectFromSkRect((*iter)->GetFilterMutation().GetFilterRect());
477  // `filterRect` is in global coordinates. We need to convert to local space.
478  filterRect = CGRectApplyAffineTransform(
479  filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale));
480  // `filterRect` reprents the rect that should be filtered inside the `flutter_view_`.
481  // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
482  // filtered.
483  if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
484  break;
485  }
486  CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
487  CGRect frameInClipView = [flutter_view_.get() convertRect:intersection toView:clipView];
488  // sigma_x is arbitrarily chosen as the radius value because Quartz sets
489  // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
490  // is not supported in Quartz's gaussianBlur CAFilter, so it is not used
491  // to blur the PlatformView.
492  CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
493  UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
494  initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
495  PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView
496  blurRadius:blurRadius
497  visualEffectView:visualEffectView];
498  if (!filter) {
500  } else {
501  [blurFilters addObject:filter];
502  }
503  break;
504  }
505  }
506  ++iter;
507  }
508 
509  if (canApplyBlurBackdrop) {
510  [clipView applyBlurBackdropFilters:blurFilters];
511  }
512 
513  // The UIKit frame is set based on the logical resolution (points) instead of physical.
514  // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
515  // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
516  // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
517  // down to the logical resoltion before applying it to the layer of PlatformView.
518  transformMatrix.postScale(1 / screenScale, 1 / screenScale);
519 
520  // Reverse the offset of the clipView.
521  // The clipView's frame includes the final translate of the final transform matrix.
522  // Thus, this translate needs to be reversed so the platform view can layout at the correct
523  // offset.
524  //
525  // Note that the transforms are not applied to the clipping paths because clipping paths happen on
526  // the mask view, whose origin is always (0,0) to the flutter_view.
527  transformMatrix.postTranslate(-clipView.frame.origin.x, -clipView.frame.origin.y);
528 
529  embedded_view.layer.transform = GetCATransform3DFromSkMatrix(transformMatrix);
530 }
531 
532 // Composite the PlatformView with `view_id`.
533 //
534 // Every frame, during the paint traversal of the layer tree, this method is called for all
535 // the PlatformViews in `views_to_recomposite_`.
536 //
537 // Note that `views_to_recomposite_` does not represent all the views in the view hierarchy,
538 // if a PlatformView does not change its composition parameter from last frame, it is not
539 // included in the `views_to_recomposite_`.
541  const EmbeddedViewParams& params) {
542  CGRect frame = CGRectMake(0, 0, params.sizePoints().width(), params.sizePoints().height());
543  FlutterTouchInterceptingView* touchInterceptor = platform_views_[view_id].touch_interceptor.get();
544 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
545  FML_DCHECK(CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero));
546  if (non_zero_origin_views_.find(view_id) == non_zero_origin_views_.end() &&
547  !CGPointEqualToPoint([touchInterceptor embeddedView].frame.origin, CGPointZero)) {
548  non_zero_origin_views_.insert(view_id);
549  NSLog(
550  @"A Embedded PlatformView's origin is not CGPointZero.\n"
551  " View id: %@\n"
552  " View info: \n %@ \n"
553  "A non-zero origin might cause undefined behavior.\n"
554  "See https://github.com/flutter/flutter/issues/109700 for more details.\n"
555  "If you are the author of the PlatformView, please update the implementation of the "
556  "PlatformView to have a (0, 0) origin.\n"
557  "If you have a valid case of using a non-zero origin, "
558  "please leave a comment at https://github.com/flutter/flutter/issues/109700 with details.",
559  @(view_id), [touchInterceptor embeddedView]);
560  }
561 #endif
562  touchInterceptor.layer.transform = CATransform3DIdentity;
563  touchInterceptor.frame = frame;
564  touchInterceptor.alpha = 1;
565 
566  const MutatorsStack& mutatorStack = params.mutatorsStack();
567  UIView* clippingView = platform_views_[view_id].root_view.get();
568  // The frame of the clipping view should be the final bounding rect.
569  // Because the translate matrix in the Mutator Stack also includes the offset,
570  // when we apply the transforms matrix in |ApplyMutators|, we need
571  // to remember to do a reverse translate.
572  const SkRect& rect = params.finalBoundingRect();
573  CGFloat screenScale = [UIScreen mainScreen].scale;
574  clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
575  rect.width() / screenScale, rect.height() / screenScale);
576  ApplyMutators(mutatorStack, touchInterceptor, rect);
577 }
578 
580  return slices_[view_id]->canvas();
581 }
582 
584  // Reset will only be called from the raster thread or a merged raster/platform thread.
585  // platform_views_ must only be modified on the platform thread, and any operations that
586  // read or modify platform views should occur there.
587  fml::TaskRunner::RunNowOrPostTask(
588  platform_task_runner_, [&, composition_order = composition_order_]() {
589  for (int64_t view_id : composition_order_) {
590  [platform_views_[view_id].root_view.get() removeFromSuperview];
591  }
592  platform_views_.clear();
593  });
594 
595  composition_order_.clear();
596  slices_.clear();
597  current_composition_params_.clear();
598  views_to_recomposite_.clear();
599  layer_pool_->RecycleLayers();
600  visited_platform_views_.clear();
601 }
602 
603 bool PlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
604  const std::shared_ptr<IOSContext>& ios_context,
605  std::unique_ptr<SurfaceFrame> background_frame) {
606  TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame");
607 
608  // No platform views to render; we're done.
609  if (flutter_view_ == nullptr || (composition_order_.empty() && !had_platform_views_)) {
610  had_platform_views_ = false;
611  return background_frame->Submit();
612  }
613  had_platform_views_ = !composition_order_.empty();
614 
615  bool did_encode = true;
616  LayersMap platform_view_layers;
617  std::vector<std::unique_ptr<SurfaceFrame>> surface_frames;
618  surface_frames.reserve(composition_order_.size());
619  std::unordered_map<int64_t, SkRect> view_rects;
620 
621  for (int64_t view_id : composition_order_) {
622  view_rects[view_id] = current_composition_params_[view_id].finalBoundingRect();
623  }
624 
625  std::unordered_map<int64_t, SkRect> overlay_layers =
626  SliceViews(background_frame->Canvas(), composition_order_, slices_, view_rects);
627 
628  size_t required_overlay_layers = 0;
629  for (int64_t view_id : composition_order_) {
630  std::unordered_map<int64_t, SkRect>::const_iterator overlay = overlay_layers.find(view_id);
631  if (overlay == overlay_layers.end()) {
632  continue;
633  }
634  required_overlay_layers++;
635  }
636 
637  // If there are not sufficient overlay layers, we must construct them on the platform
638  // thread, at least until we've refactored iOS surface creation to use IOSurfaces
639  // instead of CALayers.
640  CreateMissingOverlays(gr_context, ios_context, required_overlay_layers);
641 
642  int64_t overlay_id = 0;
643  for (int64_t view_id : composition_order_) {
644  std::unordered_map<int64_t, SkRect>::const_iterator overlay = overlay_layers.find(view_id);
645  if (overlay == overlay_layers.end()) {
646  continue;
647  }
648  std::shared_ptr<OverlayLayer> layer = GetExistingLayer();
649  if (!layer) {
650  continue;
651  }
652 
653  std::unique_ptr<SurfaceFrame> frame = layer->surface->AcquireFrame(frame_size_);
654  // If frame is null, AcquireFrame already printed out an error message.
655  if (!frame) {
656  continue;
657  }
658  DlCanvas* overlay_canvas = frame->Canvas();
659  int restore_count = overlay_canvas->GetSaveCount();
660  overlay_canvas->Save();
661  overlay_canvas->ClipRect(overlay->second);
662  overlay_canvas->Clear(DlColor::kTransparent());
663  slices_[view_id]->render_into(overlay_canvas);
664  overlay_canvas->RestoreToCount(restore_count);
665 
666  // This flutter view is never the last in a frame, since we always submit the
667  // underlay view last.
668  frame->set_submit_info({.frame_boundary = false, .present_with_transaction = true});
669  layer->did_submit_last_frame = frame->Encode();
670 
671  did_encode &= layer->did_submit_last_frame;
672  platform_view_layers[view_id] = LayerData{
673  .rect = overlay->second, //
674  .view_id = view_id, //
675  .overlay_id = overlay_id, //
676  .layer = layer //
677  };
678  surface_frames.push_back(std::move(frame));
679  overlay_id++;
680  }
681 
682  auto previous_submit_info = background_frame->submit_info();
683  background_frame->set_submit_info({
684  .frame_damage = previous_submit_info.frame_damage,
685  .buffer_damage = previous_submit_info.buffer_damage,
686  .present_with_transaction = true,
687  });
688  background_frame->Encode();
689  surface_frames.push_back(std::move(background_frame));
690 
691  // Mark all layers as available, so they can be used in the next frame.
692  std::vector<std::shared_ptr<OverlayLayer>> unused_layers = layer_pool_->RemoveUnusedLayers();
693  layer_pool_->RecycleLayers();
694 
695  auto task = [&, //
696  platform_view_layers = std::move(platform_view_layers), //
697  current_composition_params = current_composition_params_, //
698  views_to_recomposite = views_to_recomposite_, //
699  composition_order = composition_order_, //
700  unused_layers = std::move(unused_layers), //
701  surface_frames = std::move(surface_frames) //
702  ]() mutable {
703  PerformSubmit(platform_view_layers, //
704  current_composition_params, //
705  views_to_recomposite, //
706  composition_order, //
707  unused_layers, //
708  surface_frames //
709  );
710  };
711 
712  fml::TaskRunner::RunNowOrPostTask(platform_task_runner_, fml::MakeCopyable(std::move(task)));
713 
714  return did_encode;
715 }
716 
717 void PlatformViewsController::CreateMissingOverlays(GrDirectContext* gr_context,
718  const std::shared_ptr<IOSContext>& ios_context,
719  size_t required_overlay_layers) {
720  TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers");
721 
722  if (required_overlay_layers <= layer_pool_->size()) {
723  return;
724  }
725  auto missing_layer_count = required_overlay_layers - layer_pool_->size();
726 
727  // If the raster thread isn't merged, create layers on the platform thread and block until
728  // complete.
729  auto latch = std::make_shared<fml::CountDownLatch>(1u);
730  fml::TaskRunner::RunNowOrPostTask(platform_task_runner_, [&]() {
731  for (auto i = 0u; i < missing_layer_count; i++) {
732  CreateLayer(gr_context, //
733  ios_context, //
734  ((FlutterView*)flutter_view_.get()).pixelFormat //
735  );
736  }
737  latch->CountDown();
738  });
739  if (![[NSThread currentThread] isMainThread]) {
740  latch->Wait();
741  }
742 }
743 
744 /// Update the buffers and mutate the platform views in CATransaction on the platform thread.
745 void PlatformViewsController::PerformSubmit(
746  const LayersMap& platform_view_layers,
747  std::unordered_map<int64_t, EmbeddedViewParams>& current_composition_params,
748  const std::unordered_set<int64_t>& views_to_recomposite,
749  const std::vector<int64_t>& composition_order,
750  const std::vector<std::shared_ptr<OverlayLayer>>& unused_layers,
751  const std::vector<std::unique_ptr<SurfaceFrame>>& surface_frames) {
752  TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit");
753  FML_DCHECK([[NSThread currentThread] isMainThread]);
754 
755  [CATransaction begin];
756 
757  // Configure Flutter overlay views.
758  for (const auto& [view_id, layer_data] : platform_view_layers) {
759  layer_data.layer->UpdateViewState(flutter_view_, //
760  layer_data.rect, //
761  layer_data.view_id, //
762  layer_data.overlay_id //
763  );
764  }
765 
766  // Dispose unused Flutter Views.
767  for (auto& view : GetViewsToDispose()) {
768  [view removeFromSuperview];
769  }
770 
771  // Composite Platform Views.
772  for (int64_t view_id : views_to_recomposite) {
773  CompositeWithParams(view_id, current_composition_params[view_id]);
774  }
775 
776  // Present callbacks.
777  for (const auto& frame : surface_frames) {
778  frame->Submit();
779  }
780 
781  // If a layer was allocated in the previous frame, but it's not used in the current frame,
782  // then it can be removed from the scene.
783  RemoveUnusedLayers(unused_layers, composition_order);
784 
785  // Organize the layers by their z indexes.
786  BringLayersIntoView(platform_view_layers, composition_order);
787 
788  [CATransaction commit];
789 }
790 
791 void PlatformViewsController::BringLayersIntoView(const LayersMap& layer_map,
792  const std::vector<int64_t>& composition_order) {
793  FML_DCHECK(flutter_view_);
794  UIView* flutter_view = flutter_view_.get();
795 
796  previous_composition_order_.clear();
797  NSMutableArray* desired_platform_subviews = [NSMutableArray array];
798  for (int64_t platform_view_id : composition_order) {
799  previous_composition_order_.push_back(platform_view_id);
800  UIView* platform_view_root = platform_views_[platform_view_id].root_view.get();
801  if (platform_view_root != nil) {
802  [desired_platform_subviews addObject:platform_view_root];
803  }
804 
805  auto maybe_layer_data = layer_map.find(platform_view_id);
806  if (maybe_layer_data != layer_map.end()) {
807  auto view = maybe_layer_data->second.layer->overlay_view_wrapper;
808  if (view != nil) {
809  [desired_platform_subviews addObject:view];
810  }
811  }
812  }
813 
814  NSSet* desired_platform_subviews_set = [NSSet setWithArray:desired_platform_subviews];
815  NSArray* existing_platform_subviews = [flutter_view.subviews
816  filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object,
817  NSDictionary* bindings) {
818  return [desired_platform_subviews_set containsObject:object];
819  }]];
820 
821  // Manipulate view hierarchy only if needed, to address a performance issue where
822  // `BringLayersIntoView` is called even when view hierarchy stays the same.
823  // See: https://github.com/flutter/flutter/issues/121833
824  // TODO(hellohuanlin): investigate if it is possible to skip unnecessary BringLayersIntoView.
825  if (![desired_platform_subviews isEqualToArray:existing_platform_subviews]) {
826  for (UIView* subview in desired_platform_subviews) {
827  // `addSubview` will automatically reorder subview if it is already added.
828  [flutter_view addSubview:subview];
829  }
830  }
831 }
832 
833 std::shared_ptr<OverlayLayer> PlatformViewsController::GetExistingLayer() {
834  return layer_pool_->GetNextLayer();
835 }
836 
837 void PlatformViewsController::CreateLayer(GrDirectContext* gr_context,
838  const std::shared_ptr<IOSContext>& ios_context,
839  MTLPixelFormat pixel_format) {
840  layer_pool_->CreateLayer(gr_context, ios_context, pixel_format);
841 }
842 
843 void PlatformViewsController::RemoveUnusedLayers(
844  const std::vector<std::shared_ptr<OverlayLayer>>& unused_layers,
845  const std::vector<int64_t>& composition_order) {
846  for (const std::shared_ptr<OverlayLayer>& layer : unused_layers) {
847  [layer->overlay_view_wrapper removeFromSuperview];
848  }
849 
850  std::unordered_set<int64_t> composition_order_set;
851  for (int64_t view_id : composition_order) {
852  composition_order_set.insert(view_id);
853  }
854  // Remove unused platform views.
855  for (int64_t view_id : previous_composition_order_) {
856  if (composition_order_set.find(view_id) == composition_order_set.end()) {
857  UIView* platform_view_root = platform_views_[view_id].root_view.get();
858  [platform_view_root removeFromSuperview];
859  }
860  }
861 }
862 
863 std::vector<UIView*> PlatformViewsController::GetViewsToDispose() {
864  std::vector<UIView*> views;
865  if (views_to_dispose_.empty()) {
866  return views;
867  }
868 
869  std::unordered_set<int64_t> views_to_composite(composition_order_.begin(),
870  composition_order_.end());
871  std::unordered_set<int64_t> views_to_delay_dispose;
872  for (int64_t viewId : views_to_dispose_) {
873  if (views_to_composite.count(viewId)) {
874  views_to_delay_dispose.insert(viewId);
875  continue;
876  }
877  UIView* root_view = platform_views_[viewId].root_view.get();
878  views.push_back(root_view);
879  current_composition_params_.erase(viewId);
880  views_to_recomposite_.erase(viewId);
881  platform_views_.erase(viewId);
882  }
883  views_to_dispose_ = std::move(views_to_delay_dispose);
884  return views;
885 }
886 
887 void PlatformViewsController::ResetFrameState() {
888  slices_.clear();
889  composition_order_.clear();
890  visited_platform_views_.clear();
891 }
892 
893 } // namespace flutter
flutter::PlatformViewsController::FindFirstResponderPlatformViewId
long FindFirstResponderPlatformViewId()
Returns the platform view id if the platform view (or any of its descendant view) is the first respon...
Definition: platform_views_controller.mm:389
flutter::PlatformViewsController::PostPrerollAction
PostPrerollResult PostPrerollAction(const fml::RefPtr< fml::RasterThreadMerger > &raster_thread_merger, bool impeller_enabled)
Determine if thread merging is required after prerolling platform views.
Definition: platform_views_controller.mm:291
flutter::PlatformViewsController::SetTaskRunner
void SetTaskRunner(const fml::RefPtr< fml::TaskRunner > &platform_task_runner)
Set the platform task runner used to post rendering tasks.
Definition: platform_views_controller.mm:120
flutter::PlatformViewsController::EndFrame
void EndFrame(bool should_resubmit_frame, const fml::RefPtr< fml::RasterThreadMerger > &raster_thread_merger, bool impeller_enabled)
Mark the end of a compositor frame.
Definition: platform_views_controller.mm:326
platform_views_controller.h
flutter::PlatformViewsController::PlatformViewsController
PlatformViewsController()
Definition: platform_views_controller.mm:113
flutter::PlatformViewsController::SetFlutterView
void SetFlutterView(UIView *flutter_view) __attribute__((cf_audited_transfer))
Set the flutter view.
Definition: platform_views_controller.mm:129
flutter::PlatformViewsController::LayerPoolSize
size_t LayerPoolSize() const
Definition: platform_views_controller.mm:373
FlutterMethodNotImplemented
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
FlutterError
Definition: FlutterCodecs.h:246
flutter::PlatformViewsController::CompositeEmbeddedView
DlCanvas * CompositeEmbeddedView(int64_t view_id)
Returns the Canvas for the overlay slice for the given platform view.
Definition: platform_views_controller.mm:579
flutter::PlatformViewsController::EmbeddedViewCount
size_t EmbeddedViewCount() const
Definition: platform_views_controller.mm:369
flutter::PlatformViewsController::PrerollCompositeEmbeddedView
void PrerollCompositeEmbeddedView(int64_t view_id, std::unique_ptr< flutter::EmbeddedViewParams > params)
Record a platform view in the layer tree to be rendered, along with the positioning and mutator param...
Definition: platform_views_controller.mm:350
flutter::canApplyBlurBackdrop
BOOL canApplyBlurBackdrop
Definition: platform_views_controller.mm:111
initWithFrame
instancetype initWithFrame
Definition: FlutterTextInputPlugin.h:172
platform_view
std::unique_ptr< flutter::PlatformViewIOS > platform_view
Definition: FlutterEnginePlatformViewTest.mm:65
ios_surface.h
FlutterMethodCall
Definition: FlutterCodecs.h:220
flutter::PlatformViewsController::GetFlutterViewController
UIViewController< FlutterViewResponder > * GetFlutterViewController() __attribute__((cf_audited_transfer))
Retrieve the view controller.
Definition: platform_views_controller.mm:138
flutter::PlatformViewsController::SubmitFrame
bool SubmitFrame(GrDirectContext *gr_context, const std::shared_ptr< IOSContext > &ios_context, std::unique_ptr< SurfaceFrame > frame)
Encode rendering for the Flutter overlay views and queue up perform platform view mutations.
Definition: platform_views_controller.mm:603
flutter
Definition: accessibility_bridge.h:28
FlutterOverlayView.h
flutter::PlatformViewsController::Reset
void Reset()
Discards all platform views instances and auxiliary resources.
Definition: platform_views_controller.mm:583
flutter::PlatformViewsController::BeginFrame
void BeginFrame(SkISize frame_size)
Mark the beginning of a frame and record the size of the onscreen.
Definition: platform_views_controller.mm:282
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterPlatformViewGestureRecognizersBlockingPolicy
FlutterPlatformViewGestureRecognizersBlockingPolicy
Definition: FlutterPlugin.h:252
FlutterClippingMaskViewPool
Definition: FlutterPlatformViews_Internal.h:66
flutter::PlatformViewsController::RegisterViewFactory
void RegisterViewFactory(NSObject< FlutterPlatformViewFactory > *factory, NSString *factoryId, FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy) __attribute__((cf_audited_transfer))
set the factory used to construct embedded UI Views.
Definition: platform_views_controller.mm:272
flutter::PlatformViewsController::SetFlutterViewController
void SetFlutterViewController(UIViewController< FlutterViewResponder > *flutter_view_controller) __attribute__((cf_audited_transfer))
Set the flutter view controller.
Definition: platform_views_controller.mm:133
ChildClippingView
Definition: FlutterPlatformViews_Internal.h:123
FlutterStandardTypedData
Definition: FlutterCodecs.h:300
flutter::PlatformViewsController::PushFilterToVisitedPlatformViews
void PushFilterToVisitedPlatformViews(const std::shared_ptr< const DlImageFilter > &filter, const SkRect &filter_rect)
Pushes backdrop filter mutation to the mutator stack of each visited platform view.
Definition: platform_views_controller.mm:340
FlutterView
Definition: FlutterView.h:34
FlutterStandardTypedData::data
NSData * data
Definition: FlutterCodecs.h:344
flutter::PlatformViewsController::GetPlatformViewByID
UIView * GetPlatformViewByID(int64_t view_id)
Definition: platform_views_controller.mm:377
flutter::PlatformViewsController::OnMethodCall
void OnMethodCall(FlutterMethodCall *call, FlutterResult result) __attribute__((cf_audited_transfer))
Handler for platform view message channels.
Definition: platform_views_controller.mm:142
flutter::PlatformViewsController
Composites Flutter UI and overlay layers alongside embedded UIViews.
Definition: platform_views_controller.h:31
PlatformViewFilter
Definition: FlutterPlatformViews_Internal.h:84
flutter::PlatformViewsController::CompositeWithParams
void CompositeWithParams(int64_t view_id, const EmbeddedViewParams &params)
Definition: platform_views_controller.mm:540
flutter::PlatformViewsController::GetFlutterTouchInterceptingViewByID
FlutterTouchInterceptingView * GetFlutterTouchInterceptingViewByID(int64_t view_id)
Returns theFlutterTouchInterceptingView with the provided view_id.
Definition: platform_views_controller.mm:381
flutter::PlatformViewsController::GetWeakPtr
fml::WeakPtr< flutter::PlatformViewsController > GetWeakPtr()
Retrieve a weak pointer to this controller.
Definition: platform_views_controller.mm:125
FlutterView.h
FlutterTouchInterceptingView
Definition: FlutterPlatformViews_Internal.h:138
flutter::PlatformViewsController::CancelFrame
void CancelFrame()
Cancel the current frame, indicating that no platform views are composited.
Definition: platform_views_controller.mm:287
flutter::OverlayLayerPool
Storage for Overlay layers across frames.
Definition: overlay_layer_pool.h:53
FlutterClippingMaskView
Definition: FlutterPlatformViews_Internal.h:35