1 // Copyright 2016 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 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
6
7 #include "base/run_loop.h"
8 #include "base/scoped_observer.h"
9 #include "base/test/metrics/histogram_tester.h"
10 #include "build/build_config.h"
11 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
12 #include "chrome/browser/ssl/security_state_tab_helper.h"
13 #include "chrome/browser/ui/browser_commands.h"
14 #include "chrome/browser/ui/browser_finder.h"
15 #include "chrome/browser/ui/page_info/page_info_dialog.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/browser/ui/test/test_browser_dialog.h"
18 #include "chrome/browser/ui/view_ids.h"
19 #include "chrome/browser/ui/views/frame/browser_view.h"
20 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
21 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
22 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
23 #include "chrome/common/chrome_features.h"
24 #include "chrome/common/url_constants.h"
25 #include "chrome/test/base/in_process_browser_test.h"
26 #include "chrome/test/base/ui_test_utils.h"
27 #include "components/content_settings/core/browser/content_settings_registry.h"
28 #include "components/content_settings/core/common/content_settings_types.h"
29 #include "components/page_info/page_info.h"
30 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
31 #include "components/safe_browsing/content/password_protection/metrics_util.h"
32 #include "components/safe_browsing/core/features.h"
33 #include "components/strings/grit/components_strings.h"
34 #include "content/public/browser/navigation_handle.h"
35 #include "content/public/browser/render_frame_host.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/browser/web_contents_observer.h"
38 #include "content/public/common/referrer.h"
39 #include "content/public/test/browser_test.h"
40 #include "content/public/test/browser_test_utils.h"
41 #include "content/public/test/test_navigation_observer.h"
42 #include "net/test/cert_test_util.h"
43 #include "net/test/embedded_test_server/http_request.h"
44 #include "net/test/embedded_test_server/http_response.h"
45 #include "net/test/test_certificate_data.h"
46 #include "net/test/test_data_directory.h"
47 #include "testing/gmock/include/gmock/gmock.h"
48 #include "testing/gtest/include/gtest/gtest.h"
49 #include "third_party/blink/public/common/features.h"
50 #include "ui/accessibility/ax_action_data.h"
51 #include "ui/accessibility/ax_enums.mojom.h"
52 #include "ui/base/l10n/l10n_util.h"
53 #include "ui/events/types/event_type.h"
54 #include "ui/views/test/widget_test.h"
55
56 namespace {
57
58 using password_manager::metrics_util::PasswordType;
59
60 constexpr char kExpiredCertificateFile[] = "expired_cert.pem";
61
62 class ClickEvent : public ui::Event {
63 public:
ClickEvent()64 ClickEvent() : ui::Event(ui::ET_UNKNOWN, base::TimeTicks(), 0) {}
65 };
66
PerformMouseClickOnView(views::View * view)67 void PerformMouseClickOnView(views::View* view) {
68 ui::AXActionData data;
69 data.action = ax::mojom::Action::kDoDefault;
70 view->HandleAccessibleAction(data);
71 }
72
73 // Clicks the location icon to open the page info bubble.
OpenPageInfoBubble(Browser * browser)74 void OpenPageInfoBubble(Browser* browser) {
75 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
76 LocationIconView* location_icon_view =
77 browser_view->toolbar()->location_bar()->location_icon_view();
78 ASSERT_TRUE(location_icon_view);
79 ClickEvent event;
80 location_icon_view->ShowBubble(event);
81 views::BubbleDialogDelegateView* page_info =
82 PageInfoBubbleView::GetPageInfoBubbleForTesting();
83 EXPECT_NE(nullptr, page_info);
84 page_info->set_close_on_deactivate(false);
85 }
86
87 // Opens the Page Info bubble and retrieves the UI view identified by
88 // |view_id|.
GetView(Browser * browser,int view_id)89 views::View* GetView(Browser* browser, int view_id) {
90 views::Widget* page_info_bubble =
91 PageInfoBubbleView::GetPageInfoBubbleForTesting()->GetWidget();
92 EXPECT_TRUE(page_info_bubble);
93
94 views::View* view = page_info_bubble->GetRootView()->GetViewByID(view_id);
95 EXPECT_TRUE(view);
96 return view;
97 }
98
99 // Clicks the "Site settings" button from Page Info and waits for a "Settings"
100 // tab to open.
ClickAndWaitForSettingsPageToOpen(views::View * site_settings_button)101 void ClickAndWaitForSettingsPageToOpen(views::View* site_settings_button) {
102 content::WebContentsAddedObserver new_tab_observer;
103 PerformMouseClickOnView(site_settings_button);
104
105 base::string16 expected_title(base::ASCIIToUTF16("Settings"));
106 content::TitleWatcher title_watcher(new_tab_observer.GetWebContents(),
107 expected_title);
108 EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
109 }
110
111 // Returns the URL of the new tab that's opened on clicking the "Site settings"
112 // button from Page Info.
OpenSiteSettingsForUrl(Browser * browser,const GURL & url)113 const GURL OpenSiteSettingsForUrl(Browser* browser, const GURL& url) {
114 ui_test_utils::NavigateToURL(browser, url);
115 OpenPageInfoBubble(browser);
116 // Get site settings button.
117 views::View* site_settings_button = GetView(
118 browser,
119 PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_SITE_SETTINGS);
120 ClickAndWaitForSettingsPageToOpen(site_settings_button);
121
122 return browser->tab_strip_model()
123 ->GetActiveWebContents()
124 ->GetLastCommittedURL();
125 }
126
127 } // namespace
128
129 class PageInfoBubbleViewBrowserTest : public DialogBrowserTest {
130 public:
131 PageInfoBubbleViewBrowserTest() = default;
132 // DialogBrowserTest:
ShowUi(const std::string & name)133 void ShowUi(const std::string& name) override {
134 // Bubble dialogs' bounds may exceed the display's work area.
135 // https://crbug.com/893292.
136 set_should_verify_dialog_bounds(false);
137
138 // All the possible test names.
139 constexpr char kInsecure[] = "Insecure";
140 constexpr char kInternal[] = "Internal";
141 constexpr char kInternalExtension[] = "InternalExtension";
142 constexpr char kInternalViewSource[] = "InternalViewSource";
143 constexpr char kFile[] = "File";
144 constexpr char kSecure[] = "Secure";
145 constexpr char kEvSecure[] = "EvSecure";
146 constexpr char kMalware[] = "Malware";
147 constexpr char kDeceptive[] = "Deceptive";
148 constexpr char kUnwantedSoftware[] = "UnwantedSoftware";
149 constexpr char kSignInSyncPasswordReuse[] = "SignInSyncPasswordReuse";
150 constexpr char kSignInNonSyncPasswordReuse[] = "SignInNonSyncPasswordReuse";
151 constexpr char kEnterprisePasswordReuse[] = "EnterprisePasswordReuse";
152 constexpr char kSavedPasswordReuse[] = "SavedPasswordReuse";
153 constexpr char kMalwareAndBadCert[] = "MalwareAndBadCert";
154 constexpr char kMixedContentForm[] = "MixedContentForm";
155 constexpr char kMixedContent[] = "MixedContent";
156 constexpr char kAllowAllPermissions[] = "AllowAllPermissions";
157 constexpr char kBlockAllPermissions[] = "BlockAllPermissions";
158
159 const GURL internal_url("chrome://settings");
160 const GURL internal_extension_url("chrome-extension://example");
161 const GURL file_url("file:///Users/homedirname/folder/file.pdf");
162 // Note the following two URLs are not really necessary to get the different
163 // versions of Page Info to appear, but are here to indicate the type of
164 // URL each IdentityInfo type would normally be associated with.
165 const GURL https_url("https://example.com");
166 const GURL http_url("http://example.com");
167
168 GURL url = http_url;
169 if (name == kSecure || name == kEvSecure || name == kMixedContentForm ||
170 name == kMixedContent || name == kAllowAllPermissions ||
171 name == kBlockAllPermissions || name == kMalwareAndBadCert) {
172 url = https_url;
173 }
174 if (name == kInternal) {
175 url = internal_url;
176 } else if (name == kInternalExtension) {
177 url = internal_extension_url;
178 } else if (name == kInternalViewSource) {
179 constexpr char kTestHtml[] = "/viewsource/test.html";
180 ASSERT_TRUE(embedded_test_server()->Start());
181 url = GURL(content::kViewSourceScheme + std::string(":") +
182 embedded_test_server()->GetURL(kTestHtml).spec());
183 } else if (name == kFile) {
184 url = file_url;
185 }
186
187 ui_test_utils::NavigateToURL(browser(), url);
188 OpenPageInfoBubble(browser());
189
190 PageInfoUI::IdentityInfo identity;
191 if (name == kInsecure) {
192 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_NO_CERT;
193 } else if (name == kSecure || name == kAllowAllPermissions ||
194 name == kBlockAllPermissions) {
195 // Generate a valid mock HTTPS identity, with a certificate.
196 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_CERT;
197 constexpr char kGoodCertificateFile[] = "ok_cert.pem";
198 identity.certificate = net::ImportCertFromFile(
199 net::GetTestCertsDirectory(), kGoodCertificateFile);
200 } else if (name == kEvSecure) {
201 // Generate a valid mock EV HTTPS identity, with an EV certificate. Must
202 // match conditions in PageInfoBubbleView::SetIdentityInfo() for setting
203 // the certificate button subtitle.
204 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_EV_CERT;
205 identity.connection_status = PageInfo::SITE_CONNECTION_STATUS_ENCRYPTED;
206 scoped_refptr<net::X509Certificate> ev_cert =
207 net::X509Certificate::CreateFromBytes(
208 reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der));
209 ASSERT_TRUE(ev_cert);
210 identity.certificate = ev_cert;
211 } else if (name == kMalware) {
212 identity.safe_browsing_status = PageInfo::SAFE_BROWSING_STATUS_MALWARE;
213 } else if (name == kDeceptive) {
214 identity.safe_browsing_status =
215 PageInfo::SAFE_BROWSING_STATUS_SOCIAL_ENGINEERING;
216 } else if (name == kUnwantedSoftware) {
217 identity.safe_browsing_status =
218 PageInfo::SAFE_BROWSING_STATUS_UNWANTED_SOFTWARE;
219 } else if (name == kSignInSyncPasswordReuse) {
220 identity.safe_browsing_status =
221 PageInfo::SAFE_BROWSING_STATUS_SIGNED_IN_SYNC_PASSWORD_REUSE;
222 } else if (name == kSignInNonSyncPasswordReuse) {
223 identity.safe_browsing_status =
224 PageInfo::SAFE_BROWSING_STATUS_SIGNED_IN_NON_SYNC_PASSWORD_REUSE;
225 } else if (name == kEnterprisePasswordReuse) {
226 identity.safe_browsing_status =
227 PageInfo::SAFE_BROWSING_STATUS_ENTERPRISE_PASSWORD_REUSE;
228 } else if (name == kSavedPasswordReuse) {
229 identity.safe_browsing_status =
230 PageInfo::SAFE_BROWSING_STATUS_SAVED_PASSWORD_REUSE;
231 } else if (name == kMalwareAndBadCert) {
232 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_ERROR;
233 identity.certificate = net::ImportCertFromFile(
234 net::GetTestCertsDirectory(), kExpiredCertificateFile);
235 identity.safe_browsing_status = PageInfo::SAFE_BROWSING_STATUS_MALWARE;
236 } else if (name == kMixedContentForm) {
237 identity.identity_status =
238 PageInfo::SITE_IDENTITY_STATUS_ADMIN_PROVIDED_CERT;
239 identity.connection_status =
240 PageInfo::SITE_CONNECTION_STATUS_INSECURE_FORM_ACTION;
241 } else if (name == kMixedContent) {
242 identity.identity_status =
243 PageInfo::SITE_IDENTITY_STATUS_ADMIN_PROVIDED_CERT;
244 identity.connection_status =
245 PageInfo::SITE_CONNECTION_STATUS_INSECURE_PASSIVE_SUBRESOURCE;
246 }
247
248 if (name == kAllowAllPermissions || name == kBlockAllPermissions) {
249 // Generate a |PermissionInfoList| with every permission allowed/blocked.
250 PermissionInfoList permissions_list;
251 for (ContentSettingsType content_type :
252 PageInfo::GetAllPermissionsForTesting()) {
253 PageInfo::PermissionInfo info;
254 info.type = content_type;
255 info.setting = (name == kAllowAllPermissions) ? CONTENT_SETTING_ALLOW
256 : CONTENT_SETTING_BLOCK;
257 info.default_setting =
258 content_settings::ContentSettingsRegistry::GetInstance()
259 ->Get(info.type)
260 ->GetInitialDefaultSetting();
261 info.source = content_settings::SettingSource::SETTING_SOURCE_USER;
262 info.is_incognito = false;
263 permissions_list.push_back(info);
264 }
265
266 ChosenObjectInfoList chosen_object_list;
267
268 PageInfoBubbleView* page_info_bubble_view =
269 static_cast<PageInfoBubbleView*>(
270 PageInfoBubbleView::GetPageInfoBubbleForTesting());
271 // Normally |PageInfoBubbleView| doesn't update the permissions already
272 // shown if they change while it's still open. For this test, manually
273 // force an update by clearing the existing permission views here.
274 page_info_bubble_view->GetFocusManager()->SetFocusedView(nullptr);
275 page_info_bubble_view->selector_rows_.clear();
276 page_info_bubble_view->permissions_view_->RemoveAllChildViews(true);
277
278 page_info_bubble_view->SetPermissionInfo(permissions_list,
279 std::move(chosen_object_list));
280 }
281
282 if (name != kInsecure && name.find(kInternal) == std::string::npos &&
283 name != kFile) {
284 // The bubble may be PageInfoBubbleView or InternalPageInfoBubbleView. The
285 // latter is only used for |kInternal|, so it is safe to static_cast here.
286 static_cast<PageInfoBubbleView*>(
287 PageInfoBubbleView::GetPageInfoBubbleForTesting())
288 ->SetIdentityInfo(identity);
289 }
290 }
291
VerifyUi()292 bool VerifyUi() override {
293 if (!DialogBrowserTest::VerifyUi())
294 return false;
295 // Check that each expected View is present in the Page Info bubble.
296 views::View* page_info_bubble_view =
297 PageInfoBubbleView::GetPageInfoBubbleForTesting()->GetContentsView();
298 for (auto id : expected_identifiers_) {
299 views::View* view = GetView(browser(), id);
300 if (!page_info_bubble_view->Contains(view))
301 return false;
302 }
303 return true;
304 }
305
306 protected:
GetSimplePageUrl() const307 GURL GetSimplePageUrl() const {
308 return ui_test_utils::GetTestUrl(
309 base::FilePath(base::FilePath::kCurrentDirectory),
310 base::FilePath(FILE_PATH_LITERAL("simple.html")));
311 }
312
GetIframePageUrl() const313 GURL GetIframePageUrl() const {
314 return ui_test_utils::GetTestUrl(
315 base::FilePath(base::FilePath::kCurrentDirectory),
316 base::FilePath(FILE_PATH_LITERAL("iframe_blank.html")));
317 }
318
ExecuteJavaScriptForTests(const std::string & js)319 void ExecuteJavaScriptForTests(const std::string& js) {
320 base::RunLoop run_loop;
321 browser()
322 ->tab_strip_model()
323 ->GetActiveWebContents()
324 ->GetMainFrame()
325 ->ExecuteJavaScriptForTests(
326 base::ASCIIToUTF16(js),
327 base::BindOnce([](const base::Closure& quit_callback,
328 base::Value result) { quit_callback.Run(); },
329 run_loop.QuitClosure()));
330 run_loop.Run();
331 }
332
TriggerReloadPromptOnClose() const333 void TriggerReloadPromptOnClose() const {
334 PageInfoBubbleView* const page_info_bubble_view =
335 static_cast<PageInfoBubbleView*>(
336 PageInfoBubbleView::GetPageInfoBubbleForTesting());
337 ASSERT_NE(nullptr, page_info_bubble_view);
338
339 // Set some dummy non-default permissions. This will trigger a reload prompt
340 // when the bubble is closed.
341 PageInfo::PermissionInfo permission;
342 permission.type = ContentSettingsType::NOTIFICATIONS;
343 permission.setting = ContentSetting::CONTENT_SETTING_BLOCK;
344 permission.default_setting = ContentSetting::CONTENT_SETTING_ASK;
345 permission.source = content_settings::SettingSource::SETTING_SOURCE_USER;
346 permission.is_incognito = false;
347 page_info_bubble_view->OnPermissionChanged(permission);
348 }
349
SetPageInfoBubbleIdentityInfo(const PageInfoUI::IdentityInfo & identity_info)350 void SetPageInfoBubbleIdentityInfo(
351 const PageInfoUI::IdentityInfo& identity_info) {
352 static_cast<PageInfoBubbleView*>(
353 PageInfoBubbleView::GetPageInfoBubbleForTesting())
354 ->SetIdentityInfo(identity_info);
355 }
356
GetCertificateButtonTitle() const357 base::string16 GetCertificateButtonTitle() const {
358 // Only PageInfoBubbleViewBrowserTest can access certificate_button_ in
359 // PageInfoBubbleView, or title() in HoverButton.
360 PageInfoBubbleView* page_info_bubble_view =
361 static_cast<PageInfoBubbleView*>(
362 PageInfoBubbleView::GetPageInfoBubbleForTesting());
363 return page_info_bubble_view->certificate_button_->title()->GetText();
364 }
365
GetCertificateButtonSubtitle() const366 base::string16 GetCertificateButtonSubtitle() const {
367 PageInfoBubbleView* page_info_bubble_view =
368 static_cast<PageInfoBubbleView*>(
369 PageInfoBubbleView::GetPageInfoBubbleForTesting());
370 return page_info_bubble_view->certificate_button_->subtitle()->GetText();
371 }
372
GetPageInfoBubbleViewDetailText()373 const base::string16 GetPageInfoBubbleViewDetailText() {
374 PageInfoBubbleView* page_info_bubble_view =
375 static_cast<PageInfoBubbleView*>(
376 PageInfoBubbleView::GetPageInfoBubbleForTesting());
377 return page_info_bubble_view->details_text();
378 }
379
380 private:
381 std::vector<PageInfoBubbleView::PageInfoBubbleViewID> expected_identifiers_;
382
383 DISALLOW_COPY_AND_ASSIGN(PageInfoBubbleViewBrowserTest);
384 };
385
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ShowBubble)386 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, ShowBubble) {
387 OpenPageInfoBubble(browser());
388 EXPECT_EQ(PageInfoBubbleView::BUBBLE_PAGE_INFO,
389 PageInfoBubbleView::GetShownBubbleType());
390 }
391
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ChromeURL)392 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, ChromeURL) {
393 ui_test_utils::NavigateToURL(browser(), GURL("chrome://settings"));
394 OpenPageInfoBubble(browser());
395 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
396 PageInfoBubbleView::GetShownBubbleType());
397 }
398
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ChromeExtensionURL)399 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, ChromeExtensionURL) {
400 ui_test_utils::NavigateToURL(
401 browser(), GURL("chrome-extension://extension-id/options.html"));
402 OpenPageInfoBubble(browser());
403 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
404 PageInfoBubbleView::GetShownBubbleType());
405 }
406
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ChromeDevtoolsURL)407 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, ChromeDevtoolsURL) {
408 ui_test_utils::NavigateToURL(
409 browser(), GURL("devtools://devtools/bundled/inspector.html"));
410 OpenPageInfoBubble(browser());
411 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
412 PageInfoBubbleView::GetShownBubbleType());
413 }
414
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ViewSourceURL)415 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, ViewSourceURL) {
416 ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
417 browser()
418 ->tab_strip_model()
419 ->GetActiveWebContents()
420 ->GetMainFrame()
421 ->ViewSource();
422 OpenPageInfoBubble(browser());
423 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
424 PageInfoBubbleView::GetShownBubbleType());
425 }
426
427 // Test opening "Site Details" via Page Info from an ASCII origin does the
428 // correct URL canonicalization.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,SiteSettingsLink)429 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, SiteSettingsLink) {
430 GURL url = GURL("https://www.google.com/");
431 std::string expected_origin = "https%3A%2F%2Fwww.google.com";
432 EXPECT_EQ(GURL(chrome::kChromeUISiteDetailsPrefixURL + expected_origin),
433 OpenSiteSettingsForUrl(browser(), url));
434 }
435
436 // Test opening "Site Details" via Page Info from a non-ASCII URL converts it to
437 // an origin and does punycode conversion as well as URL canonicalization.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,SiteSettingsLinkWithNonAsciiUrl)438 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
439 SiteSettingsLinkWithNonAsciiUrl) {
440 GURL url = GURL("http://.ws/other/stuff.htm");
441 std::string expected_origin = "http%3A%2F%2Fxn--9q9h.ws";
442 EXPECT_EQ(GURL(chrome::kChromeUISiteDetailsPrefixURL + expected_origin),
443 OpenSiteSettingsForUrl(browser(), url));
444 }
445
446 // Test opening "Site Details" via Page Info from an origin with a non-default
447 // (scheme, port) pair will specify port # in the origin passed to query params.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,SiteSettingsLinkWithNonDefaultPort)448 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
449 SiteSettingsLinkWithNonDefaultPort) {
450 GURL url = GURL("https://www.example.com:8372");
451 std::string expected_origin = "https%3A%2F%2Fwww.example.com%3A8372";
452 EXPECT_EQ(GURL(chrome::kChromeUISiteDetailsPrefixURL + expected_origin),
453 OpenSiteSettingsForUrl(browser(), url));
454 }
455
456 // Test opening "Site Details" via Page Info from about:blank goes to "Content
457 // Settings" (the alternative is a blank origin being sent to "Site Details").
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,SiteSettingsLinkWithAboutBlankURL)458 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
459 SiteSettingsLinkWithAboutBlankURL) {
460 EXPECT_EQ(GURL(chrome::kChromeUIContentSettingsURL),
461 OpenSiteSettingsForUrl(browser(), GURL(url::kAboutBlankURL)));
462 }
463
464 // Test opening page info bubble that matches
465 // SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE threat type.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,VerifyEnterprisePasswordReusePageInfoBubble)466 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
467 VerifyEnterprisePasswordReusePageInfoBubble) {
468 ASSERT_TRUE(embedded_test_server()->Start());
469 base::HistogramTester histograms;
470 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
471
472 // Update security state of the current page to match
473 // SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE.
474 safe_browsing::ChromePasswordProtectionService* service =
475 safe_browsing::ChromePasswordProtectionService::
476 GetPasswordProtectionService(browser()->profile());
477 content::WebContents* contents =
478 browser()->tab_strip_model()->GetActiveWebContents();
479 safe_browsing::ReusedPasswordAccountType reused_password_account_type;
480 reused_password_account_type.set_account_type(
481 safe_browsing::ReusedPasswordAccountType::NON_GAIA_ENTERPRISE);
482 service->set_reused_password_account_type_for_last_shown_warning(
483 reused_password_account_type);
484
485 service->ShowModalWarning(
486 contents, safe_browsing::RequestOutcome::UNKNOWN,
487 safe_browsing::LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED,
488 "unused_token", reused_password_account_type);
489
490 OpenPageInfoBubble(browser());
491 views::View* change_password_button = GetView(
492 browser(), PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_CHANGE_PASSWORD);
493 views::View* allowlist_password_reuse_button = GetView(
494 browser(),
495 PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_ALLOWLIST_PASSWORD_REUSE);
496
497 SecurityStateTabHelper* helper =
498 SecurityStateTabHelper::FromWebContents(contents);
499 std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
500 helper->GetVisibleSecurityState();
501 ASSERT_EQ(security_state::MALICIOUS_CONTENT_STATUS_ENTERPRISE_PASSWORD_REUSE,
502 visible_security_state->malicious_content_status);
503 ASSERT_EQ(l10n_util::GetStringUTF16(
504 IDS_PAGE_INFO_CHANGE_PASSWORD_DETAILS_ENTERPRISE),
505 GetPageInfoBubbleViewDetailText());
506
507 // Verify these two buttons are showing.
508 EXPECT_TRUE(change_password_button->GetVisible());
509 EXPECT_TRUE(allowlist_password_reuse_button->GetVisible());
510
511 // Verify clicking on button will increment corresponding bucket of
512 // PasswordProtection.PageInfoAction.NonGaiaEnterprisePasswordEntry histogram.
513 PerformMouseClickOnView(change_password_button);
514 EXPECT_THAT(
515 histograms.GetAllSamples(
516 safe_browsing::kEnterprisePasswordPageInfoHistogram),
517 testing::ElementsAre(
518 base::Bucket(static_cast<int>(safe_browsing::WarningAction::SHOWN),
519 1),
520 base::Bucket(
521 static_cast<int>(safe_browsing::WarningAction::CHANGE_PASSWORD),
522 1)));
523
524 PerformMouseClickOnView(allowlist_password_reuse_button);
525 EXPECT_THAT(
526 histograms.GetAllSamples(
527 safe_browsing::kEnterprisePasswordPageInfoHistogram),
528 testing::ElementsAre(
529 base::Bucket(static_cast<int>(safe_browsing::WarningAction::SHOWN),
530 1),
531 base::Bucket(
532 static_cast<int>(safe_browsing::WarningAction::CHANGE_PASSWORD),
533 1),
534 base::Bucket(static_cast<int>(
535 safe_browsing::WarningAction::MARK_AS_LEGITIMATE),
536 1)));
537 // Security state will change after allowlisting.
538 visible_security_state = helper->GetVisibleSecurityState();
539 EXPECT_EQ(security_state::MALICIOUS_CONTENT_STATUS_NONE,
540 visible_security_state->malicious_content_status);
541 }
542
543 // Test opening page info bubble that matches
544 // SB_THREAT_TYPE_SAVED_PASSWORD_REUSE threat type.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,VerifySavedPasswordReusePageInfoBubble)545 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
546 VerifySavedPasswordReusePageInfoBubble) {
547 ASSERT_TRUE(embedded_test_server()->Start());
548 base::HistogramTester histograms;
549 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
550
551 // Update security state of the current page to match
552 // SB_THREAT_TYPE_SAVED_PASSWORD_REUSE.
553 safe_browsing::ChromePasswordProtectionService* service =
554 safe_browsing::ChromePasswordProtectionService::
555 GetPasswordProtectionService(browser()->profile());
556 content::WebContents* contents =
557 browser()->tab_strip_model()->GetActiveWebContents();
558 safe_browsing::ReusedPasswordAccountType reused_password_account_type;
559 reused_password_account_type.set_account_type(
560 safe_browsing::ReusedPasswordAccountType::SAVED_PASSWORD);
561 service->set_reused_password_account_type_for_last_shown_warning(
562 reused_password_account_type);
563
564 service->ShowModalWarning(
565 contents, safe_browsing::RequestOutcome::UNKNOWN,
566 safe_browsing::LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED,
567 "unused_token", reused_password_account_type);
568
569 OpenPageInfoBubble(browser());
570 views::View* change_password_button = GetView(
571 browser(), PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_CHANGE_PASSWORD);
572 views::View* allowlist_password_reuse_button = GetView(
573 browser(),
574 PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_ALLOWLIST_PASSWORD_REUSE);
575
576 SecurityStateTabHelper* helper =
577 SecurityStateTabHelper::FromWebContents(contents);
578 std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
579 helper->GetVisibleSecurityState();
580 ASSERT_EQ(security_state::MALICIOUS_CONTENT_STATUS_SAVED_PASSWORD_REUSE,
581 visible_security_state->malicious_content_status);
582
583 // Verify these two buttons are showing.
584 EXPECT_TRUE(change_password_button->GetVisible());
585 EXPECT_TRUE(allowlist_password_reuse_button->GetVisible());
586
587 // Verify clicking on button will increment corresponding bucket of
588 // PasswordProtection.PageInfoAction.NonGaiaEnterprisePasswordEntry histogram.
589 PerformMouseClickOnView(change_password_button);
590 EXPECT_THAT(
591 histograms.GetAllSamples(safe_browsing::kSavedPasswordPageInfoHistogram),
592 testing::ElementsAre(
593 base::Bucket(static_cast<int>(safe_browsing::WarningAction::SHOWN),
594 1),
595 base::Bucket(
596 static_cast<int>(safe_browsing::WarningAction::CHANGE_PASSWORD),
597 1)));
598
599 PerformMouseClickOnView(allowlist_password_reuse_button);
600 EXPECT_THAT(
601 histograms.GetAllSamples(safe_browsing::kSavedPasswordPageInfoHistogram),
602 testing::ElementsAre(
603 base::Bucket(static_cast<int>(safe_browsing::WarningAction::SHOWN),
604 1),
605 base::Bucket(
606 static_cast<int>(safe_browsing::WarningAction::CHANGE_PASSWORD),
607 1),
608 base::Bucket(static_cast<int>(
609 safe_browsing::WarningAction::MARK_AS_LEGITIMATE),
610 1)));
611 // Security state will change after allowlisting.
612 visible_security_state = helper->GetVisibleSecurityState();
613 EXPECT_EQ(security_state::MALICIOUS_CONTENT_STATUS_NONE,
614 visible_security_state->malicious_content_status);
615 }
616
617 // Shows the Page Info bubble for a HTTP page (specifically, about:blank).
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_Insecure)618 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_Insecure) {
619 ShowAndVerifyUi();
620 }
621
622 // Shows the Page Info bubble for a HTTPS page.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_Secure)623 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_Secure) {
624 ShowAndVerifyUi();
625 }
626
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_EvSecure)627 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_EvSecure) {
628 ShowAndVerifyUi();
629 }
630
631 // Shows the Page Info bubble for an internal page, e.g. chrome://settings.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_Internal)632 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_Internal) {
633 ShowAndVerifyUi();
634 }
635
636 // Shows the Page Info bubble for an extensions page.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_InternalExtension)637 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
638 InvokeUi_InternalExtension) {
639 ShowAndVerifyUi();
640 }
641
642 // Shows the Page Info bubble for a chrome page that displays the source HTML.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_InternalViewSource)643 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
644 InvokeUi_InternalViewSource) {
645 ShowAndVerifyUi();
646 }
647
648 // Shows the Page Info bubble for a file:// URL.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_File)649 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_File) {
650 ShowAndVerifyUi();
651 }
652
653 // Shows the Page Info bubble for a site flagged for malware by Safe Browsing.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_Malware)654 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_Malware) {
655 ShowAndVerifyUi();
656 }
657
658 // Shows the Page Info bubble for a site flagged for social engineering by Safe
659 // Browsing.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_Deceptive)660 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_Deceptive) {
661 ShowAndVerifyUi();
662 }
663
664 // Shows the Page Info bubble for a site flagged for distributing unwanted
665 // software by Safe Browsing.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_UnwantedSoftware)666 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
667 InvokeUi_UnwantedSoftware) {
668 ShowAndVerifyUi();
669 }
670
671 // Shows the Page Info bubble Safe Browsing warning after detecting the user has
672 // re-used an existing password on a site, e.g. due to phishing.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_PasswordReuse)673 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_PasswordReuse) {
674 ShowAndVerifyUi();
675 }
676
677 // Shows the Page Info bubble for a site flagged for malware that also has a bad
678 // certificate.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_MalwareAndBadCert)679 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
680 InvokeUi_MalwareAndBadCert) {
681 ShowAndVerifyUi();
682 }
683
684 // Shows the Page Info bubble for an admin-provided cert when the page is
685 // secure, but has a form that submits to an insecure url.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_MixedContentForm)686 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
687 InvokeUi_MixedContentForm) {
688 ShowAndVerifyUi();
689 }
690
691 // Shows the Page Info bubble for an admin-provided cert when the page is
692 // secure, but it uses insecure resources (e.g. images).
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_MixedContent)693 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, InvokeUi_MixedContent) {
694 ShowAndVerifyUi();
695 }
696
697 // Shows the Page Info bubble with all the permissions displayed with 'Allow'
698 // set. All permissions will show regardless of its factory default value.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_AllowAllPermissions)699 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
700 InvokeUi_AllowAllPermissions) {
701 ShowAndVerifyUi();
702 }
703
704 // Shows the Page Info bubble with all the permissions displayed with 'Block'
705 // set. All permissions will show regardless of its factory default value.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,InvokeUi_BlockAllPermissions)706 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
707 InvokeUi_BlockAllPermissions) {
708 ShowAndVerifyUi();
709 }
710
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ClosesOnUserNavigateToSamePage)711 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
712 ClosesOnUserNavigateToSamePage) {
713 ui_test_utils::NavigateToURL(browser(), GetSimplePageUrl());
714 EXPECT_EQ(PageInfoBubbleView::BUBBLE_NONE,
715 PageInfoBubbleView::GetShownBubbleType());
716 OpenPageInfoBubble(browser());
717 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
718 PageInfoBubbleView::GetShownBubbleType());
719 ui_test_utils::NavigateToURL(browser(), GetSimplePageUrl());
720 EXPECT_EQ(PageInfoBubbleView::BUBBLE_NONE,
721 PageInfoBubbleView::GetShownBubbleType());
722 }
723
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,ClosesOnUserNavigateToDifferentPage)724 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
725 ClosesOnUserNavigateToDifferentPage) {
726 ui_test_utils::NavigateToURL(browser(), GetSimplePageUrl());
727 EXPECT_EQ(PageInfoBubbleView::BUBBLE_NONE,
728 PageInfoBubbleView::GetShownBubbleType());
729 OpenPageInfoBubble(browser());
730 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
731 PageInfoBubbleView::GetShownBubbleType());
732 ui_test_utils::NavigateToURL(browser(), GetIframePageUrl());
733 EXPECT_EQ(PageInfoBubbleView::BUBBLE_NONE,
734 PageInfoBubbleView::GetShownBubbleType());
735 }
736
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,DoesntCloseOnSubframeNavigate)737 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
738 DoesntCloseOnSubframeNavigate) {
739 ui_test_utils::NavigateToURL(browser(), GetIframePageUrl());
740 EXPECT_EQ(PageInfoBubbleView::BUBBLE_NONE,
741 PageInfoBubbleView::GetShownBubbleType());
742 OpenPageInfoBubble(browser());
743 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
744 PageInfoBubbleView::GetShownBubbleType());
745 content::NavigateIframeToURL(
746 browser()->tab_strip_model()->GetActiveWebContents(), "test",
747 GetSimplePageUrl());
748 // Expect that the bubble is still open even after a subframe navigation has
749 // happened.
750 EXPECT_EQ(PageInfoBubbleView::BUBBLE_INTERNAL_PAGE,
751 PageInfoBubbleView::GetShownBubbleType());
752 }
753
754 class PageInfoBubbleViewBrowserTestWithAutoupgradesDisabled
755 : public PageInfoBubbleViewBrowserTest {
756 public:
SetUpCommandLine(base::CommandLine * command_line)757 void SetUpCommandLine(base::CommandLine* command_line) override {
758 PageInfoBubbleViewBrowserTest::SetUpCommandLine(command_line);
759 feature_list.InitAndDisableFeature(
760 blink::features::kMixedContentAutoupgrade);
761 }
762
763 private:
764 base::test::ScopedFeatureList feature_list;
765 };
766
767 // Ensure changes to security state are reflected in an open PageInfo bubble.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTestWithAutoupgradesDisabled,UpdatesOnSecurityStateChange)768 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTestWithAutoupgradesDisabled,
769 UpdatesOnSecurityStateChange) {
770 net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
771 https_server.AddDefaultHandlers(
772 base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
773 ASSERT_TRUE(https_server.Start());
774
775 ui_test_utils::NavigateToURL(
776 browser(), https_server.GetURL("/delayed_mixed_content.html"));
777 OpenPageInfoBubble(browser());
778
779 views::BubbleDialogDelegateView* page_info =
780 PageInfoBubbleView::GetPageInfoBubbleForTesting();
781
782 EXPECT_EQ(page_info->GetWindowTitle(),
783 l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURE_SUMMARY));
784
785 ExecuteJavaScriptForTests("load_mixed();");
786 EXPECT_EQ(page_info->GetWindowTitle(),
787 l10n_util::GetStringUTF16(IDS_PAGE_INFO_MIXED_CONTENT_SUMMARY));
788 }
789
790 // Ensure a page can both have an invalid certificate *and* be blocked by Safe
791 // Browsing. Regression test for bug 869925.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,BlockedAndInvalidCert)792 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, BlockedAndInvalidCert) {
793 net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
794 https_server.AddDefaultHandlers(
795 base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
796 ASSERT_TRUE(https_server.Start());
797
798 ui_test_utils::NavigateToURL(browser(), https_server.GetURL("/simple.html"));
799
800 // Setup the bogus identity with an expired cert and SB flagging.
801 PageInfoUI::IdentityInfo identity;
802 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_ERROR;
803 identity.certificate = net::ImportCertFromFile(net::GetTestCertsDirectory(),
804 kExpiredCertificateFile);
805 identity.safe_browsing_status = PageInfo::SAFE_BROWSING_STATUS_MALWARE;
806 OpenPageInfoBubble(browser());
807
808 SetPageInfoBubbleIdentityInfo(identity);
809
810 views::BubbleDialogDelegateView* page_info =
811 PageInfoBubbleView::GetPageInfoBubbleForTesting();
812
813 // Verify bubble complains of malware...
814 EXPECT_EQ(page_info->GetWindowTitle(),
815 l10n_util::GetStringUTF16(IDS_PAGE_INFO_MALWARE_SUMMARY));
816
817 // ...and has a "Certificate (Invalid)" button.
818 const base::string16 invalid_parens = l10n_util::GetStringUTF16(
819 IDS_PAGE_INFO_CERTIFICATE_INVALID_PARENTHESIZED);
820 EXPECT_EQ(GetCertificateButtonTitle(),
821 l10n_util::GetStringFUTF16(IDS_PAGE_INFO_CERTIFICATE_BUTTON_TEXT,
822 invalid_parens));
823 }
824
825 // Ensure a page that has an EV certificate *and* is blocked by Safe Browsing
826 // shows the correct PageInfo UI. Regression test for crbug.com/1014240.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,MalwareAndEvCert)827 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest, MalwareAndEvCert) {
828 net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
829 https_server.AddDefaultHandlers(
830 base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
831 ASSERT_TRUE(https_server.Start());
832
833 ui_test_utils::NavigateToURL(browser(), https_server.GetURL("/simple.html"));
834
835 // Generate a valid mock EV HTTPS identity, with an EV certificate. Must
836 // match conditions in PageInfoBubbleView::SetIdentityInfo() for setting
837 // the certificate button subtitle.
838 PageInfoUI::IdentityInfo identity;
839 identity.identity_status = PageInfo::SITE_IDENTITY_STATUS_EV_CERT;
840 identity.connection_status = PageInfo::SITE_CONNECTION_STATUS_ENCRYPTED;
841 scoped_refptr<net::X509Certificate> ev_cert =
842 net::X509Certificate::CreateFromBytes(
843 reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der));
844 ASSERT_TRUE(ev_cert);
845 identity.certificate = ev_cert;
846
847 // Have the page also trigger an SB malware warning.
848 identity.safe_browsing_status = PageInfo::SAFE_BROWSING_STATUS_MALWARE;
849
850 OpenPageInfoBubble(browser());
851 SetPageInfoBubbleIdentityInfo(identity);
852
853 views::BubbleDialogDelegateView* page_info =
854 PageInfoBubbleView::GetPageInfoBubbleForTesting();
855
856 // Verify bubble complains of malware...
857 EXPECT_EQ(page_info->GetWindowTitle(),
858 l10n_util::GetStringUTF16(IDS_PAGE_INFO_MALWARE_SUMMARY));
859
860 // ...and has the correct organization details in the Certificate button.
861 EXPECT_EQ(GetCertificateButtonSubtitle(),
862 l10n_util::GetStringFUTF16(
863 IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY_EV_VERIFIED,
864 base::UTF8ToUTF16("Thawte Inc"), base::UTF8ToUTF16("US")));
865 }
866
867 namespace {
868
869 // Tracks focus of an arbitrary UI element.
870 class FocusTracker {
871 public:
focused() const872 bool focused() const { return focused_; }
873
874 // Wait for focused() to be in state |target_state_is_focused|. If focused()
875 // is already in the desired state, returns immediately, otherwise waits until
876 // it is.
WaitForFocus(bool target_state_is_focused)877 void WaitForFocus(bool target_state_is_focused) {
878 if (focused_ == target_state_is_focused)
879 return;
880 target_state_is_focused_ = target_state_is_focused;
881 run_loop_.Run();
882 }
883
884 protected:
FocusTracker(bool initially_focused)885 explicit FocusTracker(bool initially_focused) : focused_(initially_focused) {}
~FocusTracker()886 virtual ~FocusTracker() {}
887
OnFocused()888 void OnFocused() {
889 focused_ = true;
890 if (run_loop_.running() && target_state_is_focused_ == focused_)
891 run_loop_.Quit();
892 }
893
OnBlurred()894 void OnBlurred() {
895 focused_ = false;
896 if (run_loop_.running() && target_state_is_focused_ == focused_)
897 run_loop_.Quit();
898 }
899
900 private:
901 // Whether the tracked visual element is currently focused.
902 bool focused_ = false;
903
904 // Desired state when waiting for focus to change.
905 bool target_state_is_focused_;
906
907 base::RunLoop run_loop_;
908
909 DISALLOW_COPY_AND_ASSIGN(FocusTracker);
910 };
911
912 // Watches a WebContents for focus changes.
913 class WebContentsFocusTracker : public FocusTracker,
914 public content::WebContentsObserver {
915 public:
WebContentsFocusTracker(content::WebContents * web_contents)916 explicit WebContentsFocusTracker(content::WebContents* web_contents)
917 : FocusTracker(IsWebContentsFocused(web_contents)),
918 WebContentsObserver(web_contents) {}
919
OnWebContentsFocused(content::RenderWidgetHost * render_widget_host)920 void OnWebContentsFocused(
921 content::RenderWidgetHost* render_widget_host) override {
922 OnFocused();
923 }
924
OnWebContentsLostFocus(content::RenderWidgetHost * render_widget_host)925 void OnWebContentsLostFocus(
926 content::RenderWidgetHost* render_widget_host) override {
927 OnBlurred();
928 }
929
930 private:
IsWebContentsFocused(content::WebContents * web_contents)931 static bool IsWebContentsFocused(content::WebContents* web_contents) {
932 Browser* const browser = chrome::FindBrowserWithWebContents(web_contents);
933 if (!browser)
934 return false;
935 if (browser->tab_strip_model()->GetActiveWebContents() != web_contents)
936 return false;
937 return BrowserView::GetBrowserViewForBrowser(browser)
938 ->contents_web_view()
939 ->HasFocus();
940 }
941 };
942
943 // Watches a View for focus changes.
944 class ViewFocusTracker : public FocusTracker, public views::ViewObserver {
945 public:
ViewFocusTracker(views::View * view)946 explicit ViewFocusTracker(views::View* view)
947 : FocusTracker(view->HasFocus()) {
948 scoped_observer_.Add(view);
949 }
950
OnViewFocused(views::View * observed_view)951 void OnViewFocused(views::View* observed_view) override { OnFocused(); }
952
OnViewBlurred(views::View * observed_view)953 void OnViewBlurred(views::View* observed_view) override { OnBlurred(); }
954
955 private:
956 ScopedObserver<views::View, views::ViewObserver> scoped_observer_{this};
957 };
958
959 } // namespace
960
961 #if defined(OS_MAC)
962 // https://crbug.com/1029882
963 #define MAYBE_FocusReturnsToContentOnClose DISABLED_FocusReturnsToContentOnClose
964 #else
965 #define MAYBE_FocusReturnsToContentOnClose FocusReturnsToContentOnClose
966 #endif
967
968 // Test that when the PageInfo bubble is closed, focus is returned to the web
969 // contents pane.
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,MAYBE_FocusReturnsToContentOnClose)970 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
971 MAYBE_FocusReturnsToContentOnClose) {
972 content::WebContents* const web_contents =
973 browser()->tab_strip_model()->GetActiveWebContents();
974 WebContentsFocusTracker web_contents_focus_tracker(web_contents);
975 web_contents->Focus();
976 web_contents_focus_tracker.WaitForFocus(true);
977
978 OpenPageInfoBubble(browser());
979 PageInfoBubbleView* page_info_bubble_view = static_cast<PageInfoBubbleView*>(
980 PageInfoBubbleView::GetPageInfoBubbleForTesting());
981 EXPECT_FALSE(web_contents_focus_tracker.focused());
982
983 page_info_bubble_view->GetWidget()->CloseWithReason(
984 views::Widget::ClosedReason::kEscKeyPressed);
985 web_contents_focus_tracker.WaitForFocus(true);
986 EXPECT_TRUE(web_contents_focus_tracker.focused());
987 }
988
989 #if defined(OS_MAC)
990 // https://crbug.com/1029882
991 #define MAYBE_FocusDoesNotReturnToContentsOnReloadPrompt \
992 DISABLED_FocusDoesNotReturnToContentsOnReloadPrompt
993 #else
994 #define MAYBE_FocusDoesNotReturnToContentsOnReloadPrompt \
995 FocusDoesNotReturnToContentsOnReloadPrompt
996 #endif
997
998 // Test that when the PageInfo bubble is closed and a reload prompt is
999 // displayed, focus is NOT returned to the web contents pane, but rather returns
1000 // to the location bar so accessibility users must tab through the reload prompt
1001 // before getting back to web contents (see https://crbug.com/910067).
IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,MAYBE_FocusDoesNotReturnToContentsOnReloadPrompt)1002 IN_PROC_BROWSER_TEST_F(PageInfoBubbleViewBrowserTest,
1003 MAYBE_FocusDoesNotReturnToContentsOnReloadPrompt) {
1004 content::WebContents* const web_contents =
1005 browser()->tab_strip_model()->GetActiveWebContents();
1006 WebContentsFocusTracker web_contents_focus_tracker(web_contents);
1007 ViewFocusTracker location_bar_focus_tracker(
1008 BrowserView::GetBrowserViewForBrowser(browser())->GetLocationBarView());
1009 web_contents->Focus();
1010 web_contents_focus_tracker.WaitForFocus(true);
1011
1012 OpenPageInfoBubble(browser());
1013 PageInfoBubbleView* page_info_bubble_view = static_cast<PageInfoBubbleView*>(
1014 PageInfoBubbleView::GetPageInfoBubbleForTesting());
1015 EXPECT_FALSE(web_contents_focus_tracker.focused());
1016
1017 TriggerReloadPromptOnClose();
1018 page_info_bubble_view->GetWidget()->CloseWithReason(
1019 views::Widget::ClosedReason::kEscKeyPressed);
1020 location_bar_focus_tracker.WaitForFocus(true);
1021 web_contents_focus_tracker.WaitForFocus(false);
1022 EXPECT_TRUE(location_bar_focus_tracker.focused());
1023 EXPECT_FALSE(web_contents_focus_tracker.focused());
1024 }
1025