| // Copyright 2018 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. |
| |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/ssl/security_state_tab_helper.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/frame/toolbar_button_provider.h" |
| #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h" |
| #include "chrome/browser/ui/views/omnibox/omnibox_view_views.h" |
| #include "chrome/browser/ui/views/page_action/page_action_icon_container_view.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/views/scoped_macviews_browser_mode.h" |
| #include "components/security_state/core/security_state.h" |
| #include "components/toolbar/toolbar_field_trial.h" |
| #include "components/toolbar/toolbar_model_impl.h" |
| #include "components/zoom/zoom_controller.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/page_zoom.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "net/cert/ct_policy_status.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "services/network/public/cpp/features.h" |
| #include "ui/base/ui_base_switches.h" |
| |
| class LocationBarViewBrowserTest : public InProcessBrowserTest { |
| public: |
| LocationBarViewBrowserTest() = default; |
| |
| LocationBarView* GetLocationBarView() { |
| BrowserView* browser_view = |
| BrowserView::GetBrowserViewForBrowser(browser()); |
| return browser_view->GetLocationBarView(); |
| } |
| |
| PageActionIconView* GetZoomView() { |
| return BrowserView::GetBrowserViewForBrowser(browser()) |
| ->toolbar_button_provider() |
| ->GetPageActionIconContainerView() |
| ->GetPageActionIconView(PageActionIconType::kZoom); |
| } |
| |
| private: |
| test::ScopedMacViewsBrowserMode views_mode_{true}; |
| DISALLOW_COPY_AND_ASSIGN(LocationBarViewBrowserTest); |
| }; |
| |
| // Ensure the location bar decoration is added when zooming, and is removed when |
| // the bubble is closed, but only if zoom was reset. |
| IN_PROC_BROWSER_TEST_F(LocationBarViewBrowserTest, LocationBarDecoration) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| zoom::ZoomController* zoom_controller = |
| zoom::ZoomController::FromWebContents(web_contents); |
| PageActionIconView* zoom_view = GetZoomView(); |
| |
| ASSERT_TRUE(zoom_view); |
| EXPECT_FALSE(zoom_view->visible()); |
| EXPECT_FALSE(ZoomBubbleView::GetZoomBubble()); |
| |
| // Altering zoom should display a bubble. Note ZoomBubbleView closes |
| // asynchronously, so precede checks with a run loop flush. |
| zoom_controller->SetZoomLevel(content::ZoomFactorToZoomLevel(1.5)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(zoom_view->visible()); |
| EXPECT_TRUE(ZoomBubbleView::GetZoomBubble()); |
| |
| // Close the bubble at other than 100% zoom. Icon should remain visible. |
| ZoomBubbleView::CloseCurrentBubble(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(zoom_view->visible()); |
| EXPECT_FALSE(ZoomBubbleView::GetZoomBubble()); |
| |
| // Show the bubble again. |
| zoom_controller->SetZoomLevel(content::ZoomFactorToZoomLevel(2.0)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(zoom_view->visible()); |
| EXPECT_TRUE(ZoomBubbleView::GetZoomBubble()); |
| |
| // Remains visible at 100% until the bubble is closed. |
| zoom_controller->SetZoomLevel(content::ZoomFactorToZoomLevel(1.0)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(zoom_view->visible()); |
| EXPECT_TRUE(ZoomBubbleView::GetZoomBubble()); |
| |
| // Closing at 100% hides the icon. |
| ZoomBubbleView::CloseCurrentBubble(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(zoom_view->visible()); |
| EXPECT_FALSE(ZoomBubbleView::GetZoomBubble()); |
| } |
| |
| // Ensure that location bar bubbles close when the webcontents hides. |
| IN_PROC_BROWSER_TEST_F(LocationBarViewBrowserTest, BubblesCloseOnHide) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| zoom::ZoomController* zoom_controller = |
| zoom::ZoomController::FromWebContents(web_contents); |
| PageActionIconView* zoom_view = GetZoomView(); |
| |
| ASSERT_TRUE(zoom_view); |
| EXPECT_FALSE(zoom_view->visible()); |
| |
| zoom_controller->SetZoomLevel(content::ZoomFactorToZoomLevel(1.5)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(zoom_view->visible()); |
| EXPECT_TRUE(ZoomBubbleView::GetZoomBubble()); |
| |
| chrome::NewTab(browser()); |
| chrome::SelectNextTab(browser()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(ZoomBubbleView::GetZoomBubble()); |
| } |
| |
| class TouchLocationBarViewBrowserTest : public LocationBarViewBrowserTest { |
| public: |
| TouchLocationBarViewBrowserTest() = default; |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitchASCII( |
| switches::kTopChromeMD, switches::kTopChromeMDMaterialTouchOptimized); |
| LocationBarViewBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TouchLocationBarViewBrowserTest); |
| }; |
| |
| // Test the corners of the OmniboxViewViews do not get drawn on top of the |
| // rounded corners of the omnibox in touch mode. |
| IN_PROC_BROWSER_TEST_F(TouchLocationBarViewBrowserTest, OmniboxViewViewsSize) { |
| // Make sure all the LocationBarView children are invisible. This should |
| // ensure there are no trailing decorations at the end of the omnibox |
| // (currently, the LocationIconView is *always* added as a leading decoration, |
| // so it's not possible to test the leading side). |
| views::View* omnibox_view_views = GetLocationBarView()->omnibox_view(); |
| for (int i = 0; i < GetLocationBarView()->child_count(); ++i) { |
| views::View* child = GetLocationBarView()->child_at(i); |
| if (child != omnibox_view_views) |
| child->SetVisible(false); |
| } |
| |
| GetLocationBarView()->Layout(); |
| // Check |omnibox_view_views| is not wider than the LocationBarView with its |
| // rounded ends removed. |
| EXPECT_LE(omnibox_view_views->width(), |
| GetLocationBarView()->width() - GetLocationBarView()->height()); |
| // Check the trailing edge of |omnibox_view_views| does not exceed the |
| // trailing edge of the LocationBarView with its endcap removed. |
| EXPECT_LE(omnibox_view_views->bounds().right(), |
| GetLocationBarView()->GetLocalBoundsWithoutEndcaps().right()); |
| } |
| |
| // Make sure the IME autocomplete selection text is positioned correctly when |
| // there are no trailing decorations. |
| IN_PROC_BROWSER_TEST_F(TouchLocationBarViewBrowserTest, |
| IMEInlineAutocompletePosition) { |
| // Make sure all the LocationBarView children are invisible. This should |
| // ensure there are no trailing decorations at the end of the omnibox. |
| OmniboxViewViews* omnibox_view_views = GetLocationBarView()->omnibox_view(); |
| views::Label* ime_inline_autocomplete_view = |
| GetLocationBarView()->ime_inline_autocomplete_view_; |
| for (int i = 0; i < GetLocationBarView()->child_count(); ++i) { |
| views::View* child = GetLocationBarView()->child_at(i); |
| if (child != omnibox_view_views) |
| child->SetVisible(false); |
| } |
| omnibox_view_views->SetText(base::UTF8ToUTF16("谷")); |
| GetLocationBarView()->SetImeInlineAutocompletion(base::UTF8ToUTF16("歌")); |
| EXPECT_TRUE(ime_inline_autocomplete_view->visible()); |
| |
| GetLocationBarView()->Layout(); |
| |
| // Make sure the IME inline autocomplete view starts at the end of |
| // |omnibox_view_views|. |
| EXPECT_EQ(omnibox_view_views->bounds().right(), |
| ime_inline_autocomplete_view->x()); |
| } |
| |
| // After SetUpInterceptor() is called, requests to this hostname will be mocked |
| // and use specified certificate validation results. This allows tests to mock |
| // Extended Validation (EV) certificate connections. |
| const char kMockSecureHostname[] = "example-secure.test"; |
| const GURL kMockSecureURL = GURL("https://example-secure.test"); |
| |
| class SecurityIndicatorTest : public InProcessBrowserTest { |
| public: |
| SecurityIndicatorTest() : InProcessBrowserTest(), cert_(nullptr) {} |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| cert_ = |
| net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert_); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| LocationBarView* GetLocationBarView() { |
| BrowserView* browser_view = |
| BrowserView::GetBrowserViewForBrowser(browser()); |
| return browser_view->GetLocationBarView(); |
| } |
| |
| void SetUpInterceptor(net::CertStatus cert_status) { |
| url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>( |
| base::BindRepeating(&SecurityIndicatorTest::InterceptURLLoad, |
| base::Unretained(this), cert_status)); |
| } |
| |
| void ResetInterceptor() { url_loader_interceptor_.reset(); } |
| |
| bool InterceptURLLoad(net::CertStatus cert_status, |
| content::URLLoaderInterceptor::RequestParams* params) { |
| if (params->url_request.url.host() != kMockSecureHostname) |
| return false; |
| net::SSLInfo ssl_info; |
| ssl_info.cert = cert_; |
| ssl_info.cert_status = cert_status; |
| ssl_info.ct_policy_compliance = |
| net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| network::ResourceResponseHead resource_response; |
| resource_response.mime_type = "text/html"; |
| resource_response.ssl_info = ssl_info; |
| params->client->OnReceiveResponse(resource_response); |
| network::URLLoaderCompletionStatus completion_status; |
| completion_status.ssl_info = ssl_info; |
| params->client->OnComplete(completion_status); |
| return true; |
| } |
| |
| private: |
| scoped_refptr<net::X509Certificate> cert_; |
| test::ScopedMacViewsBrowserMode views_mode_{true}; |
| |
| std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SecurityIndicatorTest); |
| }; |
| |
| // Check that the security indicator text is correctly set for the various |
| // variations of the Security UI Study (https://crbug.com/803501). |
| IN_PROC_BROWSER_TEST_F(SecurityIndicatorTest, CheckIndicatorText) { |
| const GURL kMockNonsecureURL = |
| embedded_test_server()->GetURL("example.test", "/"); |
| const base::string16 kEvString = base::ASCIIToUTF16("Test CA [US]"); |
| const base::string16 kSecureString = base::ASCIIToUTF16("Secure"); |
| const base::string16 kEmptyString = base::string16(); |
| |
| const std::string kDefaultVariation = std::string(); |
| const std::string kEvToSecureVariation( |
| toolbar::features::kSimplifyHttpsIndicatorParameterEvToSecure); |
| const std::string kSecureToLockVariation( |
| toolbar::features::kSimplifyHttpsIndicatorParameterSecureToLock); |
| const std::string kBothToLockVariation( |
| toolbar::features::kSimplifyHttpsIndicatorParameterBothToLock); |
| |
| const struct { |
| std::string feature_param; |
| GURL url; |
| net::CertStatus cert_status; |
| security_state::SecurityLevel security_level; |
| bool should_show_text; |
| base::string16 indicator_text; |
| } cases[]{// Default |
| {kDefaultVariation, kMockSecureURL, net::CERT_STATUS_IS_EV, |
| security_state::EV_SECURE, true, kEvString}, |
| {kDefaultVariation, kMockSecureURL, 0, security_state::SECURE, true, |
| kSecureString}, |
| {kDefaultVariation, kMockNonsecureURL, 0, security_state::NONE, |
| false, kEmptyString}, |
| // Variation: EV To Secure |
| {kEvToSecureVariation, kMockSecureURL, net::CERT_STATUS_IS_EV, |
| security_state::EV_SECURE, true, kSecureString}, |
| {kEvToSecureVariation, kMockSecureURL, 0, security_state::SECURE, |
| true, kSecureString}, |
| {kEvToSecureVariation, kMockNonsecureURL, 0, security_state::NONE, |
| false, kEmptyString}, |
| // Variation: Secure to Lock |
| {kSecureToLockVariation, kMockSecureURL, net::CERT_STATUS_IS_EV, |
| security_state::EV_SECURE, true, kEvString}, |
| {kSecureToLockVariation, kMockSecureURL, 0, security_state::SECURE, |
| false, kEmptyString}, |
| {kSecureToLockVariation, kMockNonsecureURL, 0, security_state::NONE, |
| false, kEmptyString}, |
| // Variation: Both to Lock |
| {kBothToLockVariation, kMockSecureURL, net::CERT_STATUS_IS_EV, |
| security_state::EV_SECURE, false, kEmptyString}, |
| {kBothToLockVariation, kMockSecureURL, 0, security_state::SECURE, |
| false, kEmptyString}, |
| {kBothToLockVariation, kMockNonsecureURL, 0, security_state::NONE, |
| false, kEmptyString}}; |
| |
| security_state::SecurityInfo security_info; |
| content::WebContents* tab = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(tab); |
| SecurityStateTabHelper* helper = SecurityStateTabHelper::FromWebContents(tab); |
| ASSERT_TRUE(helper); |
| LocationBarView* location_bar_view = GetLocationBarView(); |
| |
| for (const auto& c : cases) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| if (c.feature_param.empty()) { |
| scoped_feature_list.InitAndDisableFeature( |
| toolbar::features::kSimplifyHttpsIndicator); |
| } else { |
| scoped_feature_list.InitAndEnableFeatureWithParameters( |
| toolbar::features::kSimplifyHttpsIndicator, |
| {{toolbar::features::kSimplifyHttpsIndicatorParameterName, |
| c.feature_param}}); |
| } |
| SetUpInterceptor(c.cert_status); |
| ui_test_utils::NavigateToURL(browser(), c.url); |
| helper->GetSecurityInfo(&security_info); |
| EXPECT_EQ(c.security_level, security_info.security_level); |
| EXPECT_EQ(c.should_show_text, |
| location_bar_view->ShouldShowLocationIconText()); |
| EXPECT_EQ(c.indicator_text, location_bar_view->GetLocationIconText()); |
| ResetInterceptor(); |
| } |
| } |