Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 "chrome/browser/prerender/prerender_manager.h" | 5 #include "chrome/browser/prerender/prerender_manager.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/bind_helpers.h" | 10 #include "base/bind_helpers.h" |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 77 "POST", | 77 "POST", |
| 78 "TRACE", | 78 "TRACE", |
| 79 }; | 79 }; |
| 80 | 80 |
| 81 // Length of prerender history, for display in chrome://net-internals | 81 // Length of prerender history, for display in chrome://net-internals |
| 82 const int kHistoryLength = 100; | 82 const int kHistoryLength = 100; |
| 83 | 83 |
| 84 // Indicates whether a Prerender has been cancelled such that we need | 84 // Indicates whether a Prerender has been cancelled such that we need |
| 85 // a dummy replacement for the purpose of recording the correct PPLT for | 85 // a dummy replacement for the purpose of recording the correct PPLT for |
| 86 // the Match Complete case. | 86 // the Match Complete case. |
| 87 // Traditionally, "Match" means that a prerendered page was actually visited & | |
|
dominich
2012/01/20 22:23:37
Match? or MatchComplete?
tburkard
2012/01/20 23:23:00
What I wrote is correct, I think.
On 2012/01/20 22
| |
| 88 // the prerender was used. Our goal is to have "Match" cases line up in the | |
| 89 // control group & the experiment group, so that we can make meaningful | |
| 90 // comparisons of improvements. However, in the control group, since we don't | |
| 91 // actually perform prerenders, many of the cancellation reasons cannot be | |
| 92 // detected. Therefore, in the Prerender group, when we cancel for one of these | |
| 93 // reasons, we keep track of a dummy Prerender representing what we would | |
| 94 // have in the control group. If that dummy prerender in the prerender group | |
| 95 // would then be swapped in (but isn't actually b/c it's a dummy), we record | |
| 96 // this as a MatchComplete. This allows us to compare MatchComplete's | |
| 97 // across Prerender & Control group which ideally should be lining up. | |
| 98 // This ensures that, by comparing a larger Match in Control vs. a smaller Match | |
|
dominich
2012/01/20 22:23:37
Match or MatchComplete?
tburkard
2012/01/20 23:23:00
Done.
| |
| 99 // in the Prerender group, there is no bias in terms of the page load times | |
| 100 // of the pages forming the difference between the two sets. | |
| 87 | 101 |
| 88 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) { | 102 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) { |
| 89 return final_status != FINAL_STATUS_USED && | 103 return final_status != FINAL_STATUS_USED && |
|
dominich
2012/01/20 22:23:37
Might this be easier as an inclusive check rather
tburkard
2012/01/20 23:23:00
No, because as new status'es are added, the defaul
| |
| 90 final_status != FINAL_STATUS_TIMED_OUT && | 104 final_status != FINAL_STATUS_TIMED_OUT && |
| 91 final_status != FINAL_STATUS_EVICTED && | 105 final_status != FINAL_STATUS_EVICTED && |
| 92 final_status != FINAL_STATUS_MANAGER_SHUTDOWN && | 106 final_status != FINAL_STATUS_MANAGER_SHUTDOWN && |
| 93 final_status != FINAL_STATUS_APP_TERMINATING && | 107 final_status != FINAL_STATUS_APP_TERMINATING && |
| 94 final_status != FINAL_STATUS_RENDERER_CRASHED && | 108 final_status != FINAL_STATUS_RENDERER_CRASHED && |
| 95 final_status != FINAL_STATUS_WINDOW_OPENER && | 109 final_status != FINAL_STATUS_WINDOW_OPENER && |
| 96 final_status != FINAL_STATUS_FRAGMENT_MISMATCH && | 110 final_status != FINAL_STATUS_FRAGMENT_MISMATCH && |
| 97 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED && | 111 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED && |
| 98 final_status != FINAL_STATUS_CANCELLED && | 112 final_status != FINAL_STATUS_CANCELLED && |
| 113 final_status != FINAL_STATUS_SESSION_STORAGE_NAMESPACE_MISMATCH && | |
| 114 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED && | |
| 115 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING && | |
| 99 final_status != FINAL_STATUS_MATCH_COMPLETE_DUMMY; | 116 final_status != FINAL_STATUS_MATCH_COMPLETE_DUMMY; |
| 100 } | 117 } |
| 101 | 118 |
| 102 } // namespace | 119 } // namespace |
| 103 | 120 |
| 104 class PrerenderManager::OnCloseTabContentsDeleter | 121 class PrerenderManager::OnCloseTabContentsDeleter |
| 105 : public content::WebContentsDelegate, | 122 : public content::WebContentsDelegate, |
| 106 public base::SupportsWeakPtr< | 123 public base::SupportsWeakPtr< |
| 107 PrerenderManager::OnCloseTabContentsDeleter> { | 124 PrerenderManager::OnCloseTabContentsDeleter> { |
| 108 public: | 125 public: |
| (...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 430 if (!prerender_contents || !prerender_contents->Init()) | 447 if (!prerender_contents || !prerender_contents->Init()) |
| 431 return false; | 448 return false; |
| 432 | 449 |
| 433 histograms_->RecordPrerenderStarted(origin); | 450 histograms_->RecordPrerenderStarted(origin); |
| 434 | 451 |
| 435 // TODO(cbentzel): Move invalid checks here instead of PrerenderContents? | 452 // TODO(cbentzel): Move invalid checks here instead of PrerenderContents? |
| 436 PrerenderContentsData data(prerender_contents, GetCurrentTime()); | 453 PrerenderContentsData data(prerender_contents, GetCurrentTime()); |
| 437 | 454 |
| 438 prerender_list_.push_back(data); | 455 prerender_list_.push_back(data); |
| 439 | 456 |
| 440 if (IsControlGroup()) { | 457 if (!IsControlGroup()) { |
| 441 data.contents_->set_final_status(FINAL_STATUS_CONTROL_GROUP); | |
| 442 } else { | |
| 443 last_prerender_start_time_ = GetCurrentTimeTicks(); | 458 last_prerender_start_time_ = GetCurrentTimeTicks(); |
| 444 data.contents_->StartPrerendering(source_render_view_host, | 459 data.contents_->StartPrerendering(source_render_view_host, |
| 445 session_storage_namespace); | 460 session_storage_namespace); |
| 446 } | 461 } |
| 447 while (prerender_list_.size() > config_.max_elements) { | 462 while (prerender_list_.size() > config_.max_elements) { |
| 448 data = prerender_list_.front(); | 463 data = prerender_list_.front(); |
| 449 prerender_list_.pop_front(); | 464 prerender_list_.pop_front(); |
| 450 data.contents_->Destroy(FINAL_STATUS_EVICTED); | 465 data.contents_->Destroy(FINAL_STATUS_EVICTED); |
| 451 } | 466 } |
| 452 StartSchedulingPeriodicCleanups(); | 467 StartSchedulingPeriodicCleanups(); |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 527 } | 542 } |
| 528 } | 543 } |
| 529 // Entry not found. | 544 // Entry not found. |
| 530 return NULL; | 545 return NULL; |
| 531 } | 546 } |
| 532 | 547 |
| 533 PrerenderContents* PrerenderManager::GetEntry(const GURL& url) { | 548 PrerenderContents* PrerenderManager::GetEntry(const GURL& url) { |
| 534 return GetEntryButNotSpecifiedWC(url, NULL); | 549 return GetEntryButNotSpecifiedWC(url, NULL); |
| 535 } | 550 } |
| 536 | 551 |
| 552 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed( | |
| 553 PrerenderContents* prerender_contents, | |
| 554 FinalStatus final_status) { | |
| 555 prerender_contents->set_mc_status(PrerenderContents::MC_REPLACED); | |
| 556 histograms_->RecordMatchCompleteFinalStatus( | |
| 557 prerender_contents->origin(), | |
| 558 prerender_contents->experiment_id(), | |
| 559 FINAL_STATUS_USED); | |
| 560 prerender_contents->Destroy(FINAL_STATUS_DEVTOOLS_ATTACHED); | |
|
dominich
2012/01/20 22:23:37
This should be |final_status|. You might need a te
tburkard
2012/01/20 23:23:00
Great catch. Haven't even run/written tests yet w
| |
| 561 } | |
| 562 | |
| 537 bool PrerenderManager::MaybeUsePrerenderedPage(WebContents* web_contents, | 563 bool PrerenderManager::MaybeUsePrerenderedPage(WebContents* web_contents, |
| 538 const GURL& url, | 564 const GURL& url, |
| 539 const GURL& opener_url) { | 565 const GURL& opener_url) { |
| 540 DCHECK(CalledOnValidThread()); | 566 DCHECK(CalledOnValidThread()); |
| 541 RecordNavigation(url); | 567 RecordNavigation(url); |
| 542 | 568 |
| 543 scoped_ptr<PrerenderContents> prerender_contents( | 569 scoped_ptr<PrerenderContents> prerender_contents( |
| 544 GetEntryButNotSpecifiedWC(url, web_contents)); | 570 GetEntryButNotSpecifiedWC(url, web_contents)); |
| 545 if (prerender_contents.get() == NULL) | 571 if (prerender_contents.get() == NULL) |
| 546 return false; | 572 return false; |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 560 bool url_matches = prerender_contents->MatchesURL(url, &matching_url); | 586 bool url_matches = prerender_contents->MatchesURL(url, &matching_url); |
| 561 DCHECK(url_matches); | 587 DCHECK(url_matches); |
| 562 if (url_matches && url.ref() != matching_url.ref()) { | 588 if (url_matches && url.ref() != matching_url.ref()) { |
| 563 prerender_contents.release()->Destroy(FINAL_STATUS_FRAGMENT_MISMATCH); | 589 prerender_contents.release()->Destroy(FINAL_STATUS_FRAGMENT_MISMATCH); |
| 564 return false; | 590 return false; |
| 565 } | 591 } |
| 566 | 592 |
| 567 // If we are just in the control group (which can be detected by noticing | 593 // If we are just in the control group (which can be detected by noticing |
| 568 // that prerendering hasn't even started yet), record that |web_contents| now | 594 // that prerendering hasn't even started yet), record that |web_contents| now |
| 569 // would be showing a prerendered contents, but otherwise, don't do anything. | 595 // would be showing a prerendered contents, but otherwise, don't do anything. |
| 570 if (!prerender_contents->prerendering_has_started()) { | 596 if (!prerender_contents->prerendering_has_started()) { |
|
dominich
2012/01/20 22:23:37
Why is this different to IsControlGroup?
tburkard
2012/01/20 23:23:00
I already answered this before, I said:
This is ne
| |
| 571 MarkWebContentsAsWouldBePrerendered(web_contents); | 597 MarkWebContentsAsWouldBePrerendered(web_contents); |
| 598 prerender_contents.release()->Destroy(FINAL_STATUS_USED); | |
| 572 return false; | 599 return false; |
| 573 } | 600 } |
| 574 | 601 |
| 575 // Don't use prerendered pages if debugger is attached to the tab. | 602 // Don't use prerendered pages if debugger is attached to the tab. |
| 576 // See http://crbug.com/98541 | 603 // See http://crbug.com/98541 |
| 577 if (content::DevToolsAgentHostRegistry::IsDebuggerAttached(web_contents)) { | 604 if (content::DevToolsAgentHostRegistry::IsDebuggerAttached(web_contents)) { |
| 578 prerender_contents.release()->Destroy(FINAL_STATUS_DEVTOOLS_ATTACHED); | 605 DestroyAndMarkMatchCompleteAsUsed(prerender_contents.release(), |
| 606 FINAL_STATUS_DEVTOOLS_ATTACHED); | |
| 579 return false; | 607 return false; |
| 580 } | 608 } |
| 581 | 609 |
| 582 // If the prerendered page is in the middle of a cross-site navigation, | 610 // If the prerendered page is in the middle of a cross-site navigation, |
| 583 // don't swap it in because there isn't a good way to merge histories. | 611 // don't swap it in because there isn't a good way to merge histories. |
| 584 if (prerender_contents->IsCrossSiteNavigationPending()) { | 612 if (prerender_contents->IsCrossSiteNavigationPending()) { |
| 585 prerender_contents.release()->Destroy( | 613 DestroyAndMarkMatchCompleteAsUsed( |
| 614 prerender_contents.release(), | |
| 586 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING); | 615 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING); |
| 587 return false; | 616 return false; |
| 588 } | 617 } |
| 589 | 618 |
| 590 // If the session storage namespaces don't match, cancel the prerender. | 619 // If the session storage namespaces don't match, cancel the prerender. |
| 591 RenderViewHost* old_render_view_host = web_contents->GetRenderViewHost(); | 620 RenderViewHost* old_render_view_host = web_contents->GetRenderViewHost(); |
| 592 RenderViewHost* new_render_view_host = | 621 RenderViewHost* new_render_view_host = |
| 593 prerender_contents->prerender_contents()->web_contents()-> | 622 prerender_contents->prerender_contents()->web_contents()-> |
| 594 GetRenderViewHost(); | 623 GetRenderViewHost(); |
| 595 DCHECK(old_render_view_host); | 624 DCHECK(old_render_view_host); |
| 596 DCHECK(new_render_view_host); | 625 DCHECK(new_render_view_host); |
| 597 if (old_render_view_host->session_storage_namespace() != | 626 if (old_render_view_host->session_storage_namespace() != |
| 598 new_render_view_host->session_storage_namespace()) { | 627 new_render_view_host->session_storage_namespace()) { |
| 599 prerender_contents.release()->Destroy( | 628 DestroyAndMarkMatchCompleteAsUsed( |
| 629 prerender_contents.release(), | |
| 600 FINAL_STATUS_SESSION_STORAGE_NAMESPACE_MISMATCH); | 630 FINAL_STATUS_SESSION_STORAGE_NAMESPACE_MISMATCH); |
| 601 return false; | 631 return false; |
| 602 } | 632 } |
| 603 | 633 |
| 604 // If we don't want to use prerenders at all, we are done. | 634 // If we don't want to use prerenders at all, we are done. |
| 605 // For bookkeeping purposes, we need to mark this TabContents to | 635 // For bookkeeping purposes, we need to mark this TabContents to |
| 606 // reflect that it would have been prerendered. | 636 // reflect that it would have been prerendered. |
| 607 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) { | 637 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) { |
| 608 MarkWebContentsAsWouldBePrerendered(web_contents); | 638 MarkWebContentsAsWouldBePrerendered(web_contents); |
| 609 prerender_contents.release()->Destroy(FINAL_STATUS_NO_USE_GROUP); | 639 prerender_contents.release()->Destroy(FINAL_STATUS_NO_USE_GROUP); |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 698 it != prerender_list_.end(); | 728 it != prerender_list_.end(); |
| 699 ++it) { | 729 ++it) { |
| 700 if (it->contents_ == entry) { | 730 if (it->contents_ == entry) { |
| 701 bool swapped_in_dummy_replacement = false; | 731 bool swapped_in_dummy_replacement = false; |
| 702 | 732 |
| 703 // If this PrerenderContents is being deleted due to a cancellation, | 733 // If this PrerenderContents is being deleted due to a cancellation, |
| 704 // we need to create a dummy replacement for PPLT accounting purposes | 734 // we need to create a dummy replacement for PPLT accounting purposes |
| 705 // for the Match Complete group. | 735 // for the Match Complete group. |
| 706 // This is the case if the cancellation is for any reason that would not | 736 // This is the case if the cancellation is for any reason that would not |
| 707 // occur in the control group case. | 737 // occur in the control group case. |
| 708 if (NeedMatchCompleteDummyForFinalStatus(final_status)) { | 738 if (entry->mc_status() == PrerenderContents::MC_DEFAULT && |
|
dominich
2012/01/20 22:23:37
if mc_status is only set from this method, this co
tburkard
2012/01/20 23:23:00
huh? why? Can you give an example?
A prerender
| |
| 739 NeedMatchCompleteDummyForFinalStatus(final_status)) { | |
| 709 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering. | 740 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering. |
| 710 // However, what if new conditions are added and | 741 // However, what if new conditions are added and |
| 711 // NeedMatchCompleteDummyForFinalStatus, is not being updated. Not sure | 742 // NeedMatchCompleteDummyForFinalStatus, is not being updated. Not sure |
| 712 // what's the best thing to do here. For now, I will just check whether | 743 // what's the best thing to do here. For now, I will just check whether |
| 713 // we are actually prerendering. | 744 // we are actually prerendering. |
| 714 if (ActuallyPrerendering()) { | 745 if (ActuallyPrerendering()) { |
| 746 entry->set_mc_status(PrerenderContents::MC_REPLACED); | |
| 715 PrerenderContents* dummy_replacement_prerender_contents = | 747 PrerenderContents* dummy_replacement_prerender_contents = |
| 716 CreatePrerenderContents( | 748 CreatePrerenderContents( |
| 717 entry->prerender_url(), | 749 entry->prerender_url(), |
| 718 entry->referrer(), | 750 entry->referrer(), |
| 719 entry->origin(), | 751 entry->origin(), |
| 720 entry->experiment_id()); | 752 entry->experiment_id()); |
| 721 if (dummy_replacement_prerender_contents && | 753 if (dummy_replacement_prerender_contents && |
| 722 dummy_replacement_prerender_contents->Init()) { | 754 dummy_replacement_prerender_contents->Init()) { |
| 723 dummy_replacement_prerender_contents-> | 755 dummy_replacement_prerender_contents-> |
| 724 AddAliasURLsFromOtherPrerenderContents(entry); | 756 AddAliasURLsFromOtherPrerenderContents(entry); |
| 757 dummy_replacement_prerender_contents->set_mc_status( | |
| 758 PrerenderContents::MC_REPLACEMENT); | |
| 725 it->contents_ = dummy_replacement_prerender_contents; | 759 it->contents_ = dummy_replacement_prerender_contents; |
| 726 it->contents_->set_final_status(FINAL_STATUS_MATCH_COMPLETE_DUMMY); | |
| 727 swapped_in_dummy_replacement = true; | 760 swapped_in_dummy_replacement = true; |
| 728 } | 761 } |
| 729 } | 762 } |
| 730 } | 763 } |
| 731 if (!swapped_in_dummy_replacement) | 764 if (!swapped_in_dummy_replacement) |
| 732 prerender_list_.erase(it); | 765 prerender_list_.erase(it); |
| 733 break; | 766 break; |
| 734 } | 767 } |
| 735 } | 768 } |
| 736 AddToHistory(entry); | 769 AddToHistory(entry); |
| (...skipping 355 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1092 void PrerenderManager::DestroyAllContents(FinalStatus final_status) { | 1125 void PrerenderManager::DestroyAllContents(FinalStatus final_status) { |
| 1093 DeleteOldTabContents(); | 1126 DeleteOldTabContents(); |
| 1094 while (!prerender_list_.empty()) { | 1127 while (!prerender_list_.empty()) { |
| 1095 PrerenderContentsData data = prerender_list_.front(); | 1128 PrerenderContentsData data = prerender_list_.front(); |
| 1096 prerender_list_.pop_front(); | 1129 prerender_list_.pop_front(); |
| 1097 data.contents_->Destroy(final_status); | 1130 data.contents_->Destroy(final_status); |
| 1098 } | 1131 } |
| 1099 DeletePendingDeleteEntries(); | 1132 DeletePendingDeleteEntries(); |
| 1100 } | 1133 } |
| 1101 | 1134 |
| 1135 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus( | |
| 1136 Origin origin, | |
| 1137 uint8 experiment_id, | |
| 1138 PrerenderContents::MatchCompleteStatus mc_status, | |
| 1139 FinalStatus final_status) const { | |
|
dominich
2012/01/20 22:23:37
MC_DEFAULT will be in both histograms. Is this exp
tburkard
2012/01/20 23:23:00
not sure why stuff keeps being repeated. i said e
| |
| 1140 if (mc_status != PrerenderContents::MC_REPLACEMENT) | |
| 1141 histograms_->RecordFinalStatus(origin, experiment_id, final_status); | |
| 1142 if (mc_status != PrerenderContents::MC_REPLACED) { | |
| 1143 histograms_->RecordMatchCompleteFinalStatus(origin, experiment_id, | |
| 1144 final_status); | |
| 1145 } | |
| 1146 } | |
| 1147 | |
| 1102 void PrerenderManager::RecordFinalStatus(Origin origin, | 1148 void PrerenderManager::RecordFinalStatus(Origin origin, |
| 1103 uint8 experiment_id, | 1149 uint8 experiment_id, |
| 1104 FinalStatus final_status) const { | 1150 FinalStatus final_status) const { |
| 1105 histograms_->RecordFinalStatus(origin, experiment_id, final_status); | 1151 RecordFinalStatusWithMatchCompleteStatus(origin, experiment_id, |
| 1152 PrerenderContents::MC_DEFAULT, | |
| 1153 final_status); | |
| 1106 } | 1154 } |
| 1107 | 1155 |
| 1156 | |
| 1108 PrerenderManager* FindPrerenderManagerUsingRenderProcessId( | 1157 PrerenderManager* FindPrerenderManagerUsingRenderProcessId( |
| 1109 int render_process_id) { | 1158 int render_process_id) { |
| 1110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 1159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 1111 content::RenderProcessHost* render_process_host = | 1160 content::RenderProcessHost* render_process_host = |
| 1112 content::RenderProcessHost::FromID(render_process_id); | 1161 content::RenderProcessHost::FromID(render_process_id); |
| 1113 // Each render process is guaranteed to only hold RenderViews owned by the | 1162 // Each render process is guaranteed to only hold RenderViews owned by the |
| 1114 // same BrowserContext. This is enforced by | 1163 // same BrowserContext. This is enforced by |
| 1115 // RenderProcessHost::GetExistingProcessHost. | 1164 // RenderProcessHost::GetExistingProcessHost. |
| 1116 if (!render_process_host || !render_process_host->GetBrowserContext()) | 1165 if (!render_process_host || !render_process_host->GetBrowserContext()) |
| 1117 return NULL; | 1166 return NULL; |
| 1118 Profile* profile = Profile::FromBrowserContext( | 1167 Profile* profile = Profile::FromBrowserContext( |
| 1119 render_process_host->GetBrowserContext()); | 1168 render_process_host->GetBrowserContext()); |
| 1120 if (!profile) | 1169 if (!profile) |
| 1121 return NULL; | 1170 return NULL; |
| 1122 return PrerenderManagerFactory::GetInstance()->GetForProfile(profile); | 1171 return PrerenderManagerFactory::GetInstance()->GetForProfile(profile); |
| 1123 } | 1172 } |
| 1124 | 1173 |
| 1125 } // namespace prerender | 1174 } // namespace prerender |
| OLD | NEW |