| 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
5 #import "shell/platform/darwin/ios/framework/Source/platform_views_controller.h"
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
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
14 #import "flutter/shell/platform/darwin/ios/ios_surface.h"
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.
111 BOOL canApplyBlurBackdrop = YES;
112
113 PlatformViewsController::PlatformViewsController()
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
120 void PlatformViewsController::SetTaskRunner(
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
133 void PlatformViewsController::SetFlutterViewController(
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
142 void PlatformViewsController::OnMethodCall(FlutterMethodCall* call, FlutterResult result) {
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 {
152 result(FlutterMethodNotImplemented);
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.dev/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
272 void PlatformViewsController::RegisterViewFactory(
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
287 void PlatformViewsController::CancelFrame() {
288 ResetFrameState();
289 }
290
291 PostPrerollResult PlatformViewsController::PostPrerollAction(
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
326 void PlatformViewsController::EndFrame(
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
340 void PlatformViewsController::PushFilterToVisitedPlatformViews(
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
350 void PlatformViewsController::PrerollCompositeEmbeddedView(
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
369 size_t PlatformViewsController::EmbeddedViewCount() const {
370 return composition_order_.size();
371 }
372
373 size_t PlatformViewsController::LayerPoolSize() const {
374 return layer_pool_->size();
375 }
376
377 UIView* PlatformViewsController::GetPlatformViewByID(int64_t view_id) {
378 return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView];
379 }
380
381 FlutterTouchInterceptingView* PlatformViewsController::GetFlutterTouchInterceptingViewByID(
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
389 long PlatformViewsController::FindFirstResponderPlatformViewId() {
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) {
499 canApplyBlurBackdrop = NO;
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_`.
540 void PlatformViewsController::CompositeWithParams(int64_t view_id,
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
579 DlCanvas* PlatformViewsController::CompositeEmbeddedView(int64_t view_id) {
580 return slices_[view_id]->canvas();
581 }
582
583 void PlatformViewsController::Reset() {
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