1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
// 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 <unordered_map>
#include <vector>
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.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() {}
~ChromeOmniboxNavigationObserverTest() override {}
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";
}
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.created_by_policy = true;
factory_util.model()->Add(std::make_unique<TemplateURL>(policy_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();
return nullptr;
}
void WriteMojoMessage(const mojo::ScopedDataPipeProducerHandle& handle,
const char* message) {
uint32_t num_bytes = strlen(message);
ASSERT_EQ(MOJO_RESULT_OK,
handle->WriteData(message, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE));
}
} // 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}};
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;
} cases[] = {
// 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.push_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);
}
}
|