Index: ios/chrome/browser/ui/omnibox_perftest.mm |
diff --git a/ios/chrome/browser/ui/omnibox_perftest.mm b/ios/chrome/browser/ui/omnibox_perftest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e4e24f8ca034b8e8f31214a7f94d7072ecca5165 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/omnibox_perftest.mm |
@@ -0,0 +1,275 @@ |
+// Copyright (c) 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 <memory> |
+ |
+#include "base/mac/scoped_nsobject.h" |
+#import "base/test/ios/wait_util.h" |
+#include "base/time/time.h" |
+#include "components/toolbar/test_toolbar_model.h" |
+#include "ios/chrome/browser/autocomplete/autocomplete_classifier_factory.h" |
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" |
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h" |
+#import "ios/chrome/browser/tabs/tab.h" |
+#import "ios/chrome/browser/tabs/tab_model.h" |
+#include "ios/chrome/browser/ui/omnibox/location_bar_view_ios.h" |
+#import "ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h" |
+#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h" |
+#include "ios/chrome/browser/ui/toolbar/toolbar_model_impl_ios.h" |
+#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h" |
+#include "ios/chrome/test/base/perf_test_ios.h" |
+#include "testing/platform_test.h" |
+#import "third_party/ocmock/OCMock/OCMock.h" |
+#import "ui/base/test/ios/keyboard_appearance_listener.h" |
+ |
+namespace { |
+ |
+// Descends down a view hierarchy until the first view of |specificClass| |
+// is found. Returns nil if a view of |specificClass| cannot be found. |
+UIView* FindViewByClass(UIView* topView, Class specificClass) { |
+ if ([topView isKindOfClass:specificClass]) |
+ return [topView window] ? topView : nil; |
+ for (UIView* subview in [topView subviews]) { |
+ UIView* foundView = FindViewByClass(subview, specificClass); |
+ if (foundView) |
+ return foundView; |
+ } |
+ return nil; |
+} |
+ |
+// Constant for UI wait loop. |
+const NSTimeInterval kSpinDelaySeconds = 0.01; |
+ |
+class OmniboxPerfTest : public PerfTest { |
+ public: |
+ OmniboxPerfTest() : PerfTest("Omnibox") {} |
+ |
+ protected: |
+ void SetUp() override { |
+ PerfTest::SetUp(); |
+ TestChromeBrowserState::Builder test_cbs_builder; |
+ test_cbs_builder.AddTestingFactory( |
+ ios::TemplateURLServiceFactory::GetInstance(), |
+ ios::TemplateURLServiceFactory::GetDefaultFactory()); |
+ test_cbs_builder.AddTestingFactory( |
+ ios::AutocompleteClassifierFactory::GetInstance(), |
+ ios::AutocompleteClassifierFactory::GetDefaultFactory()); |
+ chrome_browser_state_ = test_cbs_builder.Build(); |
+ |
+ // Prepares testing profile for autocompletion. |
+ ios::AutocompleteClassifierFactory::GetForBrowserState( |
+ chrome_browser_state_.get()); |
+ |
+ // Sets up the listener for keyboard activation/deactivation notifications. |
+ keyboard_listener_.reset([[KeyboardAppearanceListener alloc] init]); |
+ |
+ // Create a real window to host the Toolbar. |
+ CGRect screenBounds = [[UIScreen mainScreen] bounds]; |
+ window_.reset([[UIWindow alloc] initWithFrame:screenBounds]); |
+ [window_ makeKeyAndVisible]; |
+ |
+ // Create a mock Tab and a TabModel that will always return the mock Tab as |
+ // the current tab. |
+ current_tab_.reset( |
+ [(Tab*)[OCMockObject niceMockForClass:[Tab class]] retain]); |
+ id tab_model = [OCMockObject mockForClass:[TabModel class]]; |
+ [[[tab_model stub] andReturn:current_tab_] currentTab]; |
+ tab_model_.reset([tab_model retain]); |
+ |
+ // Creates the Toolbar for testing and sizes it to the width of the screen. |
+ toolbar_model_delegate_.reset( |
+ new ToolbarModelDelegateIOS(tab_model_.get())); |
+ toolbar_model_ios_.reset( |
+ new ToolbarModelImplIOS(toolbar_model_delegate_.get())); |
+ |
+ // The OCMOCK_VALUE macro doesn't like std::unique_ptr, but it works just |
+ // fine if a temporary variable is used. |
+ ToolbarModelIOS* model_for_mock = toolbar_model_ios_.get(); |
+ id webToolbarDelegate = |
+ [OCMockObject niceMockForProtocol:@protocol(WebToolbarDelegate)]; |
+ [[[webToolbarDelegate stub] andReturnValue:OCMOCK_VALUE(model_for_mock)] |
+ toolbarModelIOS]; |
+ id urlLoader = [OCMockObject niceMockForProtocol:@protocol(UrlLoader)]; |
+ toolbar_.reset([[WebToolbarController alloc] |
+ initWithDelegate:webToolbarDelegate |
+ urlLoader:urlLoader |
+ browserState:chrome_browser_state_.get() |
+ preloadProvider:nil]); |
+ UIView* toolbarView = [toolbar_ view]; |
+ CGRect toolbarFrame = toolbarView.frame; |
+ toolbarFrame.origin = CGPointZero; |
+ toolbarFrame.size.width = screenBounds.size.width; |
+ toolbarView.frame = toolbarFrame; |
+ // Add toolbar to window. |
+ [window_ addSubview:toolbarView]; |
+ base::test::ios::WaitUntilCondition(^bool() { |
+ return IsToolbarLoaded(window_.get()); |
+ }); |
+ } |
+ |
+ void TearDown() override { |
+ // Remove toolbar from window. |
+ [[toolbar_ view] removeFromSuperview]; |
+ base::test::ios::WaitUntilCondition(^bool() { |
+ return !IsToolbarLoaded(window_.get()); |
+ }); |
+ [toolbar_ browserStateDestroyed]; |
+ PerfTest::TearDown(); |
+ } |
+ |
+ // Returns whether Omnibox has been loaded. |
+ bool IsToolbarLoaded(UIView* topView) { |
+ return FindViewByClass(topView, [OmniboxTextFieldIOS class]) != nil; |
+ } |
+ |
+ // Inserts the |text| string into the |textField| and return the amount |
+ // of time it took to complete the insertion. This does not time |
+ // any activities that may be triggered on other threads. |
+ base::TimeDelta TimeInsertText(OmniboxTextFieldIOS* textField, |
+ NSString* text) { |
+ base::Time startTime = base::Time::NowFromSystemTime(); |
+ [textField insertTextWhileEditing:text]; |
+ base::TimeDelta elapsed = base::Time::NowFromSystemTime() - startTime; |
+ // Adds a small delay so the run loop can update the screen. |
+ // The user experience measurement should include this visual |
+ // feedback to the user, but there is no way of catching when |
+ // the typed character showed up in the omnibox on screen. |
+ base::test::ios::SpinRunLoopWithMaxDelay( |
+ base::TimeDelta::FromSecondsD(kSpinDelaySeconds)); |
+ return elapsed; |
+ } |
+ |
+ // Creates a dummy text field and make it be a first responder so keyboard |
+ // comes up. In general, there's a lot of time spent in loading up various |
+ // graphics assets on a keyboard which may distort the measurement of Chrome |
+ // Omnibox focus timings. Call this function to preload keyboard before |
+ // doing the real test. |
+ base::TimeDelta PreLoadKeyboard() { |
+ base::scoped_nsobject<UITextField> textField( |
+ [[UITextField alloc] initWithFrame:CGRectMake(20, 100, 280, 29)]); |
+ [textField setBorderStyle:UITextBorderStyleRoundedRect]; |
+ [window_ addSubview:textField]; |
+ base::TimeDelta elapsed = base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [textField becomeFirstResponder]; |
+ }, |
+ ^bool() { |
+ return [keyboard_listener_ isKeyboardVisible]; |
+ }, |
+ nullptr, base::TimeDelta()); |
+ base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [textField resignFirstResponder]; |
+ }, |
+ ^bool() { |
+ return ![keyboard_listener_ isKeyboardVisible]; |
+ }, |
+ nullptr, base::TimeDelta()); |
+ [textField removeFromSuperview]; |
+ return elapsed; |
+ } |
+ |
+ // Enables the on-screen keyboard as if user has tapped on |textField|. |
+ // Returns the amount of time it took for the keyboard to appear. |
+ base::TimeDelta EnableKeyboard(OmniboxTextFieldIOS* textField) { |
+ return base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [textField becomeFirstResponder]; |
+ }, |
+ ^bool() { |
+ return [keyboard_listener_ isKeyboardVisible]; |
+ }, |
+ nullptr, base::TimeDelta()); |
+ } |
+ |
+ // Performs necessary cleanup (so next pass of unit test can start from |
+ // a clean slate) and then exit from |textField| to dismiss keyboard. |
+ void DisableKeyboard(OmniboxTextFieldIOS* textField) { |
+ // Busy wait until keyboard is hidden. |
+ base::test::ios::TimeUntilCondition( |
+ ^{ |
+ [textField exitPreEditState]; |
+ [textField resignFirstResponder]; |
+ }, |
+ ^bool() { |
+ return ![keyboard_listener_ isKeyboardVisible]; |
+ }, |
+ nullptr, base::TimeDelta()); |
+ } |
+ |
+ std::unique_ptr<TestChromeBrowserState> chrome_browser_state_; |
+ base::scoped_nsobject<Tab> current_tab_; |
+ base::scoped_nsobject<TabModel> tab_model_; |
+ std::unique_ptr<ToolbarModelDelegateIOS> toolbar_model_delegate_; |
+ std::unique_ptr<ToolbarModelIOS> toolbar_model_ios_; |
+ base::scoped_nsobject<WebToolbarController> toolbar_; |
+ base::scoped_nsobject<UIWindow> window_; |
+ base::scoped_nsobject<KeyboardAppearanceListener> keyboard_listener_; |
+}; |
+ |
+// Measures the amount of time it takes the Omnibox text field to activate |
+// the on-screen keyboard. |
+TEST_F(OmniboxPerfTest, TestTextFieldDidBeginEditing) { |
+ LogPerfTiming("Keyboard preload", PreLoadKeyboard()); |
+ OmniboxTextFieldIOS* textField = (OmniboxTextFieldIOS*)FindViewByClass( |
+ [toolbar_ view], [OmniboxTextFieldIOS class]); |
+ |
+ // Time how long it takes to "focus" on omnibox. |
+ RepeatTimedRuns("Begin editing", |
+ ^base::TimeDelta(int index) { |
+ return EnableKeyboard(textField); |
+ }, |
+ ^() { |
+ DisableKeyboard(textField); |
+ }); |
+} |
+ |
+// Measures the amount of time it takes to type in the first character |
+// into the Omnibox. |
+TEST_F(OmniboxPerfTest, TestTypeOneCharInTextField) { |
+ OmniboxTextFieldIOS* textField = (OmniboxTextFieldIOS*)FindViewByClass( |
+ [toolbar_ view], [OmniboxTextFieldIOS class]); |
+ RepeatTimedRuns("Type first character", |
+ ^base::TimeDelta(int index) { |
+ EnableKeyboard(textField); |
+ return TimeInsertText(textField, @"G"); |
+ }, |
+ ^() { |
+ [textField setText:@""]; |
+ DisableKeyboard(textField); |
+ }); |
+} |
+ |
+// Measures the amount of time it takes to type in the word "google" one |
+// letter at a time. |
+TEST_F(OmniboxPerfTest, TestTypingInTextField) { |
+ OmniboxTextFieldIOS* textField = (OmniboxTextFieldIOS*)FindViewByClass( |
+ [toolbar_ view], [OmniboxTextFieldIOS class]); |
+ // The characters to type into the omnibox text field. |
+ NSArray* inputCharacters = |
+ [NSArray arrayWithObjects:@"g", @"o", @"o", @"g", @"l", @"e", nil]; |
+ RepeatTimedRuns( |
+ "Typing", |
+ ^base::TimeDelta(int index) { |
+ EnableKeyboard(textField); |
+ NSMutableString* logMessage = [NSMutableString string]; |
+ base::TimeDelta elapsed; |
+ for (NSString* input in inputCharacters) { |
+ base::TimeDelta inputElapsed = TimeInsertText(textField, input); |
+ [logMessage appendFormat:@"%@'%@':%.0f", |
+ [logMessage length] ? @" " : @"", input, |
+ inputElapsed.InMillisecondsF()]; |
+ elapsed += inputElapsed; |
+ } |
+ NSLog(@"%2d: %@", index, logMessage); |
+ return elapsed; |
+ }, |
+ ^() { |
+ [textField setText:@""]; |
+ DisableKeyboard(textField); |
+ }); |
+} |
+} |