chromium/ios/chrome/browser/safe_browsing/model/safe_browsing_egtest.mm

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <string>

#import "base/strings/string_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_storage_type.h"
#import "ios/chrome/browser/bookmarks/ui_bundled/bookmark_earl_grey.h"
#import "ios/chrome/browser/bookmarks/ui_bundled/bookmark_earl_grey_ui.h"
#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_constants.h"
#import "ios/chrome/browser/ui/settings/privacy/privacy_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/web/common/features.h"
#import "ios/web/public/test/element_selector.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"
#import "ui/base/l10n/l10n_util.h"

using chrome_test_util::BackButton;
using chrome_test_util::ForwardButton;
using chrome_test_util::SettingsDoneButton;
using chrome_test_util::TappableBookmarkNodeWithLabel;

namespace {

// Text that is found when expanding details on the phishing warning page.
const char kPhishingWarningDetails[] =
    "Google Safe Browsing, which recently found phishing";

// Text that is found when expanding details on the malware warning page.
const char kMalwareWarningDetails[] =
    "Google Safe Browsing, which recently found malware";

// Request handler for net::EmbeddedTestServer that returns the request URL's
// path as the body of the response if the request URL's path starts with
// "/echo". Otherwise, returns nulltpr to allow other handlers to handle the
// request.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
    const net::test_server::HttpRequest& request) {
  if (!base::StartsWith(request.relative_url, "/echo",
                        base::CompareCase::SENSITIVE)) {
    return nullptr;
  }
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_content(request.relative_url);
  http_response->set_content_type("text/html");
  return http_response;
}

// Earl Grey matcher for the Enhanced Safe Browsing Infobar.
id<GREYMatcher> EnhancedSafeBrowsingInfobarButtonMatcher() {
  NSString* buttonLabel = l10n_util::GetNSString(
      IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_BUTTON_TEXT);
  return grey_allOf(grey_accessibilityID(kInfobarBannerAcceptButtonIdentifier),
                    grey_accessibilityLabel(buttonLabel), nil);
}

}  // namespace

// Tests Safe Browsing URL blocking.
@interface SafeBrowsingTestCase : ChromeTestCase {
  // A URL that is treated as an unsafe phishing page.
  GURL _phishingURL;
  // Text that is found on the phishing page.
  std::string _phishingContent;
  // A URL that is treated as an unsafe phishing page by real-time lookups.
  GURL _realTimePhishingURL;
  // Text that is found on the real-time phishing page.
  std::string _realTimePhishingContent;
  // A URL that is treated as an unsafe malware page.
  GURL _malwareURL;
  // Text that is found on the malware page.
  std::string _malwareContent;
  // A URL of a page with an iframe that is treated as a phishing page.
  GURL _iframeWithPhishingURL;
  // Text that is found on the iframe that is treated as a phishing page.
  std::string _iframeWithPhishingContent;
  // A URL that is treated as a safe page.
  GURL _safeURL1;
  // Text that is found on the safe page.
  std::string _safeContent1;
  // Another URL that is treated as a safe page.
  GURL _safeURL2;
  // Text that is found on the safe page.
  std::string _safeContent2;
  // The default value for SafeBrowsingEnabled pref.
  BOOL _safeBrowsingEnabledPrefDefault;
  // The default value for SafeBrowsingEnhanced pref.
  BOOL _safeBrowsingEnhancedPrefDefault;
  // The default value for SafeBrowsingProceedAnywayDisabled pref.
  BOOL _proceedAnywayDisabledPrefDefault;
}
@end

@implementation SafeBrowsingTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;

  // Use commandline args to insert fake unsafe URLs into the Safe Browsing
  // database.
  config.additional_args.push_back(std::string("--mark_as_phishing=") +
                                   _phishingURL.spec());
  config.additional_args.push_back(std::string("--mark_as_malware=") +
                                   _malwareURL.spec());
  config.additional_args.push_back(
      std::string("--mark_as_hash_prefix_real_time_phishing=") +
      _phishingURL.spec());

  // Disable HPRT for malware URL related tests since artificial verdict caching
  // for HPRT marks a URL for phishing which breaks tests. Additionally, this
  // promotes continued test coverage for the non-HPRT logic path.
  if ([self
          isRunningTest:@selector(testRestoreToWarningPagePreservesHistory)] ||
      [self isRunningTest:@selector(testMalwarePage)] ||
      [self isRunningTest:@selector(testProceedingPastMalwareWarning)]) {
    config.additional_args.push_back(std::string(
        "--disable-features=SafeBrowsingHashPrefixRealTimeLookups"));
  } else {
    config.additional_args.push_back(
        std::string("--enable-features=SafeBrowsingHashPrefixRealTimeLookups"));
  }

  config.additional_args.push_back(
      std::string("--mark_as_real_time_phishing=") +
      _realTimePhishingURL.spec());
  config.additional_args.push_back(
      std::string("--mark_as_allowlisted_for_real_time=") + _safeURL1.spec());
  config.relaunch_policy = NoForceRelaunchAndResetState;
  return config;
}

- (void)setUp {
  self.testServer->RegisterRequestHandler(base::BindRepeating(&HandleRequest));
  bool started = self.testServer->Start();
  _phishingURL = self.testServer->GetURL("/echo_phishing_page");
  _phishingContent = "phishing_page";

  _realTimePhishingURL = self.testServer->GetURL("/echo_realtime_page");
  _realTimePhishingContent = "realtime_page";

  _malwareURL = self.testServer->GetURL("/echo_malware_page");
  _malwareContent = "malware_page";

  _iframeWithPhishingURL =
      self.testServer->GetURL("/iframe?" + _phishingURL.spec());
  _iframeWithPhishingContent = _phishingContent;

  _safeURL1 = self.testServer->GetURL("/echo_safe_page");
  _safeContent1 = "safe_page";

  _safeURL2 = self.testServer->GetURL("/echo_also_safe");
  _safeContent2 = "also_safe";

  // Artificial verdict caching for hash prefix real time causes URLs with the
  // same host to be seen as unsafe. Replacing the host string with localhost
  // allows for proper testing between safe browsing v5 and iframe queries.
  GURL::Replacements replacements;
  replacements.SetHostStr("localhost");
  _safeURL1 = _safeURL1.ReplaceComponents(replacements);
  _safeURL2 = _safeURL2.ReplaceComponents(replacements);
  _iframeWithPhishingURL =
      _iframeWithPhishingURL.ReplaceComponents(replacements);

  // `appConfigurationForTestCase` is called during [super setUp], and
  // depends on the URLs initialized above.
  [super setUp];

  // GREYAssertTrue cannot be called before [super setUp].
  GREYAssertTrue(started, @"Test server failed to start.");

  // Save the existing value of the pref to set it back in tearDown.
  _safeBrowsingEnabledPrefDefault =
      [ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnabled];
  // Ensure that Safe Browsing opt-out starts in its default (opted-out) state.
  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kSafeBrowsingEnabled];

  // Save the existing value of the pref to set it back in tearDown.
  _safeBrowsingEnhancedPrefDefault =
      [ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnhanced];
  // Ensure that Enhanced Safe Browsing opt-out starts in its default (opted-in)
  // state.
  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kSafeBrowsingEnhanced];

  // Save the existing value of the pref to set it back in tearDown.
  _proceedAnywayDisabledPrefDefault = [ChromeEarlGrey
      userBooleanPref:prefs::kSafeBrowsingProceedAnywayDisabled];
  // Ensure that Proceed link is shown by default in the safe browsing warning.
  [ChromeEarlGrey setBoolValue:NO
                   forUserPref:prefs::kSafeBrowsingProceedAnywayDisabled];

  // Ensure that the real-time Safe Browsing opt-in starts in the default
  // (opted-out) state.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:NO];
}

- (void)tearDown {
  // Ensure that Safe Browsing is reset to its original value.
  [ChromeEarlGrey setBoolValue:_safeBrowsingEnabledPrefDefault
                   forUserPref:prefs::kSafeBrowsingEnabled];

  // Ensure that Enhanced Safe Browsing is reset to its original value.
  [ChromeEarlGrey setBoolValue:_safeBrowsingEnhancedPrefDefault
                   forUserPref:prefs::kSafeBrowsingEnhanced];

  // Ensure that Proceed link is reset to its original value.
  [ChromeEarlGrey setBoolValue:_proceedAnywayDisabledPrefDefault
                   forUserPref:prefs::kSafeBrowsingProceedAnywayDisabled];

  // Ensure that the real-time Safe Browsing opt-in is reset to its original
  // value.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:NO];

  [super tearDown];
}

#pragma mark - Helper methods

// Instantiates an ElementSelector to detect the enhanced protection message on
// interstitial page.
- (ElementSelector*)enhancedProtectionMessage {
  NSString* selector =
      @"(function() {"
       "  var element = document.getElementById('enhanced-protection-message');"
       "  if (element == null) return false;"
       "  if (element.classList.contains('hidden')) return false;"
       "  return true;"
       "})()";
  NSString* description = @"Enhanced Safe Browsing message.";
  return [ElementSelector selectorWithScript:selector
                         selectorDescription:description];
}

#pragma mark - Tests
// Tests that safe pages are not blocked.
- (void)testSafePage {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
}

// Tests that a phishing page is blocked, and the "Back to safety" button on
// the warning page works as expected.
- (void)testPhishingPage {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the phishing page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the "Back to safety" button and verify that the previous page's
  // contents are loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"primary-button"];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
}

// Tests expanding the details on a phishing warning, and proceeding past the
// warning. Also verifies that a warning is still shown when visiting the unsafe
// URL in a new tab.
- (void)testProceedingPastPhishingWarning {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the phishing page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the Details button and verify that warning details are shown.
  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kPhishingWarningDetails];

  // Tap on the link to proceed to the unsafe page, and verify that this page is
  // loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
  [ChromeEarlGrey waitForWebStateContainingText:_phishingContent];

  // In a new tab, a warning should still be shown, even though the user
  // proceeded to the unsafe content in the other tab.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
}

// Tests expanding the details on a phishing warning, and proceeding past the
// warning in incognito mode. Also verifies that a warning is still shown when
// visiting the unsafe URL in a new incognito tab.
- (void)testProceedingPastPhishingWarningInIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the phishing page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the Details button and verify that warning details are shown.
  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kPhishingWarningDetails];

  // Tap on the link to proceed to the unsafe page, and verify that this page is
  // loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
  [ChromeEarlGrey waitForWebStateContainingText:_phishingContent];

  // In a new tab, a warning should still be shown, even though the user
  // proceeded to the unsafe content in the other tab.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
}

// Tests that a malware page is blocked, and the "Back to safety" button on the
// warning page works as expected.
- (void)testMalwarePage {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_malwareURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the "Back to safety" button and verify that the previous page's
  // contents are loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"primary-button"];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
}

// Tests expanding the details on a malware warning, proceeding past the
// warning, and navigating back/forward to the unsafe page.
- (void)testProceedingPastMalwareWarning {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_malwareURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the Details button and verify that warning details are shown.
  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kMalwareWarningDetails];

  if (@available(iOS 15.1, *)) {
  } else {
    // Workaround https://bugs.webkit.org/show_bug.cgi?id=226323, which can
    // break loading the unsafe page below.
    return;
  }

  // Tap on the link to proceed to the unsafe page, and verify that this page is
  // loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
  [ChromeEarlGrey waitForWebStateFrameContainingText:_malwareContent];

  // Verify that no warning is shown when navigating back and then forward to
  // the unsafe page.
  [ChromeEarlGrey goBack];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
  [ChromeEarlGrey goForward];
  [ChromeEarlGrey waitForWebStateContainingText:_malwareContent];

  // Visit another safe page, and then navigate back to the unsafe page and
  // verify that no warning is shown.
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  [ChromeEarlGrey goBack];
  [ChromeEarlGrey waitForWebStateContainingText:_malwareContent];
}

// Tests expanding the details on a malware warning, proceeding past the
// warning, and navigating back/forward to the unsafe page, in incognito mode.
- (void)testProceedingPastMalwareWarningInIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_malwareURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the Details button and verify that warning details are shown.
  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kMalwareWarningDetails];

  if (@available(iOS 15.1, *)) {
  } else {
    // Workaround https://bugs.webkit.org/show_bug.cgi?id=226323, which can
    // break loading the unsafe page below.
    return;
  }

  // Tap on the link to proceed to the unsafe page, and verify that this page is
  // loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
  [ChromeEarlGrey waitForWebStateFrameContainingText:_malwareContent];

  // Verify that no warning is shown when navigating back and then forward to
  // the unsafe page.
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
  [[EarlGrey selectElementWithMatcher:ForwardButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:_malwareContent];

  // Visit another safe page, and then navigate back to the unsafe page and
  // verify that no warning is shown.
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:_malwareContent];
}

// Tests that disabling and re-enabling Safe Browsing works as expected.
- (void)testDisableAndEnableSafeBrowsing {
  // Disable Safe Browsing and verify that unsafe content is shown.
  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kSafeBrowsingEnabled];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:_phishingContent];

  // Re-enable Safe Browsing and verify that a warning is shown for unsafe
  // content.
  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kSafeBrowsingEnabled];
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
}

// Tests enabling Enhanced Protection from a Standard Protection state (Default
// state) from the interstitial blocking page.
- (void)testDisableAndEnableEnhancedSafeBrowsing {
  BOOL isInfobarEnabled = [ChromeEarlGrey isEnhancedSafeBrowsingInfobarEnabled];
  // Disable Enhanced Safe Browsing and verify that a dark red box prompting to
  // turn on Enhanced Protection is visible.
  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kSafeBrowsingEnhanced];
  ElementSelector* enhancedSafeBrowsingMessage =
      [self enhancedProtectionMessage];

  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
  [ChromeEarlGrey loadURL:_phishingURL];
  if (isInfobarEnabled) {
    [ChromeEarlGrey waitForMatcher:EnhancedSafeBrowsingInfobarButtonMatcher()];
  } else {
    [ChromeEarlGrey
        waitForWebStateContainingElement:enhancedSafeBrowsingMessage];
  }

  // Re-enable Enhanced Safe Browsing and verify that a dark red box prompting
  // to turn on Enhanced Protection is not visible.
  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kSafeBrowsingEnhanced];
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
  if (isInfobarEnabled) {
    [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                        EnhancedSafeBrowsingInfobarButtonMatcher()];
  } else {
    [ChromeEarlGrey
        waitForWebStateNotContainingElement:enhancedSafeBrowsingMessage];
  }
}

- (void)testEnhancedSafeBrowsingLink {
  BOOL isInfobarEnabled = [ChromeEarlGrey isEnhancedSafeBrowsingInfobarEnabled];
  // Disable Enhanced Safe Browsing and verify that a dark red box prompting to
  // turn on Enhanced Protection is visible.
  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kSafeBrowsingEnhanced];
  ElementSelector* enhancedSafeBrowsingMessage =
      [self enhancedProtectionMessage];

  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];
  [ChromeEarlGrey loadURL:_phishingURL];
  if (isInfobarEnabled) {
    [[EarlGrey
        selectElementWithMatcher:EnhancedSafeBrowsingInfobarButtonMatcher()]
        performAction:grey_tap()];
  } else {
    [ChromeEarlGrey
        waitForWebStateContainingElement:enhancedSafeBrowsingMessage];
    [ChromeEarlGrey tapWebStateElementWithID:@"enhanced-protection-link"];
  }

  [[EarlGrey
      selectElementWithMatcher:
          grey_accessibilityID(kSettingsSafeBrowsingEnhancedProtectionCellId)]
      performAction:grey_tap()];
  GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnhanced],
                 @"Failed to toggle-on Enhanced Safe Browsing");
  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
      performAction:grey_tap()];

  // Verify that a dark red box prompting to turn on Enhanced Protection is not
  // visible.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
  if (isInfobarEnabled) {
    [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                        EnhancedSafeBrowsingInfobarButtonMatcher()];
  } else {
    [ChromeEarlGrey
        waitForWebStateNotContainingElement:enhancedSafeBrowsingMessage];
  }
}

// Tests displaying a warning for an unsafe page in incognito mode, and
// proceeding past the warning.
- (void)testWarningInIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the phishing page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the Details button and verify that warning details are shown.
  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kPhishingWarningDetails];

  // Tap on the link to proceed to the unsafe page, and verify that this page is
  // loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
  [ChromeEarlGrey waitForWebStateContainingText:_phishingContent];
}

// Tests that the proceed option is not shown when
// kSafeBrowsingProceedAnywayDisabled is enabled.
- (void)testProceedAlwaysDisabled {
  // Enable the pref.
  [ChromeEarlGrey setBoolValue:YES
                   forUserPref:prefs::kSafeBrowsingProceedAnywayDisabled];

  // Load the a malware safe browsing error page.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:"Dangerous site"];

  [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
  [ChromeEarlGrey waitForWebStateContainingText:kPhishingWarningDetails];

  // Verify that the proceed-link element is not found.  When the proceed link
  // is disabled, the entire second paragraph hidden.
  NSString* selector =
      @"(function() {"
       "  var element = document.getElementById('final-paragraph');"
       "  if (element.classList.contains('hidden')) return true;"
       "  return false;"
       "})()";
  NSString* description = @"Hidden proceed-anyway link.";
  ElementSelector* proceedLink =
      [ElementSelector selectorWithScript:selector
                      selectorDescription:description];
  GREYAssert(
      [ChromeEarlGrey webStateContainsElement:proceedLink],
      @"Proceed anyway link shown despite kSafeBrowsingProceedAnywayDisabled");
}

// Tests performing a back navigation to a warning page and a forward navigation
// from a warning page.
- (void)testBackForwardNavigationWithWarning {
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_phishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  // TODO(crbug.com/40159013): Adding a delay to avoid never-ending load on the
  // last navigation forward. Should be fixed in newer iOS version.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1));

  [ChromeEarlGrey goBack];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
  // TODO(crbug.com/40159013): Adding a delay to avoid never-ending load on the
  // last navigation forward. Should be fixed in newer iOS version.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1));

  [ChromeEarlGrey goForward];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
}

// Tests performing a back navigation to a warning page and a forward navigation
// from a warning page, in incognito mode.
- (void)testBackForwardNavigationWithWarningInIncognito {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_malwareURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];

  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  [[EarlGrey selectElementWithMatcher:ForwardButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
}

// Tests that performing session restoration to a Safe Browsing warning page
// preserves navigation history.
// TODO(crbug.com/41489568):  Test is flaky on device. Re-enable the test.
- (void)testRestoreToWarningPagePreservesHistory {
  // Build up navigation history that consists of a safe URL, a warning page,
  // and another safe URL.
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load the malware page and verify a warning is shown.
  [ChromeEarlGrey loadURL:_malwareURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Tap on the "Back to safety" button and verify that the previous page's
  // contents are loaded.
  [ChromeEarlGrey tapWebStateElementWithID:@"primary-button"];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  [ChromeEarlGrey goForward];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];

  // Navigate back so that both the back list and the forward list are
  // non-empty.
  [ChromeEarlGrey goBack];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Do a session restoration and verify that all navigation history is
  // preserved.
  [self triggerRestoreByRestartingApplication];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  [ChromeEarlGrey goBack];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  [ChromeEarlGrey goForward];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  [ChromeEarlGrey goForward];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
}

// Tests that a page with an unsafe ifame is not blocked.
- (void)testPageWithUnsafeIframeSkipSubresources {
  [ChromeEarlGrey loadURL:_safeURL2];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent2];
  [ChromeEarlGrey loadURL:_safeURL1];
  [ChromeEarlGrey waitForWebStateContainingText:_safeContent1];

  // Load a page that has an iframe with malware, and verify that a warning is
  // not shown.
  [ChromeEarlGrey loadURL:_iframeWithPhishingURL];
  [ChromeEarlGrey
      waitForWebStateFrameContainingText:_iframeWithPhishingContent];
}

// Tests that real-time lookups are not performed when opted-out of real-time
// lookups.
- (void)testRealTimeLookupsWhileOptedOut {
  // Load the real-time phishing page and verify that no warning is shown.
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:_realTimePhishingContent];
}

// Tests that real-time lookups are not performed when opted-out of Safe
// Browsing, regardless of the state of the real-time opt-in.
- (void)testRealTimeLookupsWhileOptedOutOfSafeBrowsing {
  // Opt out of Safe Browsing.
  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kSafeBrowsingEnabled];

  // Load the real-time phishing page and verify that no warning is shown.
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:_realTimePhishingContent];

  // Opt-in to real-time checks and verify that it's still the case that no
  // warning is shown.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:YES];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:_realTimePhishingContent];
}

// Tests that a page identified as unsafe by real-time Safe Browsing is blocked
// when opted-in to real-time lookups.
- (void)testRealTimeLookupsWhileOptedIn {
  // Opt-in to real-time checks.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:YES];

  // Load the real-time phishing page and verify that a warning page is shown.
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];
}

// Tests that real-time lookups are not performed in incognito mode.
- (void)testRealTimeLookupsInIncognito {
  // Opt-in to real-time checks.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:YES];

  // Load the real-time phishing page and verify that no warning is shown.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_realTimePhishingURL];
  [ChromeEarlGrey waitForWebStateContainingText:_realTimePhishingContent];
}

// Tests that when a page identified as unsafe by real-time Safe Browsing is
// loaded using a bookmark, a warning is shown.
- (void)testRealTimeWarningForBookmark {
  NSString* phishingTitle = @"Real-time phishing";
  [BookmarkEarlGrey
      addBookmarkWithTitle:phishingTitle
                       URL:base::SysUTF8ToNSString(_realTimePhishingURL.spec())
                 inStorage:BookmarkStorageType::kLocalOrSyncable];
  // Opt-in to real-time checks.
  [ChromeEarlGrey setURLKeyedAnonymizedDataCollectionEnabled:YES];

  // Load the real-time phishing page using its bookmark, and verify that a
  // warning is shown.
  [BookmarkEarlGreyUI openBookmarks];
  [BookmarkEarlGreyUI openMobileBookmarks];
  [[EarlGrey
      selectElementWithMatcher:TappableBookmarkNodeWithLabel(phishingTitle)]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:l10n_util::GetStringUTF8(
                                                    IDS_SAFEBROWSING_HEADING)];

  // Remove bookmarked phishing site.
  [BookmarkEarlGrey clearBookmarks];
}

@end