share platform view slicing logic across iOS and Android. (flutter/engine#54010)

This removes support for "unobstructed platform views" on iOS - instead prefering to use the Android strategy of minimizing overlay layers, as this is generally more performant.
This commit is contained in:
Jonah Williams
2024-07-24 11:02:09 -07:00
committed by GitHub
parent 851a3e5829
commit 9d7ce06c95
10 changed files with 334 additions and 183 deletions

View File

@@ -80,6 +80,7 @@
../../../flutter/flow/surface_frame_unittests.cc
../../../flutter/flow/testing
../../../flutter/flow/texture_unittests.cc
../../../flutter/flow/view_slicer_unittests.cc
../../../flutter/flutter_frontend_server
../../../flutter/fml/ascii_trie_unittests.cc
../../../flutter/fml/backtrace_unittests.cc

View File

@@ -41734,6 +41734,8 @@ ORIGIN: ../../../flutter/flow/surface.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface_frame.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/surface_frame.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/view_slicer.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/view_slicer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flutter_vma/flutter_skia_vma.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flutter_vma/flutter_skia_vma.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flutter_vma/flutter_vma.cc + ../../../flutter/LICENSE
@@ -44616,6 +44618,8 @@ FILE: ../../../flutter/flow/surface.cc
FILE: ../../../flutter/flow/surface.h
FILE: ../../../flutter/flow/surface_frame.cc
FILE: ../../../flutter/flow/surface_frame.h
FILE: ../../../flutter/flow/view_slicer.cc
FILE: ../../../flutter/flow/view_slicer.h
FILE: ../../../flutter/flutter_vma/flutter_skia_vma.cc
FILE: ../../../flutter/flutter_vma/flutter_skia_vma.h
FILE: ../../../flutter/flutter_vma/flutter_vma.cc

View File

@@ -88,6 +88,8 @@ source_set("flow") {
"surface.h",
"surface_frame.cc",
"surface_frame.h",
"view_slicer.cc",
"view_slicer.h",
]
public_configs = [ "//flutter:config" ]
@@ -183,6 +185,7 @@ if (enable_unittests) {
"testing/mock_layer_unittests.cc",
"testing/mock_texture_unittests.cc",
"texture_unittests.cc",
"view_slicer_unittests.cc",
]
deps = [

View File

@@ -0,0 +1,116 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/flow/view_slicer.h"
#include <unordered_map>
#include "flow/embedded_views.h"
#include "fml/logging.h"
namespace flutter {
std::unordered_map<int64_t, SkRect> SliceViews(
DlCanvas* background_canvas,
const std::vector<int64_t>& composition_order,
const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&
slices,
const std::unordered_map<int64_t, SkRect>& view_rects) {
std::unordered_map<int64_t, SkRect> overlay_layers;
auto current_frame_view_count = composition_order.size();
// Restore the clip context after exiting this method since it's changed
// below.
DlAutoCanvasRestore save(background_canvas, /*do_save=*/true);
for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order[i];
EmbedderViewSlice* slice = slices.at(view_id).get();
if (slice->canvas() == nullptr) {
continue;
}
slice->end_recording();
SkRect full_joined_rect = SkRect::MakeEmpty();
// Determinate if Flutter UI intersects with any of the previous
// platform views stacked by z position.
//
// This is done by querying the r-tree that holds the records for the
// picture recorder corresponding to the flow layers added after a platform
// view layer.
for (int j = i; j >= 0; j--) {
int64_t current_view_id = composition_order[j];
auto maybe_rect = view_rects.find(current_view_id);
FML_DCHECK(maybe_rect != view_rects.end());
if (maybe_rect == view_rects.end()) {
continue;
}
SkRect current_view_rect = maybe_rect->second;
const SkIRect rounded_in_platform_view_rect = current_view_rect.roundIn();
// Each rect corresponds to a native view that renders Flutter UI.
std::vector<SkIRect> intersection_rects =
slice->region(current_view_rect).getRects();
// Ignore intersections of single width/height on the edge of the platform
// view.
// This is to address the following performance issue when interleaving
// adjacent platform views and layers: Since we `roundOut` both platform
// view rects and the layer rects, as long as the coordinate is
// fractional, there will be an intersection of a single pixel width (or
// height) after rounding out, even if they do not intersect before
// rounding out. We have to round out both platform view rect and the
// layer rect. Rounding in platform view rect will result in missing pixel
// on the intersection edge. Rounding in layer rect will result in missing
// pixel on the edge of the layer on top of the platform view.
for (auto it = intersection_rects.begin(); it != intersection_rects.end();
/*no-op*/) {
// If intersection_rect does not intersect with the *rounded in*
// platform view rect, then the intersection must be a single pixel
// width (or height) on edge.
if (!SkIRect::Intersects(*it, rounded_in_platform_view_rect)) {
it = intersection_rects.erase(it);
} else {
++it;
}
}
// Limit the number of native views, so it doesn't grow forever.
//
// In this case, the rects are merged into a single one that is the union
// of all the rects.
SkRect partial_joined_rect = SkRect::MakeEmpty();
for (const SkIRect& rect : intersection_rects) {
partial_joined_rect.join(SkRect::Make(rect));
}
// Get the intersection rect with the `current_view_rect`,
partial_joined_rect.intersect(SkRect::Make(current_view_rect.roundOut()));
// Join the `partial_joined_rect` into `full_joined_rect` to get the rect
// above the current `slice`
full_joined_rect.join(partial_joined_rect);
}
if (!full_joined_rect.isEmpty()) {
overlay_layers.insert({view_id, full_joined_rect});
// Clip the background canvas, so it doesn't contain any of the pixels
// drawn on the overlay layer.
background_canvas->ClipRect(full_joined_rect,
DlCanvas::ClipOp::kDifference);
}
slice->render_into(background_canvas);
}
// Manually trigger the DlAutoCanvasRestore before we submit the frame
save.Restore();
return overlay_layers;
}
} // namespace flutter

View File

@@ -0,0 +1,25 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_FLOW_VIEW_SLICER_H_
#define FLUTTER_FLOW_VIEW_SLICER_H_
#include <unordered_map>
#include "display_list/dl_canvas.h"
#include "flow/embedded_views.h"
namespace flutter {
/// @brief Compute the required overlay layers and clip the view slices
/// according to the size and position of the platform views.
std::unordered_map<int64_t, SkRect> SliceViews(
DlCanvas* background_canvas,
const std::vector<int64_t>& composition_order,
const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&
slices,
const std::unordered_map<int64_t, SkRect>& view_rects);
} // namespace flutter
#endif // FLUTTER_FLOW_VIEW_SLICER_H_

View File

@@ -0,0 +1,138 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <unordered_map>
#include "display_list/dl_builder.h"
#include "flow/embedded_views.h"
#include "flutter/flow/view_slicer.h"
#include "gtest/gtest.h"
namespace flutter {
namespace testing {
namespace {
void AddSliceOfSize(
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>& slices,
int64_t id,
SkRect rect) {
slices[id] = std::make_unique<DisplayListEmbedderViewSlice>(rect);
DlPaint paint;
paint.setColor(DlColor::kBlack());
slices[id]->canvas()->DrawRect(rect, paint);
}
} // namespace
TEST(ViewSlicerTest, CanSlicerNonOverlappingViews) {
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
std::vector<int64_t> composition_order = {1};
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(99, 99, 100, 100));
std::unordered_map<int64_t, SkRect> view_rects = {
{1, SkRect::MakeLTRB(50, 50, 60, 60)}};
auto computed_overlays =
SliceViews(&builder, composition_order, slices, view_rects);
EXPECT_TRUE(computed_overlays.empty());
}
TEST(ViewSlicerTest, IgnoresFractionalOverlaps) {
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
std::vector<int64_t> composition_order = {1};
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50.49, 50.49));
std::unordered_map<int64_t, SkRect> view_rects = {
{1, SkRect::MakeLTRB(50.5, 50.5, 100, 100)}};
auto computed_overlays =
SliceViews(&builder, composition_order, slices, view_rects);
EXPECT_TRUE(computed_overlays.empty());
}
TEST(ViewSlicerTest, ComputesOverlapWith1PV) {
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
std::vector<int64_t> composition_order = {1};
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50, 50));
std::unordered_map<int64_t, SkRect> view_rects = {
{1, SkRect::MakeLTRB(0, 0, 100, 100)}};
auto computed_overlays =
SliceViews(&builder, composition_order, slices, view_rects);
EXPECT_EQ(computed_overlays.size(), 1u);
auto overlay = computed_overlays.find(1);
ASSERT_NE(overlay, computed_overlays.end());
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 50, 50));
}
TEST(ViewSlicerTest, ComputesOverlapWith2PV) {
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
std::vector<int64_t> composition_order = {1, 2};
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50, 50));
AddSliceOfSize(slices, 2, SkRect::MakeLTRB(50, 50, 100, 100));
std::unordered_map<int64_t, SkRect> view_rects = {
{1, SkRect::MakeLTRB(0, 0, 50, 50)}, //
{2, SkRect::MakeLTRB(50, 50, 100, 100)}, //
};
auto computed_overlays =
SliceViews(&builder, composition_order, slices, view_rects);
EXPECT_EQ(computed_overlays.size(), 2u);
auto overlay = computed_overlays.find(1);
ASSERT_NE(overlay, computed_overlays.end());
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 50, 50));
overlay = computed_overlays.find(2);
ASSERT_NE(overlay, computed_overlays.end());
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(50, 50, 100, 100));
}
TEST(ViewSlicerTest, OverlappingTwoPVs) {
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
std::vector<int64_t> composition_order = {1, 2};
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
// This embeded view overlaps both platform views:
//
// [ A [ ]]
// [_____[ C ]]
// [ B [ ]]
// [ ]
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 0, 0));
AddSliceOfSize(slices, 2, SkRect::MakeLTRB(0, 0, 100, 100));
std::unordered_map<int64_t, SkRect> view_rects = {
{1, SkRect::MakeLTRB(0, 0, 50, 50)}, //
{2, SkRect::MakeLTRB(50, 50, 100, 100)}, //
};
auto computed_overlays =
SliceViews(&builder, composition_order, slices, view_rects);
EXPECT_EQ(computed_overlays.size(), 1u);
auto overlay = computed_overlays.find(2);
ASSERT_NE(overlay, computed_overlays.end());
// We create a single overlay for both overlapping sections.
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 100, 100));
}
} // namespace testing
} // namespace flutter

View File

@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h"
#include "flow/view_slicer.h"
#include "flutter/common/constants.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/trace_event.h"
@@ -78,72 +79,17 @@ void AndroidExternalViewEmbedder::SubmitFlutterView(
return;
}
std::unordered_map<int64_t, SkRect> overlay_layers;
DlCanvas* background_canvas = frame->Canvas();
auto current_frame_view_count = composition_order_.size();
// Restore the clip context after exiting this method since it's changed
// below.
DlAutoCanvasRestore save(background_canvas, /*do_save=*/true);
for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order_[i];
EmbedderViewSlice* slice = slices_.at(view_id).get();
if (slice->canvas() == nullptr) {
continue;
}
slice->end_recording();
SkRect full_joined_rect = SkRect::MakeEmpty();
// Determinate if Flutter UI intersects with any of the previous
// platform views stacked by z position.
//
// This is done by querying the r-tree that holds the records for the
// picture recorder corresponding to the flow layers added after a platform
// view layer.
for (ssize_t j = i; j >= 0; j--) {
int64_t current_view_id = composition_order_[j];
SkRect current_view_rect = GetViewRect(current_view_id);
// The rect above the `current_view_rect`
SkRect partial_joined_rect = SkRect::MakeEmpty();
// Each rect corresponds to a native view that renders Flutter UI.
std::vector<SkIRect> intersection_rects =
slice->region(current_view_rect).getRects();
// Limit the number of native views, so it doesn't grow forever.
//
// In this case, the rects are merged into a single one that is the union
// of all the rects.
for (const SkIRect& rect : intersection_rects) {
partial_joined_rect.join(SkRect::Make(rect));
}
// Get the intersection rect with the `current_view_rect`,
partial_joined_rect.intersect(current_view_rect);
// Join the `partial_joined_rect` into `full_joined_rect` to get the rect
// above the current `slice`
full_joined_rect.join(partial_joined_rect);
}
if (!full_joined_rect.isEmpty()) {
// Subpixels in the platform may not align with the canvas subpixels.
//
// To workaround it, round the floating point bounds and make the rect
// slightly larger.
//
// For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.
full_joined_rect.set(full_joined_rect.roundOut());
overlay_layers.insert({view_id, full_joined_rect});
// Clip the background canvas, so it doesn't contain any of the pixels
// drawn on the overlay layer.
background_canvas->ClipRect(full_joined_rect,
DlCanvas::ClipOp::kDifference);
}
slice->render_into(background_canvas);
std::unordered_map<int64_t, SkRect> view_rects;
for (auto platform_id : composition_order_) {
view_rects[platform_id] = GetViewRect(platform_id);
}
// Manually trigger the DlAutoCanvasRestore before we submit the frame
save.Restore();
std::unordered_map<int64_t, SkRect> overlay_layers =
SliceViews(frame->Canvas(), //
composition_order_, //
slices_, //
view_rects //
);
// Submit the background canvas frame before switching the GL context to
// the overlay surfaces.

View File

@@ -6,6 +6,7 @@
#include <Metal/Metal.h>
#include "flutter/flow/view_slicer.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
@@ -681,101 +682,36 @@ bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
DlCanvas* background_canvas = frame->Canvas();
// Resolve all pending GPU operations before allocating a new surface.
background_canvas->Flush();
// Clipping the background canvas before drawing the picture recorders requires
// saving and restoring the clip context.
DlAutoCanvasRestore save(background_canvas, /*do_save=*/true);
// Maps a platform view id to a vector of `FlutterPlatformViewLayer`.
LayersMap platform_view_layers;
auto did_submit = true;
auto num_platform_views = composition_order_.size();
// TODO(hellohuanlin) this double for-loop is expensive with wasted computations.
// See: https://github.com/flutter/flutter/issues/145802
for (size_t i = 0; i < num_platform_views; i++) {
int64_t platform_view_id = composition_order_[i];
EmbedderViewSlice* slice = slices_[platform_view_id].get();
slice->end_recording();
// Check if the current picture contains overlays that intersect with the
// current platform view or any of the previous platform views.
for (size_t j = i + 1; j > 0; j--) {
int64_t current_platform_view_id = composition_order_[j - 1];
SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id);
std::vector<SkIRect> intersection_rects = slice->region(platform_view_rect).getRects();
const SkIRect rounded_in_platform_view_rect = platform_view_rect.roundIn();
// Ignore intersections of single width/height on the edge of the platform view.
// This is to address the following performance issue when interleaving adjacent
// platform views and layers:
// Since we `roundOut` both platform view rects and the layer rects, as long as
// the coordinate is fractional, there will be an intersection of a single pixel width
// (or height) after rounding out, even if they do not intersect before rounding out.
// We have to round out both platform view rect and the layer rect.
// Rounding in platform view rect will result in missing pixel on the intersection edge.
// Rounding in layer rect will result in missing pixel on the edge of the layer on top
// of the platform view.
for (auto it = intersection_rects.begin(); it != intersection_rects.end(); /*no-op*/) {
// If intersection_rect does not intersect with the *rounded in* platform
// view rect, then the intersection must be a single pixel width (or height) on edge.
if (!SkIRect::Intersects(*it, rounded_in_platform_view_rect)) {
it = intersection_rects.erase(it);
} else {
++it;
}
}
auto allocation_size = intersection_rects.size();
// For testing purposes, the overlay id is used to find the overlay view.
// This is the index of the layer for the current platform view.
auto overlay_id = platform_view_layers[current_platform_view_id].size();
// If the max number of allocations per platform view is exceeded,
// then join all the rects into a single one.
//
// TODO(egarciad): Consider making this configurable.
// https://github.com/flutter/flutter/issues/52510
if (allocation_size > kMaxLayerAllocations) {
SkIRect joined_rect = SkIRect::MakeEmpty();
for (const SkIRect& rect : intersection_rects) {
joined_rect.join(rect);
}
// Replace the rects in the intersection rects list for a single rect that is
// the union of all the rects in the list.
intersection_rects.clear();
intersection_rects.push_back(joined_rect);
}
for (SkIRect& joined_rect : intersection_rects) {
// Get the intersection rect between the current rect
// and the platform view rect.
joined_rect.intersect(platform_view_rect.roundOut());
// Clip the background canvas, so it doesn't contain any of the pixels drawn
// on the overlay layer.
background_canvas->ClipRect(SkRect::Make(joined_rect), DlCanvas::ClipOp::kDifference);
// Get a new host layer.
std::shared_ptr<FlutterPlatformViewLayer> layer =
GetLayer(gr_context, //
ios_context, //
slice, //
joined_rect, //
current_platform_view_id, //
overlay_id, //
((FlutterView*)flutter_view_.get()).pixelFormat //
);
did_submit &= layer->did_submit_last_frame;
platform_view_layers[current_platform_view_id].push_back(layer);
overlay_id++;
}
}
slice->render_into(background_canvas);
std::unordered_map<int64_t, SkRect> view_rects;
for (auto view_id : composition_order_) {
view_rects[view_id] = GetPlatformViewRect(view_id);
}
// Manually trigger the SkAutoCanvasRestore before we submit the frame
save.Restore();
std::unordered_map<int64_t, SkRect> overlay_layers =
SliceViews(background_canvas, composition_order_, slices_, view_rects);
LayersMap platform_view_layers;
auto did_submit = true;
int overlay_id = 0;
for (int64_t view_id : composition_order_) {
std::unordered_map<int64_t, SkRect>::const_iterator overlay = overlay_layers.find(view_id);
if (overlay == overlay_layers.end()) {
continue;
}
std::shared_ptr<FlutterPlatformViewLayer> layer =
GetLayer(gr_context, //
ios_context, //
slices_[view_id].get(), //
overlay->second, //
view_id, //
overlay_id, //
((FlutterView*)flutter_view_.get()).pixelFormat //
);
did_submit &= layer->did_submit_last_frame;
platform_view_layers[view_id].push_back(layer);
overlay_id++;
}
// If a layer was allocated in the previous frame, but it's not used in the current frame,
// then it can be removed from the scene.
@@ -833,7 +769,7 @@ std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLay
GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
EmbedderViewSlice* slice,
SkIRect rect,
SkRect rect,
int64_t view_id,
int64_t overlay_id,
MTLPixelFormat pixel_format) {
@@ -868,7 +804,7 @@ std::shared_ptr<FlutterPlatformViewLayer> FlutterPlatformViewsController::GetLay
DlCanvas* overlay_canvas = frame->Canvas();
int restore_count = overlay_canvas->GetSaveCount();
overlay_canvas->Save();
overlay_canvas->ClipRect(SkRect::Make(rect));
overlay_canvas->ClipRect(rect);
overlay_canvas->Clear(DlColor::kTransparent());
slice->render_into(overlay_canvas);
overlay_canvas->RestoreToCount(restore_count);

View File

@@ -288,8 +288,6 @@ class FlutterPlatformViewsController {
void PushVisitedPlatformView(int64_t view_id) { visited_platform_views_.push_back(view_id); }
private:
static const size_t kMaxLayerAllocations = 2;
using LayersMap = std::map<int64_t, std::vector<std::shared_ptr<FlutterPlatformViewLayer>>>;
void OnCreate(FlutterMethodCall* call, FlutterResult result) __attribute__((cf_audited_transfer));
@@ -334,7 +332,7 @@ class FlutterPlatformViewsController {
std::shared_ptr<FlutterPlatformViewLayer> GetLayer(GrDirectContext* gr_context,
const std::shared_ptr<IOSContext>& ios_context,
EmbedderViewSlice* slice,
SkIRect rect,
SkRect rect,
int64_t view_id,
int64_t overlay_id,
MTLPixelFormat pixel_format);
@@ -362,7 +360,7 @@ class FlutterPlatformViewsController {
// operation until the next platform view or the end of the last leaf node in the layer tree.
//
// The Slices are deleted by the FlutterPlatformViewsController.reset().
std::map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices_;
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices_;
fml::scoped_nsobject<FlutterMethodChannel> channel_;
fml::scoped_nsobject<UIView> flutter_view_;

View File

@@ -234,21 +234,14 @@ static const CGFloat kCompareAccuracy = 0.001;
XCTAssertEqual(platform_view2.frame.size.width, 250);
XCTAssertEqual(platform_view2.frame.size.height, 250);
XCUIElement* overlay1 = app.otherElements[@"platform_view[0].overlay[0]"];
XCUIElement* overlay1 = app.otherElements[@"platform_view[1].overlay[0]"];
XCTAssertTrue(overlay1.exists);
XCTAssertEqual(overlay1.frame.origin.x, 25);
XCTAssertEqual(overlay1.frame.origin.y, 300);
XCTAssertEqual(overlay1.frame.origin.y, 0);
XCTAssertEqual(overlay1.frame.size.width, 225);
XCTAssertEqual(overlay1.frame.size.height, 200);
XCTAssertEqual(overlay1.frame.size.height, 500);
XCUIElement* overlay2 = app.otherElements[@"platform_view[1].overlay[0]"];
XCTAssertTrue(overlay2.exists);
XCTAssertEqual(overlay2.frame.origin.x, 25);
XCTAssertEqual(overlay2.frame.origin.y, 0);
XCTAssertEqual(overlay2.frame.size.width, 225);
XCTAssertEqual(overlay2.frame.size.height, 250);
XCUIElement* overlayView0 = app.otherElements[@"platform_view[0].overlay_view[0]"];
XCUIElement* overlayView0 = app.otherElements[@"platform_view[1].overlay_view[0]"];
XCTAssertTrue(overlayView0.exists);
// Overlay should always be the same frame as the app.
XCTAssertEqualWithAccuracy(overlayView0.frame.origin.x, app.frame.origin.x, kCompareAccuracy);
@@ -256,15 +249,6 @@ static const CGFloat kCompareAccuracy = 0.001;
XCTAssertEqualWithAccuracy(overlayView0.frame.size.width, app.frame.size.width, kCompareAccuracy);
XCTAssertEqualWithAccuracy(overlayView0.frame.size.height, app.frame.size.height,
kCompareAccuracy);
XCUIElement* overlayView1 = app.otherElements[@"platform_view[1].overlay_view[0]"];
XCTAssertTrue(overlayView1.exists);
// Overlay should always be the same frame as the app.
XCTAssertEqualWithAccuracy(overlayView1.frame.origin.x, app.frame.origin.x, kCompareAccuracy);
XCTAssertEqualWithAccuracy(overlayView1.frame.origin.y, app.frame.origin.x, kCompareAccuracy);
XCTAssertEqualWithAccuracy(overlayView1.frame.size.width, app.frame.size.width, kCompareAccuracy);
XCTAssertEqualWithAccuracy(overlayView1.frame.size.height, app.frame.size.height,
kCompareAccuracy);
}
// More then two overlays are merged into a single layer.