Index: ios/chrome/browser/ui/stack_view/stack_view_controller_perftest.mm |
diff --git a/ios/chrome/browser/ui/stack_view/stack_view_controller_perftest.mm b/ios/chrome/browser/ui/stack_view/stack_view_controller_perftest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fded70ef1de00b2bc70af497e3b3fa78262ad07c |
--- /dev/null |
+++ b/ios/chrome/browser/ui/stack_view/stack_view_controller_perftest.mm |
@@ -0,0 +1,408 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import <UIKit/UIKit.h> |
+ |
+#include "base/callback_helpers.h" |
+#import "base/mac/bind_objc_block.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#import "base/test/ios/wait_util.h" |
+#import "ios/chrome/browser/snapshots/snapshot_manager.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#include "ios/chrome/browser/test/perf_test_with_bvc_ios.h" |
+#import "ios/chrome/browser/ui/browser_view_controller.h" |
+#import "ios/chrome/browser/ui/fullscreen_controller.h" |
+#import "ios/chrome/browser/ui/stack_view/stack_view_controller.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#include "ios/web/public/referrer.h" |
+#import "net/base/mac/url_conversions.h" |
+ |
+// These tests measure the performance of opening the stack view controller on |
+// an iPhone. On an iPad, the tests do not run, as the iPad does not use the |
+// stack view controller. |
+ |
+// Opening the SVC smoothly and quickly is critical to the user experience, and |
+// any sort of pause or delay is awkward and gives a poor impression. |
+// The target time of opening SVC is less than 250 ms. In order to target |
+// optimizations and ensure that improvements actually lower the time, this test |
+// aims to mimic as closely as possible the application experience. |
+ |
+// To mimic the full experience, it is necessary to set up a browser view |
+// controller, with a website loaded, and use as much of the generic experience |
+// as possible while culling items such as PrerenderController which may |
+// muddy the performance analysis. In that vein, the tests wait for websites to |
+// load before opening the stack view controller, to avoid slowing down the |
+// animation run loops with networking or javascript calls. This way the tests |
+// provide meaningful and trackable performance numbers. The downside is that it |
+// means the tests do not completely mimic the user experience, as the animation |
+// may in fact take longer if the website is still loading, and the UIWebView |
+// making network calls on the main thread. |
+ |
+// Testing delegate to receive animation start and end notifications. |
+// It contains a weak pointer to the BrowserViewController so that it can |
+// take a snapshot of the toolbar for the toolbar animation. |
+@interface StackViewControllerPerfTestDelegate |
+ : NSObject<TabSwitcherDelegate, StackViewControllerTestDelegate> { |
+ @private |
+ BOOL showAnimationStarted_; |
+ BOOL showAnimationEnded_; |
+ BOOL dismissAnimationStarted_; |
+ BOOL dismissAnimationEnded_; |
+ BOOL preloadCardViewsEnded_; |
+ @public |
+ BrowserViewController* bvc_; // weak |
+} |
+ |
+- (void)reinitialize; |
+ |
+@property(nonatomic, assign) BOOL showAnimationStarted; |
+@property(nonatomic, assign) BOOL showAnimationEnded; |
+@property(nonatomic, assign) BOOL dismissAnimationStarted; |
+@property(nonatomic, assign) BOOL dismissAnimationEnded; |
+@property(nonatomic, assign) BOOL preloadCardViewsEnded; |
+ |
+@end |
+ |
+@implementation StackViewControllerPerfTestDelegate |
+ |
+@synthesize showAnimationStarted = showAnimationStarted_; |
+@synthesize showAnimationEnded = showAnimationEnded_; |
+@synthesize dismissAnimationStarted = dismissAnimationStarted_; |
+@synthesize dismissAnimationEnded = dismissAnimationEnded_; |
+@synthesize preloadCardViewsEnded = preloadCardViewsEnded_; |
+ |
+- (id)initWithBrowserViewController:(BrowserViewController*)bvc { |
+ self = [super init]; |
+ if (self) |
+ bvc_ = bvc; |
+ return self; |
+} |
+ |
+- (void)reinitialize { |
+ [self setShowAnimationStarted:NO]; |
+ [self setShowAnimationEnded:NO]; |
+ [self setDismissAnimationStarted:NO]; |
+ [self setDismissAnimationEnded:NO]; |
+ [self setPreloadCardViewsEnded:NO]; |
+} |
+ |
+- (void)stackViewControllerShowWithSelectedTabAnimationDidStart { |
+ [self setShowAnimationStarted:YES]; |
+} |
+ |
+- (void)stackViewControllerShowWithSelectedTabAnimationDidEnd { |
+ [self setShowAnimationEnded:YES]; |
+} |
+ |
+- (void)tabSwitcher:(id<TabSwitcher>)tabSwitcher |
+ dismissTransitionWillStartWithActiveModel:(TabModel*)tabModel { |
+ [self setDismissAnimationStarted:YES]; |
+} |
+ |
+- (void)tabSwitcherDismissTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
+ [self setDismissAnimationEnded:YES]; |
+} |
+ |
+- (void)stackViewControllerPreloadCardViewsDidEnd { |
+ [self setPreloadCardViewsEnded:YES]; |
+} |
+ |
+- (void)tabSwitcherPresentationTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
+ [self stackViewControllerShowWithSelectedTabAnimationDidEnd]; |
+} |
+ |
+- (id<ToolbarOwner>)tabSwitcherTransitionToolbarOwner { |
+ return bvc_; |
+} |
+ |
+@end |
+ |
+#pragma mark - |
+ |
+namespace { |
+ |
+#define ARRAYSIZE(a) \ |
+ ((sizeof(a) / sizeof(*(a))) / \ |
+ static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) |
+ |
+// Use multiple URLs in the test in case the complexity of a website has an |
+// effect on the performance of UIWebView -renderInContext. |
+static const char* url_list[] = { |
+ // TODO(crbug.com/546315): Create static websites for these. |
+ "https://www.google.com", "https://news.google.com", |
+ "https://browsingtest.appspot.com", |
+}; |
+ |
+// The maximum delay of a single spin of the run loop in seconds. |
+// This is very small to ensure that we are spining fast enough. |
+const NSTimeInterval kSpinDelay = 0.01; // seconds |
+// The total maximum delay of all spins of the run loop while |
+// waiting for the stack view to appear and disappear. |
+const NSTimeInterval kTotalSpinDelay = 20; // seconds |
+// The maximum time to wait for a website to load. |
+const NSTimeInterval kMaxPageLoadDelay = 20; // seconds |
+// The time UI gets to catch up after a website has loaded. Give a full second |
+// to make sure the progress bar's finish delay has completed and the toolbar |
+// snapshot taken. |
+const NSTimeInterval kMaxUICatchupDelay = 1.0; // seconds |
+ |
+class StackViewControllerPerfTest : public PerfTestWithBVC { |
+ public: |
+ StackViewControllerPerfTest() : PerfTestWithBVC("Stack View") {} |
+ |
+ void SetUp() override { |
+ // Opening a StackViewController is done only on iPhones, not on iPads. |
+ // This test is meaningless on an iPad. |
+ if (IsIPadIdiom()) |
+ return; |
+ |
+ [FullScreenController setHideOmniboxDelaySeconds:0.0]; |
+ |
+ // Base class does most of the setup. |
+ PerfTestWithBVC::SetUp(); |
+ |
+ current_url_index_ = 0; |
+ reuse_svc_ = false; |
+ |
+ // The testing delegate will receive stack view animation notifications. |
+ delegate_.reset([[StackViewControllerPerfTestDelegate alloc] |
+ initWithBrowserViewController:bvc_]); |
+ } |
+ void TearDown() override { |
+ // Opening a StackViewController is done only on iPhones, not on iPads. |
+ // This test is meaningless on an iPad. |
+ if (IsIPadIdiom()) |
+ return; |
+ |
+ PerfTestWithBVC::TearDown(); |
+ } |
+ |
+ protected: |
+ // Stack view controller & delegate. |
+ base::scoped_nsobject<StackViewControllerPerfTestDelegate> delegate_; |
+ base::scoped_nsobject<StackViewController> view_controller_; |
+ |
+ int current_url_index_; |
+ BOOL reuse_svc_; |
+ |
+ // Utility function to print out timing information for testing |
+ // with |number_of_tabs| tabs opened. |
+ void PrintTestResult(std::string test_description, |
+ int number_of_tabs, |
+ base::TimeDelta elapsed) { |
+ std::stringstream test_name; |
+ test_name << test_description << " - " << number_of_tabs << " Tab" |
+ << (number_of_tabs == 1 ? "" : "s"); |
+ LogPerfTiming(test_name.str(), elapsed); |
+ } |
+ |
+ // Creates and adds |number_of_tabs| tabs to the tab model. If |same_url| |
+ // is true, always uses "www.google.com", otherwise iterates through url_list. |
+ void CreateTabs(int number_of_tabs, bool same_url); |
+ // Navigates to the next URL in the current tab. |
+ void LoadNextURL(); |
+ |
+ // Gets the next URL in the list. |
+ const GURL NextURL(); |
+ // Gets "google.com" |
+ static const GURL GoogleURL(); |
+ // Waits for the page to load in the given tab. |
+ static void WaitForPageLoad(Tab* tab); |
+ // Creates the stack view and adds it to the main window. |
+ base::TimeDelta OpenStackView(); |
+ // Copy of MainController's -showTabSwitcher function. Pulling in all of |
+ // MainController is not practical for unit tests, nor necessary. |
+ void MainControllerShowTabSwitcher(); |
+ // Dismisses the stack view and removes it from the main window. |
+ base::TimeDelta CloseStackView(); |
+ |
+ // Time how long it takes BVC to make a snapshot of the current website. |
+ base::TimeDelta TakeSnapshot(); |
+}; |
+ |
+void StackViewControllerPerfTest::CreateTabs(int number_of_tabs, |
+ bool same_url) { |
+ // Create and add the tabs to the tab model. |
+ Tab* tab = nil; |
+ for (int i = 0; i < number_of_tabs; i++) { |
+ tab = [bvc_ addSelectedTabWithURL:(same_url ? GoogleURL() : NextURL()) |
+ transition:(ui::PAGE_TRANSITION_AUTO_TOPLEVEL)]; |
+ WaitForPageLoad(tab); |
+ } |
+} |
+ |
+void StackViewControllerPerfTest::LoadNextURL() { |
+ [bvc_ loadURL:NextURL() |
+ referrer:web::Referrer() |
+ transition:(ui::PAGE_TRANSITION_TYPED) |
+ rendererInitiated:NO]; |
+ Tab* tab = [tab_model_ currentTab]; |
+ WaitForPageLoad(tab); |
+} |
+ |
+const GURL StackViewControllerPerfTest::NextURL() { |
+ current_url_index_++; |
+ if (static_cast<unsigned long>(current_url_index_) >= ARRAYSIZE(url_list)) |
+ current_url_index_ = 0; |
+ return GURL(url_list[current_url_index_]); |
+} |
+ |
+const GURL StackViewControllerPerfTest::GoogleURL() { |
+ return GURL("http://www.google.com"); |
+} |
+ |
+void StackViewControllerPerfTest::WaitForPageLoad(Tab* tab) { |
+ base::test::ios::WaitUntilCondition( |
+ ^bool() { |
+ return !tab.webState->IsLoading(); |
+ }, |
+ nullptr, base::TimeDelta::FromSecondsD(kMaxPageLoadDelay)); |
+ base::test::ios::WaitUntilCondition( |
+ nil, nullptr, base::TimeDelta::FromSecondsD(kMaxUICatchupDelay)); |
+} |
+ |
+base::TimeDelta StackViewControllerPerfTest::OpenStackView() { |
+ return base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [delegate_ reinitialize]; |
+ MainControllerShowTabSwitcher(); |
+ }, |
+ ^bool() { |
+ return [delegate_ showAnimationEnded]; |
+ }, |
+ nullptr, base::TimeDelta::FromSecondsD(kTotalSpinDelay)); |
+} |
+ |
+void StackViewControllerPerfTest::MainControllerShowTabSwitcher() { |
+ // The code for this function is copied from MainController -showTabSwitcher. |
+ // Note that if the code there changes, this code should change to match. |
+ Tab* currentTab = [[bvc_ tabModel] currentTab]; |
+ |
+ // In order to generate the transition between the current browser view |
+ // controller and the tab switcher controller it's possible that multiple |
+ // screenshots of the same tab are taken. Since taking a screenshot is |
+ // expensive we activate snapshot coalescing in the scope of this function |
+ // which will cache the first snapshot for the tab and reuse it instead of |
+ // regenerating a new one each time. |
+ [currentTab setSnapshotCoalescingEnabled:YES]; |
+ base::ScopedClosureRunner runner(base::BindBlock(^{ |
+ [currentTab setSnapshotCoalescingEnabled:NO]; |
+ })); |
+ |
+ if (!view_controller_) { |
+ view_controller_.reset([[StackViewController alloc] |
+ initWithMainTabModel:tab_model_ |
+ otrTabModel:otr_tab_model_ |
+ activeTabModel:tab_model_]); |
+ } else { |
+ [view_controller_ restoreInternalStateWithMainTabModel:tab_model_ |
+ otrTabModel:otr_tab_model_ |
+ activeTabModel:tab_model_]; |
+ } |
+ [view_controller_ setDelegate:delegate_]; |
+ |
+ // The only addition to the function for testing. |
+ [view_controller_ setTestDelegate:delegate_]; |
+ |
+ [bvc_ presentViewController:view_controller_.get() |
+ animated:NO |
+ completion:^{ |
+ [view_controller_ showWithSelectedTabAnimation]; |
+ EXPECT_TRUE([delegate_ showAnimationStarted]); |
+ EXPECT_FALSE([delegate_ showAnimationEnded]); |
+ }]; |
+} |
+ |
+base::TimeDelta StackViewControllerPerfTest::CloseStackView() { |
+ base::Time startTime = base::Time::NowFromSystemTime(); |
+ // Spin and wait for the dismiss stack view animation to finish. |
+ base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [view_controller_ dismissWithSelectedTabAnimation]; |
+ EXPECT_TRUE([delegate_ dismissAnimationStarted]); |
+ EXPECT_FALSE([delegate_ dismissAnimationEnded]); |
+ }, |
+ ^bool() { |
+ return [delegate_ dismissAnimationEnded]; |
+ }, |
+ nullptr, base::TimeDelta::FromSecondsD(kTotalSpinDelay)); |
+ |
+ [view_controller_ dismissViewControllerAnimated:NO completion:nil]; |
+ if (!reuse_svc_) |
+ view_controller_.reset(); |
+ |
+ base::TimeDelta closeTime = base::Time::NowFromSystemTime() - startTime; |
+ |
+ // Run the runloop a bit longer to give time for temporary retains that happen |
+ // in the OS during view teardown to resolve, so that the view gets its |
+ // dismissal callbacks. |
+ base::test::ios::WaitUntilCondition( |
+ nil, nullptr, base::TimeDelta::FromSecondsD(kSpinDelay)); |
+ |
+ return closeTime; |
+} |
+ |
+base::TimeDelta StackViewControllerPerfTest::TakeSnapshot() { |
+ base::Time startTime = base::Time::NowFromSystemTime(); |
+ UIImage* image = [[tab_model_ currentTab] updateSnapshotWithOverlay:YES |
+ visibleFrameOnly:YES]; |
+ base::TimeDelta elapsed = base::Time::NowFromSystemTime() - startTime; |
+ EXPECT_TRUE(image); |
+ return elapsed; |
+} |
+ |
+TEST_F(StackViewControllerPerfTest, WebView_Shapshot) { |
+ // Opening a StackViewController is done only on iPhones, not on iPads. |
+ // This test is meaningless on an iPad. |
+ if (IsIPadIdiom()) |
+ return; |
+ const int kNumTests = 10; |
+ base::TimeDelta times[kNumTests]; |
+ CreateTabs(1, false); |
+ for (int i = 0; i < kNumTests; i++) { |
+ times[i] = TakeSnapshot(); |
+ LoadNextURL(); |
+ } |
+ |
+ base::TimeDelta avg = CalculateAverage(times, kNumTests, NULL, NULL); |
+ LogPerfTiming("Snapshot", avg); |
+} |
+ |
+// TODO(crbug.com/546328): Add back in tests for checking opening & closing |
+// stack view controller with multiple tabs open. |
+TEST_F(StackViewControllerPerfTest, DISABLED_OpenAndCloseStackView_1_Tab) { |
+ // Opening a StackViewController is done only on iPhones, not on iPads. |
+ // This test is meaningless on an iPad. |
+ if (IsIPadIdiom()) |
+ return; |
+ reuse_svc_ = true; |
+ const int kNumTests = 10; |
+ base::TimeDelta open_times[kNumTests]; |
+ base::TimeDelta close_times[kNumTests]; |
+ CreateTabs(1, false); |
+ for (int i = 0; i < kNumTests; i++) { |
+ open_times[i] = OpenStackView(); |
+ close_times[i] = CloseStackView(); |
+ LoadNextURL(); |
+ } |
+ |
+ base::TimeDelta max_open; |
+ base::TimeDelta max_close; |
+ // When calculating the average, only take into account the 'warm' tests. |
+ // i.e. ignore the 'cold' time. |
+ base::TimeDelta open_avg = |
+ CalculateAverage(open_times + 1, kNumTests - 1, NULL, &max_open); |
+ base::TimeDelta close_avg = |
+ CalculateAverage(close_times + 1, kNumTests - 1, NULL, &max_close); |
+ LogPerfTiming("Open cold", open_times[0]); |
+ LogPerfTiming("Open warm avg", open_avg); |
+ LogPerfTiming("Open warm max", max_open); |
+ LogPerfTiming("Close cold", close_times[0]); |
+ LogPerfTiming("Close cold avg", close_avg); |
+ LogPerfTiming("Close cold max", max_close); |
+} |
+ |
+} // anonymous namespace |