Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(41)

Side by Side Diff: chrome/browser/net/predictor_browsertest.cc

Issue 1881463003: Add a browsertest suite for net predictor (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: mmenke@ review Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <stddef.h> 5 #include <stddef.h>
6 #include <stdint.h> 6 #include <stdint.h>
7 7
8 #include "base/base64.h" 8 #include "base/base64.h"
9 #include "base/command_line.h" 9 #include "base/command_line.h"
10 #include "base/json/json_string_value_serializer.h" 10 #include "base/json/json_string_value_serializer.h"
11 #include "base/macros.h" 11 #include "base/macros.h"
12 #include "base/synchronization/lock.h" 12 #include "base/synchronization/lock.h"
13 #include "base/thread_task_runner_handle.h" 13 #include "base/thread_task_runner_handle.h"
14 #include "build/build_config.h" 14 #include "build/build_config.h"
15 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/net/predictor.h" 16 #include "chrome/browser/net/predictor.h"
17 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser.h" 18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/common/pref_names.h" 20 #include "chrome/common/pref_names.h"
20 #include "chrome/test/base/in_process_browser_test.h" 21 #include "chrome/test/base/in_process_browser_test.h"
21 #include "chrome/test/base/ui_test_utils.h" 22 #include "chrome/test/base/ui_test_utils.h"
22 #include "components/prefs/pref_service.h" 23 #include "components/prefs/pref_service.h"
23 #include "content/public/common/content_switches.h" 24 #include "content/public/common/content_switches.h"
25 #include "content/public/test/browser_test_utils.h"
24 #include "content/public/test/test_utils.h" 26 #include "content/public/test/test_utils.h"
25 #include "net/base/host_port_pair.h" 27 #include "net/base/host_port_pair.h"
26 #include "net/base/ip_endpoint.h" 28 #include "net/base/ip_endpoint.h"
27 #include "net/base/net_errors.h" 29 #include "net/base/net_errors.h"
28 #include "net/dns/host_resolver_proc.h" 30 #include "net/dns/host_resolver_proc.h"
29 #include "net/dns/mock_host_resolver.h" 31 #include "net/dns/mock_host_resolver.h"
30 #include "net/socket/stream_socket.h" 32 #include "net/socket/stream_socket.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h" 33 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "net/test/embedded_test_server/embedded_test_server_connection_listener .h" 34 #include "net/test/embedded_test_server/embedded_test_server_connection_listener .h"
35 #include "net/test/embedded_test_server/http_request.h"
36 #include "net/test/embedded_test_server/http_response.h"
37 #include "net/test/url_request/url_request_failed_job.h"
38 #include "net/url_request/url_request_filter.h"
39 #include "net/url_request/url_request_interceptor.h"
33 #include "testing/gmock/include/gmock/gmock.h" 40 #include "testing/gmock/include/gmock/gmock.h"
34 41
35 using content::BrowserThread; 42 using content::BrowserThread;
36 using testing::HasSubstr; 43 using testing::HasSubstr;
37 44
38 namespace { 45 namespace {
39 46
40 const char kBlinkPreconnectFeature[] = "LinkPreconnect"; 47 const char kBlinkPreconnectFeature[] = "LinkPreconnect";
41 const char kChromiumHostname[] = "chromium.org"; 48 const char kChromiumHostname[] = "chromium.org";
42 const char kInvalidLongHostname[] = "illegally-long-hostname-over-255-" 49 const char kInvalidLongHostname[] = "illegally-long-hostname-over-255-"
(...skipping 21 matching lines...) Expand all
64 base::AutoLock lock(lock_); 71 base::AutoLock lock(lock_);
65 uint16_t socket = GetPort(connection); 72 uint16_t socket = GetPort(connection);
66 EXPECT_TRUE(sockets_.find(socket) == sockets_.end()); 73 EXPECT_TRUE(sockets_.find(socket) == sockets_.end());
67 74
68 sockets_[socket] = SOCKET_ACCEPTED; 75 sockets_[socket] = SOCKET_ACCEPTED;
69 task_runner_->PostTask(FROM_HERE, accept_loop_.QuitClosure()); 76 task_runner_->PostTask(FROM_HERE, accept_loop_.QuitClosure());
70 } 77 }
71 78
72 // Get called from the EmbeddedTestServer thread to be notified that 79 // Get called from the EmbeddedTestServer thread to be notified that
73 // a connection was read from. 80 // a connection was read from.
74 void ReadFromSocket(const net::StreamSocket& connection) override { 81 void ReadFromSocket(const net::StreamSocket& connection, int rv) override {
82 // Don't log a read if no data was transferred. This case often happens if
83 // the sockets of the test server are being flushed and disconnected.
84 if (rv <= 0)
85 return;
75 base::AutoLock lock(lock_); 86 base::AutoLock lock(lock_);
76 uint16_t socket = GetPort(connection); 87 uint16_t socket = GetPort(connection);
77 EXPECT_FALSE(sockets_.find(socket) == sockets_.end()); 88 EXPECT_FALSE(sockets_.find(socket) == sockets_.end());
78 89
79 sockets_[socket] = SOCKET_READ_FROM; 90 sockets_[socket] = SOCKET_READ_FROM;
80 task_runner_->PostTask(FROM_HERE, read_loop_.QuitClosure()); 91 task_runner_->PostTask(FROM_HERE, read_loop_.QuitClosure());
81 } 92 }
82 93
83 // Returns the number of sockets that were accepted by the server. 94 // Returns the number of sockets that were accepted by the server.
84 size_t GetAcceptedSocketCount() const { 95 size_t GetAcceptedSocketCount() const {
85 base::AutoLock lock(lock_); 96 base::AutoLock lock(lock_);
86 return sockets_.size(); 97 return sockets_.size();
87 } 98 }
88 99
89 // Returns the number of sockets that were read from by the server. 100 // Returns the number of sockets that were read from by the server.
90 size_t GetReadSocketCount() const { 101 size_t GetReadSocketCount() const {
91 base::AutoLock lock(lock_); 102 base::AutoLock lock(lock_);
92 size_t read_sockets = 0; 103 size_t read_sockets = 0;
93 for (const auto& socket : sockets_) { 104 for (const auto& socket : sockets_) {
94 if (socket.second == SOCKET_READ_FROM) 105 if (socket.second == SOCKET_READ_FROM)
95 ++read_sockets; 106 ++read_sockets;
96 } 107 }
97 return read_sockets; 108 return read_sockets;
98 } 109 }
99 110
100 void WaitUntilFirstConnectionAccepted() { accept_loop_.Run(); } 111 void WaitUntilFirstConnectionAccepted() { accept_loop_.Run(); }
101 112
102 void WaitUntilFirstConnectionRead() { read_loop_.Run(); } 113 void WaitUntilFirstConnectionRead() { read_loop_.Run(); }
103 114
115 void ClearSockets() {
mmenke 2016/04/22 16:29:26 ClearSocketCounts? The external API is all about
Charlie Harrison 2016/04/28 16:29:51 Done.
116 base::AutoLock lock(lock_);
117 sockets_.clear();
118 }
119
104 private: 120 private:
105 static uint16_t GetPort(const net::StreamSocket& connection) { 121 static uint16_t GetPort(const net::StreamSocket& connection) {
106 // Get the remote port of the peer, since the local port will always be the 122 // Get the remote port of the peer, since the local port will always be the
107 // port the test server is listening on. This isn't strictly correct - it's 123 // port the test server is listening on. This isn't strictly correct - it's
108 // possible for multiple peers to connect with the same remote port but 124 // possible for multiple peers to connect with the same remote port but
109 // different remote IPs - but the tests here assume that connections to the 125 // different remote IPs - but the tests here assume that connections to the
110 // test server (running on localhost) will always come from localhost, and 126 // test server (running on localhost) will always come from localhost, and
111 // thus the peer port is all thats needed to distinguish two connections. 127 // thus the peer port is all thats needed to distinguish two connections.
112 // This also would be problematic if the OS reused ports, but that's not 128 // This also would be problematic if the OS reused ports, but that's not
113 // something to worry about for these tests. 129 // something to worry about for these tests.
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
195 // requested and thus is running a nested message loop. 211 // requested and thus is running a nested message loop.
196 bool is_waiting_for_hostname_; 212 bool is_waiting_for_hostname_;
197 213
198 // A list of hostnames for which resolution has already been requested. Only 214 // A list of hostnames for which resolution has already been requested. Only
199 // to be accessed from the UI thread. 215 // to be accessed from the UI thread.
200 std::vector<std::string> requested_hostnames_; 216 std::vector<std::string> requested_hostnames_;
201 217
202 DISALLOW_COPY_AND_ASSIGN(HostResolutionRequestRecorder); 218 DISALLOW_COPY_AND_ASSIGN(HostResolutionRequestRecorder);
203 }; 219 };
204 220
221 // This class intercepts URLRequests and responds with 404s.
222 class NotFoundRequestInterceptor : public net::URLRequestInterceptor {
223 public:
224 NotFoundRequestInterceptor() {}
225 ~NotFoundRequestInterceptor() override {}
226
227 private:
228 net::URLRequestJob* MaybeInterceptRequest(
229 net::URLRequest* request,
230 net::NetworkDelegate* network_delegate) const override {
231 const char kHeaders[] =
232 "HTTP/1.1 404 Not Found\n"
233 "\n";
234 std::string headers(kHeaders, arraysize(kHeaders));
mmenke 2016/04/22 16:29:25 Above two lines aren't used.
Charlie Harrison 2016/04/28 16:29:51 Done.
235
236 return new net::URLRequestFailedJob(request, network_delegate,
237 net::ERR_ABORTED);
238 }
239 DISALLOW_COPY_AND_ASSIGN(NotFoundRequestInterceptor);
240 };
241
242 class CrossSitePredictorObserver
243 : public chrome_browser_net::PredictorObserver {
244 public:
245 CrossSitePredictorObserver(const GURL source_host, const GURL cross_site_host)
mmenke 2016/04/22 16:29:25 nit: const GURL& (x2)
mmenke 2016/04/22 16:29:25 include gurl.h
Charlie Harrison 2016/04/28 16:29:51 Done.
Charlie Harrison 2016/04/28 16:29:51 Done.
246 : source_host_(source_host),
247 cross_site_host_(cross_site_host),
248 cross_site_learned_(0),
249 same_site_learned_(0),
mmenke 2016/04/22 16:29:25 I don't think we ever use same_site_learned_? The
Charlie Harrison 2016/04/28 16:29:51 Removed. If we want to add tests for them we can l
250 cross_site_preconnected_(0),
251 same_site_preconnected_(0) {}
252
253 void OnPreconnectUrl(
254 const GURL& original_url,
255 const GURL& first_party_for_cookies,
256 chrome_browser_net::UrlInfo::ResolutionMotivation motivation,
257 int count) override {
258 base::AutoLock lock(lock_);
259 if (original_url == cross_site_host_) {
260 cross_site_preconnected_ = std::max(cross_site_preconnected_, count);
261 } else if (original_url == source_host_) {
262 same_site_preconnected_ = std::max(same_site_preconnected_, count);
263 } else {
264 NOTREACHED() << "Preconnected" << original_url
mmenke 2016/04/22 16:29:25 Should use ADD_FAILURE() instead (A gtest macro th
Charlie Harrison 2016/04/28 16:29:51 Done.
265 << " When should only be preconnecting the source host: "
266 << source_host_
267 << " or the cross site host: " << cross_site_host_;
268 }
269 }
270
271 void OnLearnFromNavigation(const GURL& referring_url,
272 const GURL& target_url) override {
273 base::AutoLock lock(lock_);
274 if (referring_url == source_host_ && target_url == cross_site_host_) {
275 cross_site_learned_++;
276 } else if (referring_url == source_host_ && target_url == source_host_) {
277 same_site_learned_++;
278 } else if (referring_url != cross_site_host_) {
mmenke 2016/04/22 16:29:25 What if target_url is unexpected? Can we just do:
Charlie Harrison 2016/04/28 16:29:51 The code in your comment is a bit unclear but I th
279 NOTREACHED() << "Learned " << referring_url << " => " << target_url
mmenke 2016/04/22 16:29:25 ADD_FAILURE()
Charlie Harrison 2016/04/28 16:29:51 Done.
280 << " When should only be learning the source host: "
281 << source_host_
282 << " or the cross site host: " << cross_site_host_;
283 }
284 }
285
286 void ResetCounts() {
287 base::AutoLock lock(lock_);
288 cross_site_learned_ = 0;
289 same_site_learned_ = 0;
290 cross_site_preconnected_ = 0;
291 same_site_preconnected_ = 0;
292 }
293
294 int cross_site_learned() const {
295 base::AutoLock lock(lock_);
296 return cross_site_learned_;
297 }
298
299 int same_site_learned() const {
300 base::AutoLock lock(lock_);
301 return same_site_learned_;
302 }
303
304 int cross_site_preconnected() const {
305 base::AutoLock lock(lock_);
306 return cross_site_preconnected_;
307 }
308
309 int same_site_preconnected() const {
310 base::AutoLock lock(lock_);
311 return same_site_preconnected_;
312 }
313
314 private:
315 const GURL source_host_;
316 const GURL cross_site_host_;
317
318 int cross_site_learned_;
319 int same_site_learned_;
320
321 int cross_site_preconnected_;
322 int same_site_preconnected_;
323 mutable base::Lock lock_;
mmenke 2016/04/22 16:29:25 DISALLOW_COPY_AND_ASSIGN
Charlie Harrison 2016/04/28 16:29:51 Done.
324 };
325
205 } // namespace 326 } // namespace
206 327
207 namespace chrome_browser_net { 328 namespace chrome_browser_net {
208 329
209 class PredictorBrowserTest : public InProcessBrowserTest { 330 class PredictorBrowserTest : public InProcessBrowserTest {
210 public: 331 public:
211 PredictorBrowserTest() 332 PredictorBrowserTest()
212 : startup_url_("http://host1:1"), 333 : startup_url_("http://host1:1"),
213 referring_url_("http://host2:1"), 334 referring_url_("http://host2:1"),
214 target_url_("http://host3:1"), 335 target_url_("http://host3:1"),
215 host_resolution_request_recorder_(new HostResolutionRequestRecorder) { 336 host_resolution_request_recorder_(new HostResolutionRequestRecorder),
216 } 337 cross_site_test_server_(new net::EmbeddedTestServer()) {}
217 338
218 protected: 339 protected:
219 void SetUpInProcessBrowserTestFixture() override { 340 void SetUpInProcessBrowserTestFixture() override {
220 scoped_host_resolver_proc_.reset(new net::ScopedDefaultHostResolverProc( 341 scoped_host_resolver_proc_.reset(new net::ScopedDefaultHostResolverProc(
221 host_resolution_request_recorder_.get())); 342 host_resolution_request_recorder_.get()));
222 InProcessBrowserTest::SetUpInProcessBrowserTestFixture(); 343 InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
223 } 344 }
224 345
225 void SetUpCommandLine(base::CommandLine* command_line) override { 346 void SetUpCommandLine(base::CommandLine* command_line) override {
226 command_line->AppendSwitch( 347 command_line->AppendSwitch(
227 switches::kEnableExperimentalWebPlatformFeatures); 348 switches::kEnableExperimentalWebPlatformFeatures);
228 command_line->AppendSwitchASCII( 349 command_line->AppendSwitchASCII(
229 switches::kEnableBlinkFeatures, kBlinkPreconnectFeature); 350 switches::kEnableBlinkFeatures, kBlinkPreconnectFeature);
230 } 351 }
231 352
353 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
mmenke 2016/04/22 16:29:25 This method name should be clearer, and may need a
Charlie Harrison 2016/04/28 16:29:52 Updated the name to "RedirectForPathPathHandler".
354 const net::test_server::HttpRequest& request) {
355 if (request.GetURL().path() != "/")
mmenke 2016/04/22 16:29:25 Suggest taking this as an argument, then can have
Charlie Harrison 2016/04/28 16:29:52 Done.
356 return nullptr;
357 std::unique_ptr<net::test_server::BasicHttpResponse> response(
358 new net::test_server::BasicHttpResponse);
359 response->set_code(net::HTTP_MOVED_PERMANENTLY);
360 response->AddCustomHeader(
361 "Location", cross_site_test_server()->GetURL("/title1.html").spec());
mmenke 2016/04/22 16:29:25 Calling into the cross_site_test_server on the oth
Charlie Harrison 2016/04/28 16:29:52 Done.
362 return std::move(response);
363 }
364
232 void SetUpOnMainThread() override { 365 void SetUpOnMainThread() override {
366 embedded_test_server()->RegisterRequestHandler(base::Bind(
367 &PredictorBrowserTest::HandleRequest, base::Unretained(this)));
368 cross_site_test_server_->ServeFilesFromSourceDirectory("chrome/test/data/");
369
233 connection_listener_.reset(new ConnectionListener()); 370 connection_listener_.reset(new ConnectionListener());
371 cross_site_connection_listener_.reset(new ConnectionListener());
234 embedded_test_server()->SetConnectionListener(connection_listener_.get()); 372 embedded_test_server()->SetConnectionListener(connection_listener_.get());
373 cross_site_test_server_->SetConnectionListener(
374 cross_site_connection_listener_.get());
235 ASSERT_TRUE(embedded_test_server()->Start()); 375 ASSERT_TRUE(embedded_test_server()->Start());
376 ASSERT_TRUE(cross_site_test_server_->Start());
377
378 predictor()->set_preconnect_enabled_for_test(true);
379 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
380 base::Bind(&PredictorBrowserTest::SetUpOnIOThread,
381 base::Unretained(this)));
382 }
383
384 void SetUpOnIOThread() {
385 // Ignore all favicon requests. These are tricky to deal with because they
386 // are scheduled on the UI thread and are not accounted for in the load
387 // event. The interceptor will respond with 404s, and won't connect any
388 // sockets to the test server.
389 AddUrlInterceptor(cross_site_test_server()->GetURL("/favicon.ico"));
236 } 390 }
237 391
238 void TearDownOnMainThread() override { 392 void TearDownOnMainThread() override {
239 ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); 393 ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
240 } 394 }
241 395
242 // Navigates to a data URL containing the given content, with a MIME type of 396 // Navigates to a data URL containing the given content, with a MIME type of
243 // text/html. 397 // text/html.
244 void NavigateToDataURLWithContent(const std::string& content) { 398 void NavigateToDataURLWithContent(const std::string& content) {
245 std::string encoded_content; 399 std::string encoded_content;
246 base::Base64Encode(content, &encoded_content); 400 base::Base64Encode(content, &encoded_content);
247 std::string data_uri_content = "data:text/html;base64," + encoded_content; 401 std::string data_uri_content = "data:text/html;base64," + encoded_content;
248 ui_test_utils::NavigateToURL(browser(), GURL(data_uri_content)); 402 ui_test_utils::NavigateToURL(browser(), GURL(data_uri_content));
249 } 403 }
250 404
251 void TearDownInProcessBrowserTestFixture() override { 405 void TearDownInProcessBrowserTestFixture() override {
252 InProcessBrowserTest::TearDownInProcessBrowserTestFixture(); 406 InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
253 scoped_host_resolver_proc_.reset(); 407 scoped_host_resolver_proc_.reset();
254 } 408 }
255 409
256 void LearnAboutInitialNavigation(const GURL& url) { 410 void LearnAboutInitialNavigation(const GURL& url) {
257 Predictor* predictor = browser()->profile()->GetNetworkPredictor(); 411 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
258 BrowserThread::PostTask(BrowserThread::IO,
259 FROM_HERE,
260 base::Bind(&Predictor::LearnAboutInitialNavigation, 412 base::Bind(&Predictor::LearnAboutInitialNavigation,
261 base::Unretained(predictor), 413 base::Unretained(predictor()), url));
262 url));
263 content::RunAllPendingInMessageLoop(BrowserThread::IO); 414 content::RunAllPendingInMessageLoop(BrowserThread::IO);
264 } 415 }
265 416
266 void LearnFromNavigation(const GURL& referring_url, const GURL& target_url) { 417 void LearnFromNavigation(const GURL& referring_url, const GURL& target_url) {
267 Predictor* predictor = browser()->profile()->GetNetworkPredictor(); 418 BrowserThread::PostTask(
268 BrowserThread::PostTask(BrowserThread::IO, 419 BrowserThread::IO, FROM_HERE,
269 FROM_HERE, 420 base::Bind(&Predictor::LearnFromNavigation,
270 base::Bind(&Predictor::LearnFromNavigation, 421 base::Unretained(predictor()), referring_url, target_url));
271 base::Unretained(predictor),
272 referring_url,
273 target_url));
274 content::RunAllPendingInMessageLoop(BrowserThread::IO); 422 content::RunAllPendingInMessageLoop(BrowserThread::IO);
275 } 423 }
276 424
277 void PrepareFrameSubresources(const GURL& url) { 425 void PrepareFrameSubresources(const GURL& url) {
278 Predictor* predictor = browser()->profile()->GetNetworkPredictor(); 426 predictor()->PredictFrameSubresources(url, GURL());
279 predictor->PredictFrameSubresources(url, GURL());
280 } 427 }
281 428
282 void GetListFromPrefsAsString(const char* list_path, 429 void GetListFromPrefsAsString(const char* list_path,
283 std::string* value_as_string) const { 430 std::string* value_as_string) const {
284 PrefService* prefs = browser()->profile()->GetPrefs(); 431 PrefService* prefs = browser()->profile()->GetPrefs();
285 const base::ListValue* list_value = prefs->GetList(list_path); 432 const base::ListValue* list_value = prefs->GetList(list_path);
286 JSONStringValueSerializer serializer(value_as_string); 433 JSONStringValueSerializer serializer(value_as_string);
287 serializer.Serialize(*list_value); 434 serializer.Serialize(*list_value);
288 } 435 }
289 436
290 bool HasHostBeenRequested(const std::string& hostname) const { 437 bool HasHostBeenRequested(const std::string& hostname) const {
291 return host_resolution_request_recorder_->HasHostBeenRequested(hostname); 438 return host_resolution_request_recorder_->HasHostBeenRequested(hostname);
292 } 439 }
293 440
294 void WaitUntilHostHasBeenRequested(const std::string& hostname) { 441 void WaitUntilHostHasBeenRequested(const std::string& hostname) {
295 host_resolution_request_recorder_->WaitUntilHostHasBeenRequested(hostname); 442 host_resolution_request_recorder_->WaitUntilHostHasBeenRequested(hostname);
296 } 443 }
297 444
298 int RequestedHostnameCount() const { 445 int RequestedHostnameCount() const {
299 return host_resolution_request_recorder_->RequestedHostnameCount(); 446 return host_resolution_request_recorder_->RequestedHostnameCount();
300 } 447 }
301 448
449 net::EmbeddedTestServer* cross_site_test_server() {
450 return cross_site_test_server_.get();
451 }
452
453 Predictor* predictor() { return browser()->profile()->GetNetworkPredictor(); }
454
455 void InstallPredictorObserver() {
456 BrowserThread::PostTask(
457 BrowserThread::IO, FROM_HERE,
458 base::Bind(&PredictorBrowserTest::InstallPredictorObserverOnIOThread,
459 base::Unretained(this), base::Unretained(predictor())));
460 }
461
462 void InstallPredictorObserverOnIOThread(
463 chrome_browser_net::Predictor* predictor) {
464 observer_.reset(
465 new CrossSitePredictorObserver(embedded_test_server()->base_url(),
466 cross_site_test_server()->base_url()));
mmenke 2016/04/22 16:29:25 Suggest just creating the observer_ in InstallPred
Charlie Harrison 2016/04/28 16:29:51 Done.
467 predictor->SetObserver(observer_.get());
468 }
469
470 CrossSitePredictorObserver* observer() { return observer_.get(); }
471
472 // Navigate to an html file and request async fetches. Wait until the fetches
mmenke 2016/04/22 16:29:25 Suggest "Navigate to an html file and request asyn
Charlie Harrison 2016/04/28 16:29:51 Done.
473 // complete before continuing. Note that "cors" here means using cors-mode in
474 // correspondence with the fetch spec. This translates to using the privacy
475 // mode socket pools.
476 void NavigateToCrossSiteHTMLURL(int num_cors, const char* file_suffix) {
477 const GURL& base_url = cross_site_test_server()->base_url();
478 std::string path = base::StringPrintf(
479 "/predictor/"
480 "predictor_cross_site%s.html?subresourceHost=%s&"
481 "numCORSResources=%d",
482 file_suffix, base_url.spec().c_str(), num_cors);
483 ui_test_utils::NavigateToURL(browser(),
484 embedded_test_server()->GetURL(path));
485 bool result = false;
486 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
487 browser()->tab_strip_model()->GetActiveWebContents(), "sendFetches()",
mmenke 2016/04/22 16:29:25 Maybe sendFetches -> startFetchesAndWaitForReply?
Charlie Harrison 2016/04/28 16:29:51 SGTM. Done.
488 &result));
489 EXPECT_TRUE(result);
490 }
491
492 // Simulate the target server restarting. Delete all sockets and reset members
493 // tracking preconnects.
494 void ResetTarget() {
495 cross_site_test_server()->FlushAllSocketsAndConnectionsOnUIThread();
496 cross_site_connection_listener_->ClearSockets();
497 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount());
498 }
499
500 static void AddUrlInterceptor(const GURL url) {
mmenke 2016/04/22 16:29:25 GURL&
Charlie Harrison 2016/04/28 16:29:51 Done.
501 net::URLRequestFilter::GetInstance()->AddUrlInterceptor(
502 url, make_scoped_ptr(new NotFoundRequestInterceptor()));
503 }
504
302 const GURL startup_url_; 505 const GURL startup_url_;
303 const GURL referring_url_; 506 const GURL referring_url_;
304 const GURL target_url_; 507 const GURL target_url_;
305 std::unique_ptr<ConnectionListener> connection_listener_; 508 std::unique_ptr<ConnectionListener> connection_listener_;
509 std::unique_ptr<ConnectionListener> cross_site_connection_listener_;
306 510
307 private: 511 private:
308 scoped_refptr<HostResolutionRequestRecorder> 512 scoped_refptr<HostResolutionRequestRecorder>
309 host_resolution_request_recorder_; 513 host_resolution_request_recorder_;
310 std::unique_ptr<net::ScopedDefaultHostResolverProc> 514 std::unique_ptr<net::ScopedDefaultHostResolverProc>
311 scoped_host_resolver_proc_; 515 scoped_host_resolver_proc_;
516 std::unique_ptr<net::EmbeddedTestServer> cross_site_test_server_;
517 std::unique_ptr<CrossSitePredictorObserver> observer_;
312 }; 518 };
313 519
314 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, PRE_ShutdownStartupCycle) { 520 // Test the html test harness used to initiate cross site fetches. These
521 // initiate cross site subresource requests to the cross site test server.
522 // Inspect the predictor's internal state to make sure that they are properly
523 // logged.
524 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, CrossSiteUseOneSocket) {
525 InstallPredictorObserver();
526 NavigateToCrossSiteHTMLURL(1 /* num_cors */, "" /* file_suffix */);
527 EXPECT_EQ(1u, cross_site_connection_listener_->GetAcceptedSocketCount());
528 EXPECT_EQ(1, observer()->cross_site_learned());
529 EXPECT_EQ(2, observer()->same_site_preconnected());
530 }
531
532 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, CrossSiteUseThreeSockets) {
533 InstallPredictorObserver();
534 NavigateToCrossSiteHTMLURL(3 /* num_cors */, "" /* file_suffix */);
535 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
536 EXPECT_EQ(3, observer()->cross_site_learned());
537 EXPECT_EQ(2, observer()->same_site_preconnected());
538 }
539
540 // The following tests confirm that Chrome accurately predicts preconnects after
541 // learning from navigations. Note that every "learned" connection adds ~.33 to
542 // the expected connection number, which starts at 2. Every preconnect Chrome
543 // performs multiplies the expected connections by .66. One additional
544 // complexity
545 // is that if a preconnect from A to B is learned, an extra preconnect will be
546 // issued if the host part of A and B match (ignoring port).
547 // TODO(csharrison): This logic could probably be removed.
548 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
549 CrossSiteSimplePredictionAfterOneNavigation) {
550 InstallPredictorObserver();
551 NavigateToCrossSiteHTMLURL(2 /* num_cors */, "" /* file_suffix */);
552 EXPECT_EQ(2u, cross_site_connection_listener_->GetAcceptedSocketCount());
553 EXPECT_EQ(2, observer()->cross_site_learned());
554 EXPECT_EQ(2, observer()->same_site_preconnected());
555
556 ResetTarget();
557 observer()->ResetCounts();
558
559 // Navigate again and confirm a preconnect. Note that because the two
560 // embedded test servers have the same host_piece, an extra preconnect is
561 // issued. This results in ceil(2.66) + 1 = 4 preconnects.
562 ui_test_utils::NavigateToURL(browser(),
563 embedded_test_server()->GetURL("/title1.html"));
564 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
565 EXPECT_EQ(4, observer()->cross_site_preconnected());
566 }
567
568 // Expect that the predictor correctly predicts subframe navigations.
569 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, SubframeCrossSitePrediction) {
570 InstallPredictorObserver();
571 ui_test_utils::NavigateToURL(
572 browser(), embedded_test_server()->GetURL(
573 "/predictor/predictor_cross_site_subframe_nav.html"));
574 bool result = false;
575 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
576 browser()->tab_strip_model()->GetActiveWebContents(),
577 base::StringPrintf(
578 "navigateSubframe('%s')",
579 cross_site_test_server()->GetURL("/title1.html").spec().c_str()),
580 &result));
581 EXPECT_TRUE(result);
582 EXPECT_EQ(2u, cross_site_connection_listener_->GetAcceptedSocketCount());
583 EXPECT_EQ(1, observer()->cross_site_learned());
584 EXPECT_EQ(2, observer()->same_site_preconnected());
585
586 ResetTarget();
587 observer()->ResetCounts();
588
589 // Navigate again and confirm a preconnect. Note that because the two
590 // embedded test servers have the same host_piece, an extra preconnect is
591 // issued. This results in ceil(2 + .33) + 1 = 4 preconnects.
592 ui_test_utils::NavigateToURL(browser(),
593 embedded_test_server()->GetURL("/title1.html"));
594 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
595 EXPECT_EQ(4, observer()->cross_site_preconnected());
596 }
597
598 // Expect that the predictor correctly preconnects an already learned resource
599 // if the host shows up in a subframe.
600 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, SubframeInitiatesPreconnects) {
601 InstallPredictorObserver();
602 // Navigate to the normal cross site URL to learn the relationship.
603 NavigateToCrossSiteHTMLURL(1 /* num_cors */, "" /* file_suffix */);
604 EXPECT_EQ(1u, cross_site_connection_listener_->GetAcceptedSocketCount());
605 EXPECT_EQ(1, observer()->cross_site_learned());
606 EXPECT_EQ(2, observer()->same_site_preconnected());
607
608 ResetTarget();
609 observer()->ResetCounts();
610
611 // Navigate again and confirm a preconnect. Note that because the two
612 // embedded test servers have the same host_piece, an extra preconnect is
613 // issued. This results in ceil(2 + .33) + 1 = 4 preconnects.
614 NavigateToDataURLWithContent(
615 "<iframe src=\"" + embedded_test_server()->GetURL("/title1.html").spec() +
616 "\"></iframe>");
617 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
618 EXPECT_EQ(4, observer()->cross_site_preconnected());
619 }
620
621 // Expect that the predictor correctly learns the subresources a subframe needs
622 // to preconnect to.
623 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, SubframeLearning) {
624 InstallPredictorObserver();
625 std::string path = base::StringPrintf(
626 "/predictor/"
627 "predictor_cross_site.html?subresourceHost=%s&"
628 "numCORSResources=1&sendImmediately=1",
629 cross_site_test_server()->base_url().spec().c_str());
630 NavigateToDataURLWithContent(
631 base::StringPrintf("<iframe src=\"%s\"></iframe>",
632 embedded_test_server()->GetURL(path).spec().c_str()));
633 cross_site_connection_listener_->WaitUntilFirstConnectionRead();
634 EXPECT_EQ(1u, cross_site_connection_listener_->GetAcceptedSocketCount());
635 EXPECT_EQ(1, observer()->cross_site_learned());
636 EXPECT_EQ(2, observer()->same_site_preconnected());
637
638 ResetTarget();
639 observer()->ResetCounts();
640
641 // Navigate again and confirm a preconnect. Note that because the two
642 // embedded test servers have the same host_piece, an extra preconnect is
643 // issued. This results in ceil(2 + .33) + 1 = 4 preconnects.
644 NavigateToDataURLWithContent(
645 "<iframe src=\"" + embedded_test_server()->GetURL("/title1.html").spec() +
646 "\"></iframe>");
647 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
648 EXPECT_EQ(4, observer()->cross_site_preconnected());
649 }
650
651 // This tests the implementation details of the predictor. The current predictor
652 // only learns via the referrer header.
653 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, CrossSiteNoReferrerNoLearning) {
654 InstallPredictorObserver();
655 NavigateToCrossSiteHTMLURL(1 /* num_cors */,
656 "_no_referrer" /* file_suffix */);
657 EXPECT_EQ(1u, cross_site_connection_listener_->GetAcceptedSocketCount());
658 EXPECT_EQ(0, observer()->cross_site_learned());
659 EXPECT_EQ(2, observer()->same_site_preconnected());
660 }
661
662 // This test navigates to an html file with a tag:
663 // <meta name="referrer" content="never">
664 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
665 CrossSiteNoReferrerNoPredictionAfterOneNavigation) {
666 InstallPredictorObserver();
667 NavigateToCrossSiteHTMLURL(2 /* num_cors */,
668 "_no_referrer" /* file_suffix */);
669 EXPECT_EQ(2u, cross_site_connection_listener_->GetAcceptedSocketCount());
670 EXPECT_EQ(0, observer()->cross_site_learned());
671 EXPECT_EQ(2, observer()->same_site_preconnected());
672
673 ResetTarget();
674 observer()->ResetCounts();
675
676 // Navigate again and confirm that no preconnects occurred.
677 ui_test_utils::NavigateToURL(browser(),
678 embedded_test_server()->GetURL("/title1.html"));
679 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount());
680 EXPECT_EQ(0, observer()->cross_site_preconnected());
681 }
682
683 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
684 CrossSiteSimplePredictionAfterTwoNavigations) {
685 InstallPredictorObserver();
686 NavigateToCrossSiteHTMLURL(1 /* num_cors */, "" /* file_suffix */);
687 NavigateToCrossSiteHTMLURL(1 /* num_cors */, "" /* file_suffix */);
688
689 ResetTarget();
690 observer()->ResetCounts();
691
692 // Navigate again and confirm a preconnect. Note that because the two
693 // embedded test servers have the same host_piece, an extra preconnect is
694 // issued. This results in ceil((2 + .33)*.66 + .33) + 1 ~= 3 preconnects.
695 ui_test_utils::NavigateToURL(browser(),
696 embedded_test_server()->GetURL("/title1.html"));
697 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
698 EXPECT_EQ(3, observer()->cross_site_preconnected());
699 }
700
701 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
702 CrossSiteSimplePredictionAfterTwoNavigations2) {
703 InstallPredictorObserver();
704 NavigateToCrossSiteHTMLURL(2 /* num_cors */, "" /* file_suffix */);
705 NavigateToCrossSiteHTMLURL(2 /* num_cors */, "" /* file_suffix */);
706
707 ResetTarget();
708 observer()->ResetCounts();
709
710 // (((2 + .66)*.66) + .66) + 1 ~= 3.4
711 ui_test_utils::NavigateToURL(browser(),
712 embedded_test_server()->GetURL("/title1.html"));
713 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
714 EXPECT_EQ(4, observer()->cross_site_preconnected());
715 }
716
717 // The first navigation uses a subresource. Subsequent navigations don't use
718 // that subresource. This tests how the predictor forgets about these bad
719 // navigations.
720 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, ForgetBadPrediction) {
721 InstallPredictorObserver();
722 NavigateToCrossSiteHTMLURL(1 /* num_cors */, "" /* file_suffix */);
723
724 ResetTarget();
725 observer()->ResetCounts();
726
727 ui_test_utils::NavigateToURL(browser(),
728 embedded_test_server()->GetURL("/title1.html"));
729 // (2 + .33) + 1 = 3.33.
730 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
731 EXPECT_EQ(4, observer()->cross_site_preconnected());
732
733 ResetTarget();
734 observer()->ResetCounts();
735
736 ui_test_utils::NavigateToURL(browser(),
737 embedded_test_server()->GetURL("/title1.html"));
738
739 // ceil((2 + .33) * .66) + 1 = 3.
740 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
741 EXPECT_EQ(3, observer()->cross_site_preconnected());
742
743 ResetTarget();
744 observer()->ResetCounts();
745
746 ui_test_utils::NavigateToURL(browser(),
747 embedded_test_server()->GetURL("/title1.html"));
748 // ceil((2 + .33) * .66 * .66) + 1 = 3.
749 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
750 EXPECT_EQ(3, observer()->cross_site_preconnected());
751
752 ResetTarget();
753 observer()->ResetCounts();
754
755 ui_test_utils::NavigateToURL(browser(),
756 embedded_test_server()->GetURL("/title1.html"));
757 // Finally, (2 + .33) * .66^3 ~= .67. Not enough for a preconnect.
758 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount());
759 EXPECT_EQ(0, observer()->cross_site_preconnected());
760 }
761
762 // The predictor does not follow redirects if the original url had a non-empty
763 // path (a path that was more than just "/").
764 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
765 CrossSiteRedirectNoPredictionWithPath) {
766 InstallPredictorObserver();
767 ui_test_utils::NavigateToURL(
768 browser(),
769 embedded_test_server()->GetURL(base::StringPrintf(
770 "/server-redirect?%s",
771 cross_site_test_server()->GetURL("/title1.html").spec().c_str())));
772 EXPECT_EQ(2u, cross_site_connection_listener_->GetAcceptedSocketCount());
773 EXPECT_EQ(0, observer()->cross_site_learned());
774 EXPECT_EQ(2, observer()->same_site_preconnected());
775
776 ResetTarget();
777 observer()->ResetCounts();
778
779 ui_test_utils::NavigateToURL(browser(),
780 embedded_test_server()->GetURL("/title1.html"));
781 EXPECT_EQ(0u, cross_site_connection_listener_->GetAcceptedSocketCount());
782 EXPECT_EQ(0, observer()->cross_site_preconnected());
783 }
784
785 // The predictor does follow redirects if the original url had an empty path
786 // (a path that was more than just "/"). Use the registered "/" path to redirect
787 // to the target test server.
788 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
789 CrossSiteRedirectPredictionWithNoPath) {
790 InstallPredictorObserver();
791 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
792 EXPECT_EQ(2u, cross_site_connection_listener_->GetAcceptedSocketCount());
793 EXPECT_EQ(1, observer()->cross_site_learned());
794 EXPECT_EQ(2, observer()->same_site_preconnected());
795
796 ResetTarget();
797 observer()->ResetCounts();
798
799 ui_test_utils::NavigateToURL(browser(),
800 embedded_test_server()->GetURL("/title1.html"));
801 // Preconnect 4 sockets because ceil(2 + .33) + 1 = 4.
802 EXPECT_EQ(4u, cross_site_connection_listener_->GetAcceptedSocketCount());
803 EXPECT_EQ(4, observer()->cross_site_preconnected());
804 }
805
806 // This test uses "localhost" instead of "127.0.0.1" to avoid extra preconnects
807 // to hosts with the same host piece (ignoring port). Note that the preconnect
808 // observer is not used here due to its strict checks on hostname.
809 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
810 CrossSiteRedirectPredictionWithNoPathDifferentHostName) {
811 GURL localhost_source = GURL(base::StringPrintf(
812 "http://localhost:%s/predictor/"
813 "predictor_cross_site.html?subresourceHost=%s&numCORSResources=1",
814 embedded_test_server()->base_url().port().c_str(),
815 cross_site_test_server()->base_url().spec().c_str()));
816 ui_test_utils::NavigateToURL(browser(), localhost_source);
817 bool result = false;
818 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
819 browser()->tab_strip_model()->GetActiveWebContents(), "sendFetches()",
820 &result));
821 EXPECT_TRUE(result);
822
823 EXPECT_EQ(1u, cross_site_connection_listener_->GetAcceptedSocketCount());
824
825 ResetTarget();
826
827 ui_test_utils::NavigateToURL(
828 browser(), GURL(base::StringPrintf(
829 "http://localhost:%s/title1.html",
830 embedded_test_server()->base_url().port().c_str())));
831 // Preconnect 3 sockets because ceil(2 + .33) = 3. Note that this time there
832 // is no additional connection due to same host.
833 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
834 }
835
836 // Perform the "/" redirect twice and make sure the predictor updates twice.
837 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
838 CrossSiteTwoRedirectsPredictionWithNoPath) {
839 InstallPredictorObserver();
840 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
841 EXPECT_EQ(1, observer()->cross_site_learned());
842 EXPECT_EQ(2, observer()->same_site_preconnected());
843
844 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
845 EXPECT_EQ(2, observer()->cross_site_learned());
846 EXPECT_EQ(2, observer()->same_site_preconnected());
847
848 ResetTarget();
849 observer()->ResetCounts();
850
851 ui_test_utils::NavigateToURL(browser(),
852 embedded_test_server()->GetURL("/title1.html"));
853 // 3 preconnects expected because (2 + .33) * .66 + .33 + 1 ~= 2.87.
854 EXPECT_EQ(3u, cross_site_connection_listener_->GetAcceptedSocketCount());
855 EXPECT_EQ(3, observer()->cross_site_preconnected());
856 }
857
858 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest,
859 PRE_ShutdownStartupCyclePreresolve) {
315 // Prepare state that will be serialized on this shut-down and read on next 860 // Prepare state that will be serialized on this shut-down and read on next
316 // start-up. 861 // start-up. Ensure preresolution over preconnection.
317 LearnAboutInitialNavigation(startup_url_); 862 LearnAboutInitialNavigation(startup_url_);
863 // The target url will have a expected connection count of 2 after this call.
318 LearnFromNavigation(referring_url_, target_url_); 864 LearnFromNavigation(referring_url_, target_url_);
319 } 865
320 866 // In order to reduce the expected connection count < .8, issue predictions 3
321 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, ShutdownStartupCycle) { 867 // times. 2 * .66^3 ~= .58.
868 PrepareFrameSubresources(referring_url_);
869 PrepareFrameSubresources(referring_url_);
870 PrepareFrameSubresources(referring_url_);
871 }
872
873 IN_PROC_BROWSER_TEST_F(PredictorBrowserTest, ShutdownStartupCyclePreresolve) {
322 // Make sure that the Preferences file is actually wiped of all DNS prefetch 874 // Make sure that the Preferences file is actually wiped of all DNS prefetch
323 // related data after start-up. 875 // related data after start-up.
324 std::string cleared_startup_list; 876 std::string cleared_startup_list;
325 std::string cleared_referral_list; 877 std::string cleared_referral_list;
326 GetListFromPrefsAsString(prefs::kDnsPrefetchingStartupList, 878 GetListFromPrefsAsString(prefs::kDnsPrefetchingStartupList,
327 &cleared_startup_list); 879 &cleared_startup_list);
328 GetListFromPrefsAsString(prefs::kDnsPrefetchingHostReferralList, 880 GetListFromPrefsAsString(prefs::kDnsPrefetchingHostReferralList,
329 &cleared_referral_list); 881 &cleared_referral_list);
330 882
331 EXPECT_THAT(cleared_startup_list, Not(HasSubstr(startup_url_.host()))); 883 EXPECT_THAT(cleared_startup_list, Not(HasSubstr(startup_url_.host())));
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after
456 // Second navigation to content with an img. 1008 // Second navigation to content with an img.
457 std::string img_content = 1009 std::string img_content =
458 "<img src=\"" + preconnect_url.spec() + "test.gif\">"; 1010 "<img src=\"" + preconnect_url.spec() + "test.gif\">";
459 NavigateToDataURLWithContent(img_content); 1011 NavigateToDataURLWithContent(img_content);
460 connection_listener_->WaitUntilFirstConnectionRead(); 1012 connection_listener_->WaitUntilFirstConnectionRead();
461 EXPECT_EQ(2u, connection_listener_->GetAcceptedSocketCount()); 1013 EXPECT_EQ(2u, connection_listener_->GetAcceptedSocketCount());
462 EXPECT_EQ(1u, connection_listener_->GetReadSocketCount()); 1014 EXPECT_EQ(1u, connection_listener_->GetReadSocketCount());
463 } 1015 }
464 1016
465 } // namespace chrome_browser_net 1017 } // namespace chrome_browser_net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698