OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium 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 <UIKit/UIKit.h> |
| 6 |
| 7 #include "base/callback_helpers.h" |
| 8 #import "base/mac/bind_objc_block.h" |
| 9 #include "base/mac/foundation_util.h" |
| 10 #include "base/strings/sys_string_conversions.h" |
| 11 #import "base/test/ios/wait_util.h" |
| 12 #import "ios/chrome/browser/snapshots/snapshot_manager.h" |
| 13 #import "ios/chrome/browser/tabs/tab.h" |
| 14 #import "ios/chrome/browser/tabs/tab_model.h" |
| 15 #include "ios/chrome/browser/test/perf_test_with_bvc_ios.h" |
| 16 #import "ios/chrome/browser/ui/browser_view_controller.h" |
| 17 #import "ios/chrome/browser/ui/fullscreen_controller.h" |
| 18 #import "ios/chrome/browser/ui/stack_view/stack_view_controller.h" |
| 19 #include "ios/chrome/browser/ui/ui_util.h" |
| 20 #include "ios/web/public/referrer.h" |
| 21 #import "net/base/mac/url_conversions.h" |
| 22 |
| 23 // These tests measure the performance of opening the stack view controller on |
| 24 // an iPhone. On an iPad, the tests do not run, as the iPad does not use the |
| 25 // stack view controller. |
| 26 |
| 27 // Opening the SVC smoothly and quickly is critical to the user experience, and |
| 28 // any sort of pause or delay is awkward and gives a poor impression. |
| 29 // The target time of opening SVC is less than 250 ms. In order to target |
| 30 // optimizations and ensure that improvements actually lower the time, this test |
| 31 // aims to mimic as closely as possible the application experience. |
| 32 |
| 33 // To mimic the full experience, it is necessary to set up a browser view |
| 34 // controller, with a website loaded, and use as much of the generic experience |
| 35 // as possible while culling items such as PrerenderController which may |
| 36 // muddy the performance analysis. In that vein, the tests wait for websites to |
| 37 // load before opening the stack view controller, to avoid slowing down the |
| 38 // animation run loops with networking or javascript calls. This way the tests |
| 39 // provide meaningful and trackable performance numbers. The downside is that it |
| 40 // means the tests do not completely mimic the user experience, as the animation |
| 41 // may in fact take longer if the website is still loading, and the UIWebView |
| 42 // making network calls on the main thread. |
| 43 |
| 44 // Testing delegate to receive animation start and end notifications. |
| 45 // It contains a weak pointer to the BrowserViewController so that it can |
| 46 // take a snapshot of the toolbar for the toolbar animation. |
| 47 @interface StackViewControllerPerfTestDelegate |
| 48 : NSObject<TabSwitcherDelegate, StackViewControllerTestDelegate> { |
| 49 @private |
| 50 BOOL showAnimationStarted_; |
| 51 BOOL showAnimationEnded_; |
| 52 BOOL dismissAnimationStarted_; |
| 53 BOOL dismissAnimationEnded_; |
| 54 BOOL preloadCardViewsEnded_; |
| 55 @public |
| 56 BrowserViewController* bvc_; // weak |
| 57 } |
| 58 |
| 59 - (void)reinitialize; |
| 60 |
| 61 @property(nonatomic, assign) BOOL showAnimationStarted; |
| 62 @property(nonatomic, assign) BOOL showAnimationEnded; |
| 63 @property(nonatomic, assign) BOOL dismissAnimationStarted; |
| 64 @property(nonatomic, assign) BOOL dismissAnimationEnded; |
| 65 @property(nonatomic, assign) BOOL preloadCardViewsEnded; |
| 66 |
| 67 @end |
| 68 |
| 69 @implementation StackViewControllerPerfTestDelegate |
| 70 |
| 71 @synthesize showAnimationStarted = showAnimationStarted_; |
| 72 @synthesize showAnimationEnded = showAnimationEnded_; |
| 73 @synthesize dismissAnimationStarted = dismissAnimationStarted_; |
| 74 @synthesize dismissAnimationEnded = dismissAnimationEnded_; |
| 75 @synthesize preloadCardViewsEnded = preloadCardViewsEnded_; |
| 76 |
| 77 - (id)initWithBrowserViewController:(BrowserViewController*)bvc { |
| 78 self = [super init]; |
| 79 if (self) |
| 80 bvc_ = bvc; |
| 81 return self; |
| 82 } |
| 83 |
| 84 - (void)reinitialize { |
| 85 [self setShowAnimationStarted:NO]; |
| 86 [self setShowAnimationEnded:NO]; |
| 87 [self setDismissAnimationStarted:NO]; |
| 88 [self setDismissAnimationEnded:NO]; |
| 89 [self setPreloadCardViewsEnded:NO]; |
| 90 } |
| 91 |
| 92 - (void)stackViewControllerShowWithSelectedTabAnimationDidStart { |
| 93 [self setShowAnimationStarted:YES]; |
| 94 } |
| 95 |
| 96 - (void)stackViewControllerShowWithSelectedTabAnimationDidEnd { |
| 97 [self setShowAnimationEnded:YES]; |
| 98 } |
| 99 |
| 100 - (void)tabSwitcher:(id<TabSwitcher>)tabSwitcher |
| 101 dismissTransitionWillStartWithActiveModel:(TabModel*)tabModel { |
| 102 [self setDismissAnimationStarted:YES]; |
| 103 } |
| 104 |
| 105 - (void)tabSwitcherDismissTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
| 106 [self setDismissAnimationEnded:YES]; |
| 107 } |
| 108 |
| 109 - (void)stackViewControllerPreloadCardViewsDidEnd { |
| 110 [self setPreloadCardViewsEnded:YES]; |
| 111 } |
| 112 |
| 113 - (void)tabSwitcherPresentationTransitionDidEnd:(id<TabSwitcher>)tabSwitcher { |
| 114 [self stackViewControllerShowWithSelectedTabAnimationDidEnd]; |
| 115 } |
| 116 |
| 117 - (id<ToolbarOwner>)tabSwitcherTransitionToolbarOwner { |
| 118 return bvc_; |
| 119 } |
| 120 |
| 121 @end |
| 122 |
| 123 #pragma mark - |
| 124 |
| 125 namespace { |
| 126 |
| 127 #define ARRAYSIZE(a) \ |
| 128 ((sizeof(a) / sizeof(*(a))) / \ |
| 129 static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) |
| 130 |
| 131 // Use multiple URLs in the test in case the complexity of a website has an |
| 132 // effect on the performance of UIWebView -renderInContext. |
| 133 static const char* url_list[] = { |
| 134 // TODO(crbug.com/546315): Create static websites for these. |
| 135 "https://www.google.com", "https://news.google.com", |
| 136 "https://browsingtest.appspot.com", |
| 137 }; |
| 138 |
| 139 // The maximum delay of a single spin of the run loop in seconds. |
| 140 // This is very small to ensure that we are spining fast enough. |
| 141 const NSTimeInterval kSpinDelay = 0.01; // seconds |
| 142 // The total maximum delay of all spins of the run loop while |
| 143 // waiting for the stack view to appear and disappear. |
| 144 const NSTimeInterval kTotalSpinDelay = 20; // seconds |
| 145 // The maximum time to wait for a website to load. |
| 146 const NSTimeInterval kMaxPageLoadDelay = 20; // seconds |
| 147 // The time UI gets to catch up after a website has loaded. Give a full second |
| 148 // to make sure the progress bar's finish delay has completed and the toolbar |
| 149 // snapshot taken. |
| 150 const NSTimeInterval kMaxUICatchupDelay = 1.0; // seconds |
| 151 |
| 152 class StackViewControllerPerfTest : public PerfTestWithBVC { |
| 153 public: |
| 154 StackViewControllerPerfTest() : PerfTestWithBVC("Stack View") {} |
| 155 |
| 156 void SetUp() override { |
| 157 // Opening a StackViewController is done only on iPhones, not on iPads. |
| 158 // This test is meaningless on an iPad. |
| 159 if (IsIPadIdiom()) |
| 160 return; |
| 161 |
| 162 [FullScreenController setHideOmniboxDelaySeconds:0.0]; |
| 163 |
| 164 // Base class does most of the setup. |
| 165 PerfTestWithBVC::SetUp(); |
| 166 |
| 167 current_url_index_ = 0; |
| 168 reuse_svc_ = false; |
| 169 |
| 170 // The testing delegate will receive stack view animation notifications. |
| 171 delegate_.reset([[StackViewControllerPerfTestDelegate alloc] |
| 172 initWithBrowserViewController:bvc_]); |
| 173 } |
| 174 void TearDown() override { |
| 175 // Opening a StackViewController is done only on iPhones, not on iPads. |
| 176 // This test is meaningless on an iPad. |
| 177 if (IsIPadIdiom()) |
| 178 return; |
| 179 |
| 180 PerfTestWithBVC::TearDown(); |
| 181 } |
| 182 |
| 183 protected: |
| 184 // Stack view controller & delegate. |
| 185 base::scoped_nsobject<StackViewControllerPerfTestDelegate> delegate_; |
| 186 base::scoped_nsobject<StackViewController> view_controller_; |
| 187 |
| 188 int current_url_index_; |
| 189 BOOL reuse_svc_; |
| 190 |
| 191 // Utility function to print out timing information for testing |
| 192 // with |number_of_tabs| tabs opened. |
| 193 void PrintTestResult(std::string test_description, |
| 194 int number_of_tabs, |
| 195 base::TimeDelta elapsed) { |
| 196 std::stringstream test_name; |
| 197 test_name << test_description << " - " << number_of_tabs << " Tab" |
| 198 << (number_of_tabs == 1 ? "" : "s"); |
| 199 LogPerfTiming(test_name.str(), elapsed); |
| 200 } |
| 201 |
| 202 // Creates and adds |number_of_tabs| tabs to the tab model. If |same_url| |
| 203 // is true, always uses "www.google.com", otherwise iterates through url_list. |
| 204 void CreateTabs(int number_of_tabs, bool same_url); |
| 205 // Navigates to the next URL in the current tab. |
| 206 void LoadNextURL(); |
| 207 |
| 208 // Gets the next URL in the list. |
| 209 const GURL NextURL(); |
| 210 // Gets "google.com" |
| 211 static const GURL GoogleURL(); |
| 212 // Waits for the page to load in the given tab. |
| 213 static void WaitForPageLoad(Tab* tab); |
| 214 // Creates the stack view and adds it to the main window. |
| 215 base::TimeDelta OpenStackView(); |
| 216 // Copy of MainController's -showTabSwitcher function. Pulling in all of |
| 217 // MainController is not practical for unit tests, nor necessary. |
| 218 void MainControllerShowTabSwitcher(); |
| 219 // Dismisses the stack view and removes it from the main window. |
| 220 base::TimeDelta CloseStackView(); |
| 221 |
| 222 // Time how long it takes BVC to make a snapshot of the current website. |
| 223 base::TimeDelta TakeSnapshot(); |
| 224 }; |
| 225 |
| 226 void StackViewControllerPerfTest::CreateTabs(int number_of_tabs, |
| 227 bool same_url) { |
| 228 // Create and add the tabs to the tab model. |
| 229 Tab* tab = nil; |
| 230 for (int i = 0; i < number_of_tabs; i++) { |
| 231 tab = [bvc_ addSelectedTabWithURL:(same_url ? GoogleURL() : NextURL()) |
| 232 transition:(ui::PAGE_TRANSITION_AUTO_TOPLEVEL)]; |
| 233 WaitForPageLoad(tab); |
| 234 } |
| 235 } |
| 236 |
| 237 void StackViewControllerPerfTest::LoadNextURL() { |
| 238 [bvc_ loadURL:NextURL() |
| 239 referrer:web::Referrer() |
| 240 transition:(ui::PAGE_TRANSITION_TYPED) |
| 241 rendererInitiated:NO]; |
| 242 Tab* tab = [tab_model_ currentTab]; |
| 243 WaitForPageLoad(tab); |
| 244 } |
| 245 |
| 246 const GURL StackViewControllerPerfTest::NextURL() { |
| 247 current_url_index_++; |
| 248 if (static_cast<unsigned long>(current_url_index_) >= ARRAYSIZE(url_list)) |
| 249 current_url_index_ = 0; |
| 250 return GURL(url_list[current_url_index_]); |
| 251 } |
| 252 |
| 253 const GURL StackViewControllerPerfTest::GoogleURL() { |
| 254 return GURL("http://www.google.com"); |
| 255 } |
| 256 |
| 257 void StackViewControllerPerfTest::WaitForPageLoad(Tab* tab) { |
| 258 base::test::ios::WaitUntilCondition( |
| 259 ^bool() { |
| 260 return !tab.webState->IsLoading(); |
| 261 }, |
| 262 nullptr, base::TimeDelta::FromSecondsD(kMaxPageLoadDelay)); |
| 263 base::test::ios::WaitUntilCondition( |
| 264 nil, nullptr, base::TimeDelta::FromSecondsD(kMaxUICatchupDelay)); |
| 265 } |
| 266 |
| 267 base::TimeDelta StackViewControllerPerfTest::OpenStackView() { |
| 268 return base::test::ios::TimeUntilCondition( |
| 269 ^{ |
| 270 [delegate_ reinitialize]; |
| 271 MainControllerShowTabSwitcher(); |
| 272 }, |
| 273 ^bool() { |
| 274 return [delegate_ showAnimationEnded]; |
| 275 }, |
| 276 nullptr, base::TimeDelta::FromSecondsD(kTotalSpinDelay)); |
| 277 } |
| 278 |
| 279 void StackViewControllerPerfTest::MainControllerShowTabSwitcher() { |
| 280 // The code for this function is copied from MainController -showTabSwitcher. |
| 281 // Note that if the code there changes, this code should change to match. |
| 282 Tab* currentTab = [[bvc_ tabModel] currentTab]; |
| 283 |
| 284 // In order to generate the transition between the current browser view |
| 285 // controller and the tab switcher controller it's possible that multiple |
| 286 // screenshots of the same tab are taken. Since taking a screenshot is |
| 287 // expensive we activate snapshot coalescing in the scope of this function |
| 288 // which will cache the first snapshot for the tab and reuse it instead of |
| 289 // regenerating a new one each time. |
| 290 [currentTab setSnapshotCoalescingEnabled:YES]; |
| 291 base::ScopedClosureRunner runner(base::BindBlock(^{ |
| 292 [currentTab setSnapshotCoalescingEnabled:NO]; |
| 293 })); |
| 294 |
| 295 if (!view_controller_) { |
| 296 view_controller_.reset([[StackViewController alloc] |
| 297 initWithMainTabModel:tab_model_ |
| 298 otrTabModel:otr_tab_model_ |
| 299 activeTabModel:tab_model_]); |
| 300 } else { |
| 301 [view_controller_ restoreInternalStateWithMainTabModel:tab_model_ |
| 302 otrTabModel:otr_tab_model_ |
| 303 activeTabModel:tab_model_]; |
| 304 } |
| 305 [view_controller_ setDelegate:delegate_]; |
| 306 |
| 307 // The only addition to the function for testing. |
| 308 [view_controller_ setTestDelegate:delegate_]; |
| 309 |
| 310 [bvc_ presentViewController:view_controller_.get() |
| 311 animated:NO |
| 312 completion:^{ |
| 313 [view_controller_ showWithSelectedTabAnimation]; |
| 314 EXPECT_TRUE([delegate_ showAnimationStarted]); |
| 315 EXPECT_FALSE([delegate_ showAnimationEnded]); |
| 316 }]; |
| 317 } |
| 318 |
| 319 base::TimeDelta StackViewControllerPerfTest::CloseStackView() { |
| 320 base::Time startTime = base::Time::NowFromSystemTime(); |
| 321 // Spin and wait for the dismiss stack view animation to finish. |
| 322 base::test::ios::TimeUntilCondition( |
| 323 ^{ |
| 324 [view_controller_ dismissWithSelectedTabAnimation]; |
| 325 EXPECT_TRUE([delegate_ dismissAnimationStarted]); |
| 326 EXPECT_FALSE([delegate_ dismissAnimationEnded]); |
| 327 }, |
| 328 ^bool() { |
| 329 return [delegate_ dismissAnimationEnded]; |
| 330 }, |
| 331 nullptr, base::TimeDelta::FromSecondsD(kTotalSpinDelay)); |
| 332 |
| 333 [view_controller_ dismissViewControllerAnimated:NO completion:nil]; |
| 334 if (!reuse_svc_) |
| 335 view_controller_.reset(); |
| 336 |
| 337 base::TimeDelta closeTime = base::Time::NowFromSystemTime() - startTime; |
| 338 |
| 339 // Run the runloop a bit longer to give time for temporary retains that happen |
| 340 // in the OS during view teardown to resolve, so that the view gets its |
| 341 // dismissal callbacks. |
| 342 base::test::ios::WaitUntilCondition( |
| 343 nil, nullptr, base::TimeDelta::FromSecondsD(kSpinDelay)); |
| 344 |
| 345 return closeTime; |
| 346 } |
| 347 |
| 348 base::TimeDelta StackViewControllerPerfTest::TakeSnapshot() { |
| 349 base::Time startTime = base::Time::NowFromSystemTime(); |
| 350 UIImage* image = [[tab_model_ currentTab] updateSnapshotWithOverlay:YES |
| 351 visibleFrameOnly:YES]; |
| 352 base::TimeDelta elapsed = base::Time::NowFromSystemTime() - startTime; |
| 353 EXPECT_TRUE(image); |
| 354 return elapsed; |
| 355 } |
| 356 |
| 357 TEST_F(StackViewControllerPerfTest, WebView_Shapshot) { |
| 358 // Opening a StackViewController is done only on iPhones, not on iPads. |
| 359 // This test is meaningless on an iPad. |
| 360 if (IsIPadIdiom()) |
| 361 return; |
| 362 const int kNumTests = 10; |
| 363 base::TimeDelta times[kNumTests]; |
| 364 CreateTabs(1, false); |
| 365 for (int i = 0; i < kNumTests; i++) { |
| 366 times[i] = TakeSnapshot(); |
| 367 LoadNextURL(); |
| 368 } |
| 369 |
| 370 base::TimeDelta avg = CalculateAverage(times, kNumTests, NULL, NULL); |
| 371 LogPerfTiming("Snapshot", avg); |
| 372 } |
| 373 |
| 374 // TODO(crbug.com/546328): Add back in tests for checking opening & closing |
| 375 // stack view controller with multiple tabs open. |
| 376 TEST_F(StackViewControllerPerfTest, DISABLED_OpenAndCloseStackView_1_Tab) { |
| 377 // Opening a StackViewController is done only on iPhones, not on iPads. |
| 378 // This test is meaningless on an iPad. |
| 379 if (IsIPadIdiom()) |
| 380 return; |
| 381 reuse_svc_ = true; |
| 382 const int kNumTests = 10; |
| 383 base::TimeDelta open_times[kNumTests]; |
| 384 base::TimeDelta close_times[kNumTests]; |
| 385 CreateTabs(1, false); |
| 386 for (int i = 0; i < kNumTests; i++) { |
| 387 open_times[i] = OpenStackView(); |
| 388 close_times[i] = CloseStackView(); |
| 389 LoadNextURL(); |
| 390 } |
| 391 |
| 392 base::TimeDelta max_open; |
| 393 base::TimeDelta max_close; |
| 394 // When calculating the average, only take into account the 'warm' tests. |
| 395 // i.e. ignore the 'cold' time. |
| 396 base::TimeDelta open_avg = |
| 397 CalculateAverage(open_times + 1, kNumTests - 1, NULL, &max_open); |
| 398 base::TimeDelta close_avg = |
| 399 CalculateAverage(close_times + 1, kNumTests - 1, NULL, &max_close); |
| 400 LogPerfTiming("Open cold", open_times[0]); |
| 401 LogPerfTiming("Open warm avg", open_avg); |
| 402 LogPerfTiming("Open warm max", max_open); |
| 403 LogPerfTiming("Close cold", close_times[0]); |
| 404 LogPerfTiming("Close cold avg", close_avg); |
| 405 LogPerfTiming("Close cold max", max_close); |
| 406 } |
| 407 |
| 408 } // anonymous namespace |
OLD | NEW |