910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/omnibox/chrome_omnibox_navigation_observer.h"

#include <array>
#include <unordered_map>
#include <vector>

#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory_test_util.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"

using network::TestURLLoaderFactory;

class ChromeOmniboxNavigationObserverTest
    : public ChromeRenderViewHostTestHarness {
 public:
  ChromeOmniboxNavigationObserverTest(
      const ChromeOmniboxNavigationObserverTest&) = delete;
  ChromeOmniboxNavigationObserverTest& operator=(
      const ChromeOmniboxNavigationObserverTest&) = delete;

 protected:
  ChromeOmniboxNavigationObserverTest() = default;
  ~ChromeOmniboxNavigationObserverTest() override = default;

  content::NavigationController* navigation_controller() {
    return &(web_contents()->GetController());
  }

  TemplateURLService* model() {
    return TemplateURLServiceFactory::GetForProfile(profile());
  }

  // Functions that return the name of certain search keywords that are part
  // of the TemplateURLService attached to this profile.
  static std::u16string auto_generated_search_keyword() {
    return u"auto_generated_search_keyword";
  }
  static std::u16string non_auto_generated_search_keyword() {
    return u"non_auto_generated_search_keyword";
  }
  static std::u16string default_search_keyword() {
    return u"default_search_keyword";
  }
  static std::u16string prepopulated_search_keyword() {
    return u"prepopulated_search_keyword";
  }
  static std::u16string policy_search_keyword() {
    return u"policy_search_keyword";
  }
  static std::u16string starter_pack_keyword() {
    return u"starter_pack_keyword";
  }

 private:
  // ChromeRenderViewHostTestHarness:
  void SetUp() override;
};

void ChromeOmniboxNavigationObserverTest::SetUp() {
  ChromeRenderViewHostTestHarness::SetUp();
  infobars::ContentInfoBarManager::CreateForWebContents(web_contents());

  // Set up a series of search engines for later testing.
  TemplateURLServiceFactoryTestUtil factory_util(profile());
  factory_util.VerifyLoad();

  TemplateURLData auto_gen_turl;
  auto_gen_turl.SetKeyword(auto_generated_search_keyword());
  auto_gen_turl.safe_for_autoreplace = true;
  factory_util.model()->Add(std::make_unique<TemplateURL>(auto_gen_turl));

  TemplateURLData non_auto_gen_turl;
  non_auto_gen_turl.SetKeyword(non_auto_generated_search_keyword());
  factory_util.model()->Add(std::make_unique<TemplateURL>(non_auto_gen_turl));

  TemplateURLData default_turl;
  default_turl.SetKeyword(default_search_keyword());
  factory_util.model()->SetUserSelectedDefaultSearchProvider(
      factory_util.model()->Add(std::make_unique<TemplateURL>(default_turl)));

  TemplateURLData prepopulated_turl;
  prepopulated_turl.SetKeyword(prepopulated_search_keyword());
  prepopulated_turl.prepopulate_id = 1;
  factory_util.model()->Add(std::make_unique<TemplateURL>(prepopulated_turl));

  TemplateURLData policy_turl;
  policy_turl.SetKeyword(policy_search_keyword());
  policy_turl.policy_origin =
      TemplateURLData::PolicyOrigin::kDefaultSearchProvider;
  factory_util.model()->Add(std::make_unique<TemplateURL>(policy_turl));

  TemplateURLData starter_pack_turl;
  starter_pack_turl.SetKeyword(starter_pack_keyword());
  starter_pack_turl.starter_pack_id = 1;
  factory_util.model()->Add(std::make_unique<TemplateURL>(starter_pack_turl));
}

namespace {

scoped_refptr<net::HttpResponseHeaders> GetHeadersForResponseCode(int code) {
  if (code == 200) {
    return base::MakeRefCounted<net::HttpResponseHeaders>(
        "HTTP/1.1 200 OK\r\n");
  } else if (code == 404) {
    return base::MakeRefCounted<net::HttpResponseHeaders>(
        "HTTP/1.1 404 Not Found\r\n");
  }
  NOTREACHED();
}

void WriteMojoMessage(const mojo::ScopedDataPipeProducerHandle& handle,
                      std::string message) {
  size_t actually_written_bytes = 0;
  ASSERT_EQ(MOJO_RESULT_OK, handle->WriteData(base::as_byte_span(message),
                                              MOJO_WRITE_DATA_FLAG_NONE,
                                              actually_written_bytes));
  ASSERT_EQ(message.size(), actually_written_bytes);
}

}  // namespace

TEST_F(ChromeOmniboxNavigationObserverTest, DeleteBrokenCustomSearchEngines) {
  // The actual URL doesn't matter for this test as long as it's valid.
  struct TestData {
    std::u16string keyword;
    int status_code;
    bool expect_exists;
  };
  std::vector<TestData> cases = {
      {auto_generated_search_keyword(), 200, true},
      {auto_generated_search_keyword(), 404, false},
      {non_auto_generated_search_keyword(), 404, true},
      {default_search_keyword(), 404, true},
      {prepopulated_search_keyword(), 404, true},
      {policy_search_keyword(), 404, true},
      {starter_pack_keyword(), 404, true}};

  std::u16string query = u" text";
  for (size_t i = 0; i < cases.size(); ++i) {
    SCOPED_TRACE("case #" + base::NumberToString(i));
    // The keyword should always exist at the beginning.
    EXPECT_TRUE(model()->GetTemplateURLForKeyword(cases[i].keyword));

    AutocompleteMatch match;
    match.keyword = cases[i].keyword;
    // Append the case number to the URL to ensure that we are loading a new
    // page.
    auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
        GURL(base::StringPrintf("https://foo.com/%zu", i)), web_contents());
    navigation->SetResponseHeaders(
        GetHeadersForResponseCode(cases[i].status_code));

    // HTTPErrorNavigationThrottle checks whether body is null or not to decide
    // whether to commit an error page or a regular one.
    mojo::ScopedDataPipeProducerHandle producer_handle;
    mojo::ScopedDataPipeConsumerHandle consumer_handle;
    ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle),
              MOJO_RESULT_OK);
    navigation->SetResponseBody(std::move(consumer_handle));
    WriteMojoMessage(producer_handle, "data");

    navigation->Start();
    ChromeOmniboxNavigationObserver::CreateForTesting(
        navigation->GetNavigationHandle(), profile(), cases[i].keyword + query,
        match, AutocompleteMatch(), nullptr, base::DoNothing());

    navigation->Commit();

    EXPECT_EQ(cases[i].expect_exists,
              model()->GetTemplateURLForKeyword(cases[i].keyword) != nullptr);
  }
}

TEST_F(ChromeOmniboxNavigationObserverTest, AlternateNavInfoBar) {
  TestURLLoaderFactory test_url_loader_factory;
  scoped_refptr<network::SharedURLLoaderFactory> shared_factory =
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          &test_url_loader_factory);

  const int kNetError = net::ERR_FAILED;
  const int kNoResponse = -1;
  struct Response {
    const std::vector<std::string> urls;  // If more than one, 301 between them.
    // The final status code to return after all the redirects, or one of
    // kNetError or kNoResponse.
    const int http_response_code;
    std::string content;
  };

  // All of these test cases assume the alternate nav URL is http://example/.
  struct Case {
    const Response response;
    const bool expected_alternate_nav_bar_shown;
  };
  auto cases = std::to_array<Case>({
      // The only response provided is a net error.
      {{{"http://example/"}, kNetError}, false},
      // The response connected to a valid page.
      {{{"http://example/"}, 200}, true},
      // A non-empty page, despite the HEAD.
      {{{"http://example/"}, 200, "Content"}, true},
      // The response connected to an error page.
      {{{"http://example/"}, 404}, false},

      // The response redirected to same host, just http->https, with a path
      // change as well.  In this case the second URL should not fetched; Chrome
      // will optimistically assume the destination will return a valid page and
      // display the infobar.
      {{{"http://example/", "https://example/path"}, kNoResponse}, true},
      // Ditto, making sure it still holds when the final destination URL
      // returns a valid status code.
      {{{"http://example/", "https://example/path"}, 200}, true},

      // The response redirected to an entirely different host.  In these cases,
      // no URL should be fetched against this second host; again Chrome will
      // optimistically assume the destination will return a valid page and
      // display the infobar.
      {{{"http://example/", "http://new-destination/"}, kNoResponse}, true},
      // Ditto, making sure it still holds when the final destination URL
      // returns a valid status code.
      {{{"http://example/", "http://new-destination/"}, 200}, true},

      // The response redirected to same host, just http->https, with no other
      // changes.  In these cases, Chrome will fetch the second URL.
      // The second URL response returned a valid page.
      {{{"http://example/", "https://example/"}, 200}, true},
      // The second URL response returned an error page.
      {{{"http://example/", "https://example/"}, 404}, false},
      // The second URL response returned a net error.
      {{{"http://example/", "https://example/"}, kNetError}, false},
      // The second URL response redirected again.
      {{{"http://example/", "https://example/", "https://example/root"},
        kNoResponse},
       true},
  });
  for (size_t i = 0; i < std::size(cases); ++i) {
    SCOPED_TRACE("case #" + base::NumberToString(i));
    const Case& test_case = cases[i];
    const Response& response = test_case.response;

    // Set the URL request responses.
    test_url_loader_factory.ClearResponses();

    // Compute URL redirect chain.
    TestURLLoaderFactory::Redirects redirects;
    for (size_t dest = 1; dest < response.urls.size(); ++dest) {
      net::RedirectInfo redir_info;
      redir_info.new_url = GURL(response.urls[dest]);
      redir_info.status_code = net::HTTP_MOVED_PERMANENTLY;
      auto redir_head =
          network::CreateURLResponseHead(net::HTTP_MOVED_PERMANENTLY);
      redirects.emplace_back(redir_info, std::move(redir_head));
    }

    // Fill in final response.
    network::mojom::URLResponseHeadPtr http_head =
        network::mojom::URLResponseHead::New();
    network::URLLoaderCompletionStatus net_status;
    if (response.http_response_code == kNetError) {
      net_status = network::URLLoaderCompletionStatus(net::ERR_FAILED);
    } else if (response.http_response_code != kNoResponse) {
      net_status = network::URLLoaderCompletionStatus(net::OK);
      http_head = network::CreateURLResponseHead(
          static_cast<net::HttpStatusCode>(response.http_response_code));
    }

    test_url_loader_factory.AddResponse(GURL(response.urls[0]),
                                        std::move(http_head), response.content,
                                        net_status, std::move(redirects));

    // Create the alternate nav match and the observer.
    // |observer| gets deleted automatically after all fetchers complete.
    AutocompleteMatch alternate_nav_match;
    alternate_nav_match.destination_url = GURL("http://example/");
    auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
        GURL(base::StringPrintf("https://foo.com/%zu", i)), web_contents());
    bool displayed_infobar = false;

    navigation->Start();
    ChromeOmniboxNavigationObserver::CreateForTesting(
        navigation->GetNavigationHandle(), profile(), u"example",
        AutocompleteMatch(), alternate_nav_match, shared_factory.get(),
        base::BindLambdaForTesting(
            [&](ChromeOmniboxNavigationObserver* observer) {
              displayed_infobar = true;
            }));

    // Make sure the fetcher(s) have finished.
    base::RunLoop().RunUntilIdle();

    if (test_case.response.http_response_code != kNetError) {
      navigation->Commit();
    } else {
      navigation->Fail(kNetError);
      navigation->CommitErrorPage();
    }

    // See if AlternateNavInfoBarDelegate::Create() was called.
    EXPECT_EQ(test_case.expected_alternate_nav_bar_shown, displayed_infobar);
  }
}