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

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

Issue 15076: Clean up dns prefetch code, and also port it. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: use scoper for init & free Created 11 years, 10 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2006-2008 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 // See header file for description of class
6
7 #include "chrome/browser/net/dns_master.h" 5 #include "chrome/browser/net/dns_master.h"
8 6
7 #include <algorithm>
9 #include <set> 8 #include <set>
9 #include <sstream>
10 10
11 #include "base/compiler_specific.h"
11 #include "base/histogram.h" 12 #include "base/histogram.h"
13 #include "base/lock.h"
14 #include "base/ref_counted.h"
12 #include "base/stats_counters.h" 15 #include "base/stats_counters.h"
13 #include "base/string_util.h" 16 #include "base/string_util.h"
14 #include "base/thread.h" 17 #include "base/time.h"
15 #include "base/win_util.h" 18 #include "net/base/address_list.h"
16 #include "chrome/browser/net/dns_slave.h" 19 #include "net/base/completion_callback.h"
17 20 #include "net/base/host_resolver.h"
18 using base::TimeDelta; 21 #include "net/base/net_errors.h"
19 22
20 namespace chrome_browser_net { 23 namespace chrome_browser_net {
21 24
22 DnsMaster::DnsMaster(TimeDelta shutdown_wait_time) 25 // static
23 : slaves_have_work_(&lock_), 26 const size_t DnsMaster::kMaxConcurrentLookups = 8;
24 slave_count_(0), 27
25 running_slave_count_(0), 28 class DnsMaster::LookupRequest {
26 shutdown_(false), 29 public:
27 kShutdownWaitTime_(shutdown_wait_time) { 30 LookupRequest(DnsMaster* master, const std::string& hostname)
28 for (size_t i = 0; i < kSlaveCountMax; i++) { 31 : ALLOW_THIS_IN_INITIALIZER_LIST(
29 thread_ids_[i] = 0; 32 net_callback_(this, &LookupRequest::OnLookupFinished)),
30 thread_handles_[i] = 0; 33 master_(master),
31 slaves_[i] = NULL; 34 hostname_(hostname) {
32 } 35 }
36
37 bool Start() {
38 const int result = resolver_.Resolve(hostname_, 80, &addresses_,
39 &net_callback_);
40 return (result == net::ERR_IO_PENDING);
41 }
42
43 private:
44 void OnLookupFinished(int result) {
45 master_->OnLookupFinished(this, hostname_, result == net::OK);
46 }
47
48 // HostResolver will call us using this callback when resolution is complete.
49 net::CompletionCallbackImpl<LookupRequest> net_callback_;
50
51 DnsMaster* master_; // Master which started us.
52
53 const std::string hostname_; // Hostname to resolve.
54 net::HostResolver resolver_;
55 net::AddressList addresses_;
56
57 DISALLOW_COPY_AND_ASSIGN(LookupRequest);
58 };
59
60 DnsMaster::DnsMaster() : peak_pending_lookups_(0), shutdown_(false) {
61 }
62
63 DnsMaster::~DnsMaster() {
64 DCHECK(shutdown_);
65 }
66
67 void DnsMaster::Shutdown() {
68 AutoLock auto_lock(lock_);
69
70 DCHECK(!shutdown_);
71 shutdown_ = true;
72
73 std::set<LookupRequest*>::iterator it;
74 for (it = pending_lookups_.begin(); it != pending_lookups_.end(); ++it)
75 delete *it;
33 } 76 }
34 77
35 // Overloaded Resolve() to take a vector of names. 78 // Overloaded Resolve() to take a vector of names.
36 void DnsMaster::ResolveList(const NameList& hostnames, 79 void DnsMaster::ResolveList(const NameList& hostnames,
37 DnsHostInfo::ResolutionMotivation motivation) { 80 DnsHostInfo::ResolutionMotivation motivation) {
38 bool need_to_signal = false; 81 AutoLock auto_lock(lock_);
39 {
40 AutoLock auto_lock(lock_);
41 if (shutdown_) return;
42 if (slave_count_ < kSlaveCountMin) {
43 for (int target_count = std::min(hostnames.size(), kSlaveCountMin);
44 target_count > 0;
45 target_count--)
46 PreLockedCreateNewSlaveIfNeeded();
47 } else {
48 PreLockedCreateNewSlaveIfNeeded(); // Allocate one per list call.
49 }
50 82
51 for (NameList::const_iterator it = hostnames.begin(); 83 NameList::const_iterator it;
52 it < hostnames.end(); 84 for (it = hostnames.begin(); it < hostnames.end(); ++it)
53 it++) { 85 PreLockedResolve(*it, motivation);
54 if (PreLockedResolve(*it, motivation))
55 need_to_signal = true;
56 }
57 }
58 if (need_to_signal)
59 slaves_have_work_.Signal();
60 } 86 }
61 87
62 // Basic Resolve() takes an invidual name, and adds it 88 // Basic Resolve() takes an invidual name, and adds it
63 // to the queue. 89 // to the queue.
64 void DnsMaster::Resolve(const std::string& hostname, 90 void DnsMaster::Resolve(const std::string& hostname,
65 DnsHostInfo::ResolutionMotivation motivation) { 91 DnsHostInfo::ResolutionMotivation motivation) {
66 if (0 == hostname.length()) 92 if (0 == hostname.length())
67 return; 93 return;
68 bool need_to_signal = false; 94 AutoLock auto_lock(lock_);
69 { 95 PreLockedResolve(hostname, motivation);
70 AutoLock auto_lock(lock_);
71 if (shutdown_) return;
72 PreLockedCreateNewSlaveIfNeeded(); // Allocate one at a time.
73 if (PreLockedResolve(hostname, motivation))
74 need_to_signal = true;
75 }
76 if (need_to_signal)
77 slaves_have_work_.Signal();
78 } 96 }
79 97
80 bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer, 98 bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer,
81 DnsHostInfo* navigation_info) { 99 DnsHostInfo* navigation_info) {
82 std::string hostname = navigation_info->hostname(); 100 std::string hostname = navigation_info->hostname();
83 101
84 AutoLock auto_lock(lock_); 102 AutoLock auto_lock(lock_);
85 Results::iterator it = results_.find(hostname); 103 Results::iterator it = results_.find(hostname);
86 if (it == results_.end()) { 104 if (it == results_.end()) {
87 // Remain under lock to assure static HISTOGRAM constructor is safely run. 105 // Remain under lock to assure static HISTOGRAM constructor is safely run.
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 void DnsMaster::NonlinkNavigation(const GURL& referrer, 158 void DnsMaster::NonlinkNavigation(const GURL& referrer,
141 DnsHostInfo* navigation_info) { 159 DnsHostInfo* navigation_info) {
142 std::string referring_host = referrer.host(); 160 std::string referring_host = referrer.host();
143 if (referring_host.empty() || referring_host == navigation_info->hostname()) 161 if (referring_host.empty() || referring_host == navigation_info->hostname())
144 return; 162 return;
145 163
146 referrers_[referring_host].SuggestHost(navigation_info->hostname()); 164 referrers_[referring_host].SuggestHost(navigation_info->hostname());
147 } 165 }
148 166
149 void DnsMaster::NavigatingTo(const std::string& host_name) { 167 void DnsMaster::NavigatingTo(const std::string& host_name) {
150 bool need_to_signal = false; 168 AutoLock auto_lock(lock_);
151 { 169 Referrers::iterator it = referrers_.find(host_name);
152 AutoLock auto_lock(lock_); 170 if (referrers_.end() == it)
153 Referrers::iterator it = referrers_.find(host_name); 171 return;
154 if (referrers_.end() == it) 172 Referrer* referrer = &(it->second);
155 return; 173 for (Referrer::iterator future_host = referrer->begin();
156 Referrer* referrer = &(it->second); 174 future_host != referrer->end(); ++future_host) {
157 for (Referrer::iterator future_host = referrer->begin(); 175 DnsHostInfo* queued_info = PreLockedResolve(
158 future_host != referrer->end(); ++future_host) { 176 future_host->first,
159 DnsHostInfo* queued_info = PreLockedResolve( 177 DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
160 future_host->first, 178 if (queued_info)
161 DnsHostInfo::LEARNED_REFERAL_MOTIVATED); 179 queued_info->SetReferringHostname(host_name);
162 if (queued_info) {
163 need_to_signal = true;
164 queued_info->SetReferringHostname(host_name);
165 }
166 }
167 } 180 }
168 if (need_to_signal)
169 slaves_have_work_.Signal();
170 } 181 }
171 182
172 // Provide sort order so all .com's are together, etc. 183 // Provide sort order so all .com's are together, etc.
173 struct RightToLeftStringSorter { 184 struct RightToLeftStringSorter {
174 bool operator()(const std::string& left, const std::string& right) const { 185 bool operator()(const std::string& left, const std::string& right) const {
175 if (left == right) return true; 186 if (left == right) return true;
176 size_t left_already_matched = left.size(); 187 size_t left_already_matched = left.size();
177 size_t right_already_matched = right.size(); 188 size_t right_already_matched = right.size();
178 189
179 // Ensure both strings have characters. 190 // Ensure both strings have characters.
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
290 } 301 }
291 302
292 // Partition the DnsHostInfo's into categories. 303 // Partition the DnsHostInfo's into categories.
293 for (Snapshot::iterator it(snapshot.begin()); it != snapshot.end(); it++) { 304 for (Snapshot::iterator it(snapshot.begin()); it != snapshot.end(); it++) {
294 if (it->second.was_nonexistant()) { 305 if (it->second.was_nonexistant()) {
295 name_not_found.push_back(it->second); 306 name_not_found.push_back(it->second);
296 continue; 307 continue;
297 } 308 }
298 if (!it->second.was_found()) 309 if (!it->second.was_found())
299 continue; // Still being processed. 310 continue; // Still being processed.
300 if (TimeDelta() != it->second.benefits_remaining()) { 311 if (base::TimeDelta() != it->second.benefits_remaining()) {
301 network_hits.push_back(it->second); // With no benefit yet. 312 network_hits.push_back(it->second); // With no benefit yet.
302 continue; 313 continue;
303 } 314 }
304 if (DnsHostInfo::kMaxNonNetworkDnsLookupDuration > 315 if (DnsHostInfo::kMaxNonNetworkDnsLookupDuration >
305 it->second.resolve_duration()) { 316 it->second.resolve_duration()) {
306 already_cached.push_back(it->second); 317 already_cached.push_back(it->second);
307 continue; 318 continue;
308 } 319 }
309 // Remaining case is where prefetch benefit was significant, and was used. 320 // Remaining case is where prefetch benefit was significant, and was used.
310 // Since we shot those cases as historical hits, we won't bother here. 321 // Since we shot those cases as historical hits, we won't bother here.
(...skipping 14 matching lines...) Expand all
325 DnsHostInfo::GetHtmlTable(already_cached, 336 DnsHostInfo::GetHtmlTable(already_cached,
326 "Previously cached resolutions were found for ", brief, output); 337 "Previously cached resolutions were found for ", brief, output);
327 DnsHostInfo::GetHtmlTable(name_not_found, 338 DnsHostInfo::GetHtmlTable(name_not_found,
328 "Prefetching DNS records revealed non-existance for ", brief, output); 339 "Prefetching DNS records revealed non-existance for ", brief, output);
329 } 340 }
330 341
331 DnsHostInfo* DnsMaster::PreLockedResolve( 342 DnsHostInfo* DnsMaster::PreLockedResolve(
332 const std::string& hostname, 343 const std::string& hostname,
333 DnsHostInfo::ResolutionMotivation motivation) { 344 DnsHostInfo::ResolutionMotivation motivation) {
334 // DCHECK(We have the lock); 345 // DCHECK(We have the lock);
335 DCHECK(0 != slave_count_);
336 DCHECK(0 != hostname.length()); 346 DCHECK(0 != hostname.length());
337 347
348 if (shutdown_)
349 return NULL;
350
338 DnsHostInfo* info = &results_[hostname]; 351 DnsHostInfo* info = &results_[hostname];
339 info->SetHostname(hostname); // Initialize or DCHECK. 352 info->SetHostname(hostname); // Initialize or DCHECK.
340 // TODO(jar): I need to discard names that have long since expired. 353 // TODO(jar): I need to discard names that have long since expired.
341 // Currently we only add to the domain map :-/ 354 // Currently we only add to the domain map :-/
342 355
343 DCHECK(info->HasHostname(hostname)); 356 DCHECK(info->HasHostname(hostname));
344 357
345 if (!info->NeedsDnsUpdate(hostname)) { 358 if (!info->NeedsDnsUpdate(hostname)) {
346 info->DLogResultsStats("DNS PrefetchNotUpdated"); 359 info->DLogResultsStats("DNS PrefetchNotUpdated");
347 return NULL; 360 return NULL;
348 } 361 }
349 362
350 info->SetQueuedState(motivation); 363 info->SetQueuedState(motivation);
351 name_buffer_.push(hostname); 364 name_buffer_.push(hostname);
365
366 PreLockedScheduleLookups();
367
352 return info; 368 return info;
353 } 369 }
354 370
355 // GetNextAssignment() is executed on the thread associated with 371 void DnsMaster::PreLockedScheduleLookups() {
356 // with a prefetch slave instance. 372 while (!name_buffer_.empty() &&
357 // Return value of false indicates slave thread termination is needed. 373 pending_lookups_.size() < kMaxConcurrentLookups) {
358 // Return value of true means info argument was populated 374 const std::string hostname(name_buffer_.front());
359 // with a pointer to the assigned DnsHostInfo instance.
360 bool DnsMaster::GetNextAssignment(std::string* hostname) {
361 bool ask_for_help = false;
362 {
363 AutoLock auto_lock(lock_); // For map and buffer access
364 while (0 == name_buffer_.size() && !shutdown_) {
365 // No work pending, so just wait.
366 // wait on condition variable while releasing lock temporarilly.
367 slaves_have_work_.Wait();
368 }
369 if (shutdown_)
370 return false; // Tell slaves to terminate also.
371 *hostname = name_buffer_.front();
372 name_buffer_.pop(); 375 name_buffer_.pop();
373 376
374 DnsHostInfo* info = &results_[*hostname]; 377 DnsHostInfo* info = &results_[hostname];
375 DCHECK(info->HasHostname(*hostname)); 378 DCHECK(info->HasHostname(hostname));
376 info->SetAssignedState(); 379 info->SetAssignedState();
377 380
378 ask_for_help = name_buffer_.size() > 0; 381 LookupRequest* request = new LookupRequest(this, hostname);
379 } // Release lock_ 382 if (request->Start()) {
380 if (ask_for_help) 383 pending_lookups_.insert(request);
381 slaves_have_work_.Signal(); 384 peak_pending_lookups_ = std::max(peak_pending_lookups_,
382 return true; 385 pending_lookups_.size());
386 } else {
387 NOTREACHED();
388 delete request;
389 }
390 }
383 } 391 }
384 392
385 void DnsMaster::SetFoundState(const std::string hostname) { 393 void DnsMaster::OnLookupFinished(LookupRequest* request,
394 const std::string& hostname, bool found) {
386 AutoLock auto_lock(lock_); // For map access (changing info values). 395 AutoLock auto_lock(lock_); // For map access (changing info values).
387 DnsHostInfo* info = &results_[hostname]; 396 DnsHostInfo* info = &results_[hostname];
388 DCHECK(info->HasHostname(hostname)); 397 DCHECK(info->HasHostname(hostname));
389 if (info->is_marked_to_delete()) 398 if (info->is_marked_to_delete())
390 results_.erase(hostname); 399 results_.erase(hostname);
391 else 400 else {
392 info->SetFoundState(); 401 if (found)
393 } 402 info->SetFoundState();
394 403 else
395 void DnsMaster::SetNoSuchNameState(const std::string hostname) { 404 info->SetNoSuchNameState();
396 AutoLock auto_lock(lock_); // For map access (changing info values).
397 DnsHostInfo* info = &results_[hostname];
398 DCHECK(info->HasHostname(hostname));
399 if (info->is_marked_to_delete())
400 results_.erase(hostname);
401 else
402 info->SetNoSuchNameState();
403 }
404
405 bool DnsMaster::PreLockedCreateNewSlaveIfNeeded() {
406 // Don't create more than max.
407 if (kSlaveCountMax <= slave_count_ || shutdown_)
408 return false;
409
410 DnsSlave* slave_instance = new DnsSlave(this, slave_count_);
411 DWORD thread_id;
412 size_t stack_size = 0;
413 unsigned int flags = CREATE_SUSPENDED;
414 if (win_util::GetWinVersion() >= win_util::WINVERSION_XP) {
415 // 128kb stack size.
416 stack_size = 128*1024;
417 flags |= STACK_SIZE_PARAM_IS_A_RESERVATION;
418 }
419 HANDLE handle = CreateThread(NULL, // security
420 stack_size,
421 DnsSlave::ThreadStart,
422 reinterpret_cast<void*>(slave_instance),
423 flags,
424 &thread_id);
425 DCHECK(NULL != handle);
426 if (NULL == handle)
427 return false;
428
429 // Carlos suggests it is not valuable to do a set priority.
430 // BOOL WINAPI SetThreadPriority(handle,int nPriority);
431
432 thread_ids_[slave_count_] = thread_id;
433 thread_handles_[slave_count_] = handle;
434 slaves_[slave_count_] = slave_instance;
435 slave_count_++;
436
437 ResumeThread(handle); // WINAPI call.
438 running_slave_count_++;
439
440 return true;
441 }
442
443 void DnsMaster::SetSlaveHasTerminated(int slave_index) {
444 DCHECK_EQ(GetCurrentThreadId(), thread_ids_[slave_index]);
445 AutoLock auto_lock(lock_);
446 running_slave_count_--;
447 DCHECK(thread_ids_[slave_index]);
448 thread_ids_[slave_index] = 0;
449 }
450
451 bool DnsMaster::ShutdownSlaves() {
452 int running_slave_count;
453 {
454 AutoLock auto_lock(lock_);
455 shutdown_ = true; // Block additional resolution requests.
456 // Empty the queue gracefully
457 while (name_buffer_.size() > 0) {
458 std::string hostname = name_buffer_.front();
459 name_buffer_.pop();
460 DnsHostInfo* info = &results_[hostname];
461 DCHECK(info->HasHostname(hostname));
462 // We should be in Queued state, so simulate to end of life.
463 info->SetAssignedState(); // Simulate slave assignment.
464 info->SetNoSuchNameState(); // Simulate failed lookup.
465 results_.erase(hostname);
466 }
467 running_slave_count = running_slave_count_;
468 // Release lock, so slaves can finish up.
469 } 405 }
470 406
471 if (running_slave_count) { 407 pending_lookups_.erase(request);
472 slaves_have_work_.Broadcast(); // Slaves need to check for termination. 408 delete request;
473 409
474 DWORD result = WaitForMultipleObjects( 410 PreLockedScheduleLookups();
475 slave_count_,
476 thread_handles_,
477 TRUE, // Wait for all
478 static_cast<DWORD>(kShutdownWaitTime_.InMilliseconds()));
479
480 DCHECK(result != WAIT_TIMEOUT) << "Some slaves didn't stop";
481 if (WAIT_TIMEOUT == result)
482 return false;
483 }
484 {
485 AutoLock auto_lock(lock_);
486 while (0 < slave_count_--) {
487 if (0 == thread_ids_[slave_count_]) { // Thread terminated.
488 int result = CloseHandle(thread_handles_[slave_count_]);
489 CHECK(result);
490 thread_handles_[slave_count_] = 0;
491 delete slaves_[slave_count_];
492 slaves_[slave_count_] = NULL;
493 }
494 }
495 }
496 return true;
497 } 411 }
498 412
499 void DnsMaster::DiscardAllResults() { 413 void DnsMaster::DiscardAllResults() {
500 AutoLock auto_lock(lock_); 414 AutoLock auto_lock(lock_);
501 // Delete anything listed so far in this session that shows in about:dns. 415 // Delete anything listed so far in this session that shows in about:dns.
502 cache_eviction_map_.clear(); 416 cache_eviction_map_.clear();
503 cache_hits_.clear(); 417 cache_hits_.clear();
504 referrers_.clear(); 418 referrers_.clear();
505 419
506 420
507 // Try to delete anything in our work queue. 421 // Try to delete anything in our work queue.
508 while (!name_buffer_.empty()) { 422 while (!name_buffer_.empty()) {
509 // Emulate processing cycle as though host was not found. 423 // Emulate processing cycle as though host was not found.
510 std::string hostname = name_buffer_.front(); 424 std::string hostname = name_buffer_.front();
511 name_buffer_.pop(); 425 name_buffer_.pop();
512 DnsHostInfo* info = &results_[hostname]; 426 DnsHostInfo* info = &results_[hostname];
513 DCHECK(info->HasHostname(hostname)); 427 DCHECK(info->HasHostname(hostname));
514 info->SetAssignedState(); 428 info->SetAssignedState();
515 info->SetNoSuchNameState(); 429 info->SetNoSuchNameState();
516 } 430 }
517 // Now every result_ is either resolved, or is being worked on by a slave. 431 // Now every result_ is either resolved, or is being resolved
432 // (see LookupRequest).
518 433
519 // Step through result_, recording names of all hosts that can't be erased. 434 // Step through result_, recording names of all hosts that can't be erased.
520 // We can't erase anything being worked on by a slave. 435 // We can't erase anything being worked on.
521 Results assignees; 436 Results assignees;
522 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) { 437 for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
523 std::string hostname = it->first; 438 std::string hostname = it->first;
524 DnsHostInfo* info = &it->second; 439 DnsHostInfo* info = &it->second;
525 DCHECK(info->HasHostname(hostname)); 440 DCHECK(info->HasHostname(hostname));
526 if (info->is_assigned()) { 441 if (info->is_assigned()) {
527 info->SetPendingDeleteState(); 442 info->SetPendingDeleteState();
528 assignees[hostname] = *info; 443 assignees[hostname] = *info;
529 } 444 }
530 } 445 }
531 DCHECK(kSlaveCountMax >= assignees.size()); 446 DCHECK(assignees.size() <= kMaxConcurrentLookups);
532 results_.clear(); 447 results_.clear();
533 // Put back in the names being worked on by slaves. 448 // Put back in the names being worked on.
534 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) { 449 for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
535 DCHECK(it->second.is_marked_to_delete()); 450 DCHECK(it->second.is_marked_to_delete());
536 results_[it->first] = it->second; 451 results_[it->first] = it->second;
537 } 452 }
538 } 453 }
539 454
540 void DnsMaster::TrimReferrers() { 455 void DnsMaster::TrimReferrers() {
541 std::vector<std::string> hosts; 456 std::vector<std::string> hosts;
542 AutoLock auto_lock(lock_); 457 AutoLock auto_lock(lock_);
543 for (Referrers::const_iterator it = referrers_.begin(); 458 for (Referrers::const_iterator it = referrers_.begin();
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
577 Value* subresource_list; 492 Value* subresource_list;
578 if (!motivating_host->Get(1, &subresource_list)) 493 if (!motivating_host->Get(1, &subresource_list))
579 continue; 494 continue;
580 if (motivating_referrer.empty()) 495 if (motivating_referrer.empty())
581 continue; 496 continue;
582 referrers_[motivating_referrer].Deserialize(*subresource_list); 497 referrers_[motivating_referrer].Deserialize(*subresource_list);
583 } 498 }
584 } 499 }
585 500
586 } // namespace chrome_browser_net 501 } // namespace chrome_browser_net
587
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698