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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/prerender/prerender_contents.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/history/history_tab_helper.h"
15 #include "chrome/browser/prerender/prerender_field_trial.h"
16 #include "chrome/browser/prerender/prerender_final_status.h"
17 #include "chrome/browser/prerender/prerender_handle.h"
18 #include "chrome/browser/prerender/prerender_manager.h"
19 #include "chrome/browser/prerender/prerender_manager_factory.h"
20 #include "chrome/browser/prerender/prerender_resource_throttle.h"
21 #include "chrome/browser/prerender/prerender_tracker.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/tab_helpers.h"
25 #include "chrome/browser/ui/web_contents_sizer.h"
26 #include "chrome/common/prerender_messages.h"
27 #include "chrome/common/render_messages.h"
28 #include "chrome/common/url_constants.h"
29 #include "components/history/core/browser/history_types.h"
30 #include "content/public/browser/browser_child_process_host.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/browser/notification_service.h"
33 #include "content/public/browser/render_frame_host.h"
34 #include "content/public/browser/render_process_host.h"
35 #include "content/public/browser/render_view_host.h"
36 #include "content/public/browser/resource_request_details.h"
37 #include "content/public/browser/session_storage_namespace.h"
38 #include "content/public/browser/web_contents.h"
39 #include "content/public/browser/web_contents_delegate.h"
40 #include "content/public/common/frame_navigate_params.h"
41 #include "net/url_request/url_request_context_getter.h"
42 #include "ui/base/page_transition_types.h"
43 #include "ui/gfx/rect.h"
44
45 using content::BrowserThread;
46 using content::DownloadItem;
47 using content::OpenURLParams;
48 using content::RenderViewHost;
49 using content::ResourceRedirectDetails;
50 using content::ResourceType;
51 using content::SessionStorageNamespace;
52 using content::WebContents;
53
54 namespace prerender {
55
56 namespace {
57
58 // Internal cookie event.
59 // Whenever a prerender interacts with the cookie store, either sending
60 // existing cookies that existed before the prerender started, or when a cookie
61 // is changed, we record these events for histogramming purposes.
62 enum InternalCookieEvent {
63 INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND = 0,
64 INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE = 1,
65 INTERNAL_COOKIE_EVENT_OTHER_SEND = 2,
66 INTERNAL_COOKIE_EVENT_OTHER_CHANGE = 3,
67 INTERNAL_COOKIE_EVENT_MAX
68 };
69
70 // Indicates whether existing cookies were sent, and if they were third party
71 // cookies, and whether they were for blocking resources.
72 // Each value may be inclusive of previous values. We only care about the
73 // value with the highest index that has ever occurred in the course of a
74 // prerender.
75 enum CookieSendType {
76 COOKIE_SEND_TYPE_NONE = 0,
77 COOKIE_SEND_TYPE_FIRST_PARTY = 1,
78 COOKIE_SEND_TYPE_THIRD_PARTY = 2,
79 COOKIE_SEND_TYPE_THIRD_PARTY_BLOCKING_RESOURCE = 3,
80 COOKIE_SEND_TYPE_MAX
81 };
82
ResumeThrottles(std::vector<base::WeakPtr<PrerenderResourceThrottle>> throttles)83 void ResumeThrottles(
84 std::vector<base::WeakPtr<PrerenderResourceThrottle> > throttles) {
85 for (size_t i = 0; i < throttles.size(); i++) {
86 if (throttles[i])
87 throttles[i]->Resume();
88 }
89 }
90
91 } // namespace
92
93 // static
94 const int PrerenderContents::kNumCookieStatuses =
95 (1 << INTERNAL_COOKIE_EVENT_MAX);
96
97 // static
98 const int PrerenderContents::kNumCookieSendTypes = COOKIE_SEND_TYPE_MAX;
99
100 class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
101 public:
CreatePrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const content::Referrer & referrer,Origin origin,uint8 experiment_id)102 virtual PrerenderContents* CreatePrerenderContents(
103 PrerenderManager* prerender_manager, Profile* profile,
104 const GURL& url, const content::Referrer& referrer,
105 Origin origin, uint8 experiment_id) OVERRIDE {
106 return new PrerenderContents(prerender_manager, profile,
107 url, referrer, origin, experiment_id);
108 }
109 };
110
111 // WebContentsDelegateImpl -----------------------------------------------------
112
113 class PrerenderContents::WebContentsDelegateImpl
114 : public content::WebContentsDelegate {
115 public:
WebContentsDelegateImpl(PrerenderContents * prerender_contents)116 explicit WebContentsDelegateImpl(PrerenderContents* prerender_contents)
117 : prerender_contents_(prerender_contents) {
118 }
119
120 // content::WebContentsDelegate implementation:
OpenURLFromTab(WebContents * source,const OpenURLParams & params)121 virtual WebContents* OpenURLFromTab(WebContents* source,
122 const OpenURLParams& params) OVERRIDE {
123 // |OpenURLFromTab| is typically called when a frame performs a navigation
124 // that requires the browser to perform the transition instead of WebKit.
125 // Examples include prerendering a site that redirects to an app URL,
126 // or if --enable-strict-site-isolation is specified and the prerendered
127 // frame redirects to a different origin.
128 // TODO(cbentzel): Consider supporting this if it is a common case during
129 // prerenders.
130 prerender_contents_->Destroy(FINAL_STATUS_OPEN_URL);
131 return NULL;
132 }
133
CloseContents(content::WebContents * contents)134 virtual void CloseContents(content::WebContents* contents) OVERRIDE {
135 prerender_contents_->Destroy(FINAL_STATUS_CLOSED);
136 }
137
CanDownload(RenderViewHost * render_view_host,const GURL & url,const std::string & request_method,const base::Callback<void (bool)> & callback)138 virtual void CanDownload(
139 RenderViewHost* render_view_host,
140 const GURL& url,
141 const std::string& request_method,
142 const base::Callback<void(bool)>& callback) OVERRIDE {
143 prerender_contents_->Destroy(FINAL_STATUS_DOWNLOAD);
144 // Cancel the download.
145 callback.Run(false);
146 }
147
ShouldCreateWebContents(WebContents * web_contents,int route_id,WindowContainerType window_container_type,const base::string16 & frame_name,const GURL & target_url,const std::string & partition_id,SessionStorageNamespace * session_storage_namespace)148 virtual bool ShouldCreateWebContents(
149 WebContents* web_contents,
150 int route_id,
151 WindowContainerType window_container_type,
152 const base::string16& frame_name,
153 const GURL& target_url,
154 const std::string& partition_id,
155 SessionStorageNamespace* session_storage_namespace) OVERRIDE {
156 // Since we don't want to permit child windows that would have a
157 // window.opener property, terminate prerendering.
158 prerender_contents_->Destroy(FINAL_STATUS_CREATE_NEW_WINDOW);
159 // Cancel the popup.
160 return false;
161 }
162
OnGoToEntryOffset(int offset)163 virtual bool OnGoToEntryOffset(int offset) OVERRIDE {
164 // This isn't allowed because the history merge operation
165 // does not work if there are renderer issued challenges.
166 // TODO(cbentzel): Cancel in this case? May not need to do
167 // since render-issued offset navigations are not guaranteed,
168 // but indicates that the page cares about the history.
169 return false;
170 }
171
ShouldSuppressDialogs()172 virtual bool ShouldSuppressDialogs() OVERRIDE {
173 // We still want to show the user the message when they navigate to this
174 // page, so cancel this prerender.
175 prerender_contents_->Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
176 // Always suppress JavaScript messages if they're triggered by a page being
177 // prerendered.
178 return true;
179 }
180
RegisterProtocolHandler(WebContents * web_contents,const std::string & protocol,const GURL & url,bool user_gesture)181 virtual void RegisterProtocolHandler(WebContents* web_contents,
182 const std::string& protocol,
183 const GURL& url,
184 bool user_gesture) OVERRIDE {
185 // TODO(mmenke): Consider supporting this if it is a common case during
186 // prerenders.
187 prerender_contents_->Destroy(FINAL_STATUS_REGISTER_PROTOCOL_HANDLER);
188 }
189
GetSizeForNewRenderView(WebContents * web_contents) const190 virtual gfx::Size GetSizeForNewRenderView(
191 WebContents* web_contents) const OVERRIDE {
192 // Have to set the size of the RenderView on initialization to be sure it is
193 // set before the RenderView is hidden on all platforms (esp. Android).
194 return prerender_contents_->size_;
195 }
196
197 private:
198 PrerenderContents* prerender_contents_;
199 };
200
OnPrerenderStopLoading(PrerenderContents * contents)201 void PrerenderContents::Observer::OnPrerenderStopLoading(
202 PrerenderContents* contents) {
203 }
204
OnPrerenderDomContentLoaded(PrerenderContents * contents)205 void PrerenderContents::Observer::OnPrerenderDomContentLoaded(
206 PrerenderContents* contents) {
207 }
208
OnPrerenderCreatedMatchCompleteReplacement(PrerenderContents * contents,PrerenderContents * replacement)209 void PrerenderContents::Observer::OnPrerenderCreatedMatchCompleteReplacement(
210 PrerenderContents* contents, PrerenderContents* replacement) {
211 }
212
Observer()213 PrerenderContents::Observer::Observer() {
214 }
215
~Observer()216 PrerenderContents::Observer::~Observer() {
217 }
218
PrerenderContents(PrerenderManager * prerender_manager,Profile * profile,const GURL & url,const content::Referrer & referrer,Origin origin,uint8 experiment_id)219 PrerenderContents::PrerenderContents(
220 PrerenderManager* prerender_manager,
221 Profile* profile,
222 const GURL& url,
223 const content::Referrer& referrer,
224 Origin origin,
225 uint8 experiment_id)
226 : prerendering_has_started_(false),
227 session_storage_namespace_id_(-1),
228 prerender_manager_(prerender_manager),
229 prerender_url_(url),
230 referrer_(referrer),
231 profile_(profile),
232 page_id_(0),
233 has_stopped_loading_(false),
234 has_finished_loading_(false),
235 final_status_(FINAL_STATUS_MAX),
236 match_complete_status_(MATCH_COMPLETE_DEFAULT),
237 prerendering_has_been_cancelled_(false),
238 child_id_(-1),
239 route_id_(-1),
240 origin_(origin),
241 experiment_id_(experiment_id),
242 creator_child_id_(-1),
243 cookie_status_(0),
244 cookie_send_type_(COOKIE_SEND_TYPE_NONE),
245 network_bytes_(0) {
246 DCHECK(prerender_manager != NULL);
247 }
248
CreateMatchCompleteReplacement()249 PrerenderContents* PrerenderContents::CreateMatchCompleteReplacement() {
250 PrerenderContents* new_contents = prerender_manager_->CreatePrerenderContents(
251 prerender_url(), referrer(), origin(), experiment_id());
252
253 new_contents->load_start_time_ = load_start_time_;
254 new_contents->session_storage_namespace_id_ = session_storage_namespace_id_;
255 new_contents->set_match_complete_status(
256 PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING);
257
258 const bool did_init = new_contents->Init();
259 DCHECK(did_init);
260 DCHECK_EQ(alias_urls_.front(), new_contents->alias_urls_.front());
261 DCHECK_EQ(1u, new_contents->alias_urls_.size());
262 new_contents->alias_urls_ = alias_urls_;
263 // Erase all but the first alias URL; the replacement has adopted the
264 // remainder without increasing the renderer-side reference count.
265 alias_urls_.resize(1);
266 new_contents->set_match_complete_status(
267 PrerenderContents::MATCH_COMPLETE_REPLACEMENT);
268 NotifyPrerenderCreatedMatchCompleteReplacement(new_contents);
269 return new_contents;
270 }
271
Init()272 bool PrerenderContents::Init() {
273 return AddAliasURL(prerender_url_);
274 }
275
276 // static
CreateFactory()277 PrerenderContents::Factory* PrerenderContents::CreateFactory() {
278 return new PrerenderContentsFactoryImpl();
279 }
280
281 // static
FromWebContents(content::WebContents * web_contents)282 PrerenderContents* PrerenderContents::FromWebContents(
283 content::WebContents* web_contents) {
284 if (!web_contents)
285 return NULL;
286 PrerenderManager* prerender_manager = PrerenderManagerFactory::GetForProfile(
287 Profile::FromBrowserContext(web_contents->GetBrowserContext()));
288 if (!prerender_manager)
289 return NULL;
290 return prerender_manager->GetPrerenderContents(web_contents);
291 }
292
StartPrerendering(int creator_child_id,const gfx::Size & size,SessionStorageNamespace * session_storage_namespace,net::URLRequestContextGetter * request_context)293 void PrerenderContents::StartPrerendering(
294 int creator_child_id,
295 const gfx::Size& size,
296 SessionStorageNamespace* session_storage_namespace,
297 net::URLRequestContextGetter* request_context) {
298 DCHECK(profile_ != NULL);
299 DCHECK(!size.IsEmpty());
300 DCHECK(!prerendering_has_started_);
301 DCHECK(prerender_contents_.get() == NULL);
302 DCHECK_EQ(-1, creator_child_id_);
303 DCHECK(size_.IsEmpty());
304 DCHECK_EQ(1U, alias_urls_.size());
305
306 creator_child_id_ = creator_child_id;
307 session_storage_namespace_id_ = session_storage_namespace->id();
308 size_ = size;
309
310 DCHECK(load_start_time_.is_null());
311 load_start_time_ = base::TimeTicks::Now();
312 start_time_ = base::Time::Now();
313
314 // Everything after this point sets up the WebContents object and associated
315 // RenderView for the prerender page. Don't do this for members of the
316 // control group.
317 if (prerender_manager_->IsControlGroup(experiment_id()))
318 return;
319
320 if (origin_ == ORIGIN_LOCAL_PREDICTOR &&
321 IsLocalPredictorPrerenderAlwaysControlEnabled()) {
322 return;
323 }
324
325 prerendering_has_started_ = true;
326
327 alias_session_storage_namespace = session_storage_namespace->CreateAlias();
328 prerender_contents_.reset(
329 CreateWebContents(alias_session_storage_namespace.get()));
330 TabHelpers::AttachTabHelpers(prerender_contents_.get());
331 content::WebContentsObserver::Observe(prerender_contents_.get());
332
333 web_contents_delegate_.reset(new WebContentsDelegateImpl(this));
334 prerender_contents_.get()->SetDelegate(web_contents_delegate_.get());
335 // Set the size of the prerender WebContents.
336 ResizeWebContents(prerender_contents_.get(), size_);
337
338 child_id_ = GetRenderViewHost()->GetProcess()->GetID();
339 route_id_ = GetRenderViewHost()->GetRoutingID();
340
341 // Log transactions to see if we could merge session storage namespaces in
342 // the event of a mismatch.
343 alias_session_storage_namespace->AddTransactionLogProcessId(child_id_);
344
345 // Add the RenderProcessHost to the Prerender Manager.
346 prerender_manager()->AddPrerenderProcessHost(
347 GetRenderViewHost()->GetProcess());
348
349 // In the prerender tracker, create a Prerender Cookie Store to keep track of
350 // cookie changes performed by the prerender. Once the prerender is shown,
351 // the cookie changes will be committed to the actual cookie store,
352 // otherwise, they will be discarded.
353 // If |request_context| is NULL, the feature must be disabled, so the
354 // operation will not be performed.
355 if (request_context) {
356 BrowserThread::PostTask(
357 BrowserThread::IO, FROM_HERE,
358 base::Bind(&PrerenderTracker::AddPrerenderCookieStoreOnIOThread,
359 base::Unretained(prerender_manager()->prerender_tracker()),
360 GetRenderViewHost()->GetProcess()->GetID(),
361 make_scoped_refptr(request_context),
362 base::Bind(&PrerenderContents::Destroy,
363 AsWeakPtr(),
364 FINAL_STATUS_COOKIE_CONFLICT)));
365 }
366
367 NotifyPrerenderStart();
368
369 // Close ourselves when the application is shutting down.
370 notification_registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
371 content::NotificationService::AllSources());
372
373 // Register to inform new RenderViews that we're prerendering.
374 notification_registrar_.Add(
375 this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
376 content::Source<WebContents>(prerender_contents_.get()));
377
378 // Transfer over the user agent override.
379 prerender_contents_.get()->SetUserAgentOverride(
380 prerender_manager_->config().user_agent_override);
381
382 content::NavigationController::LoadURLParams load_url_params(
383 prerender_url_);
384 load_url_params.referrer = referrer_;
385 load_url_params.transition_type = ui::PAGE_TRANSITION_LINK;
386 if (origin_ == ORIGIN_OMNIBOX) {
387 load_url_params.transition_type = ui::PageTransitionFromInt(
388 ui::PAGE_TRANSITION_TYPED |
389 ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
390 } else if (origin_ == ORIGIN_INSTANT) {
391 load_url_params.transition_type = ui::PageTransitionFromInt(
392 ui::PAGE_TRANSITION_GENERATED |
393 ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
394 }
395 load_url_params.override_user_agent =
396 prerender_manager_->config().is_overriding_user_agent ?
397 content::NavigationController::UA_OVERRIDE_TRUE :
398 content::NavigationController::UA_OVERRIDE_FALSE;
399 prerender_contents_.get()->GetController().LoadURLWithParams(load_url_params);
400 }
401
GetChildId(int * child_id) const402 bool PrerenderContents::GetChildId(int* child_id) const {
403 CHECK(child_id);
404 DCHECK_GE(child_id_, -1);
405 *child_id = child_id_;
406 return child_id_ != -1;
407 }
408
GetRouteId(int * route_id) const409 bool PrerenderContents::GetRouteId(int* route_id) const {
410 CHECK(route_id);
411 DCHECK_GE(route_id_, -1);
412 *route_id = route_id_;
413 return route_id_ != -1;
414 }
415
SetFinalStatus(FinalStatus final_status)416 void PrerenderContents::SetFinalStatus(FinalStatus final_status) {
417 DCHECK_GE(final_status, FINAL_STATUS_USED);
418 DCHECK_LT(final_status, FINAL_STATUS_MAX);
419
420 DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
421
422 final_status_ = final_status;
423 }
424
~PrerenderContents()425 PrerenderContents::~PrerenderContents() {
426 DCHECK_NE(FINAL_STATUS_MAX, final_status());
427 DCHECK(
428 prerendering_has_been_cancelled() || final_status() == FINAL_STATUS_USED);
429 DCHECK_NE(ORIGIN_MAX, origin());
430 // Since a lot of prerenders terminate before any meaningful cookie action
431 // would have happened, only record the cookie status for prerenders who
432 // were used, cancelled, or timed out.
433 if (prerendering_has_started_ && final_status() == FINAL_STATUS_USED) {
434 prerender_manager_->RecordCookieStatus(origin(), experiment_id(),
435 cookie_status_);
436 prerender_manager_->RecordCookieSendType(origin(), experiment_id(),
437 cookie_send_type_);
438 }
439 prerender_manager_->RecordFinalStatusWithMatchCompleteStatus(
440 origin(), experiment_id(), match_complete_status(), final_status());
441
442 bool used = final_status() == FINAL_STATUS_USED ||
443 final_status() == FINAL_STATUS_WOULD_HAVE_BEEN_USED;
444 prerender_manager_->RecordNetworkBytes(origin(), used, network_bytes_);
445
446 // Broadcast the removal of aliases.
447 for (content::RenderProcessHost::iterator host_iterator =
448 content::RenderProcessHost::AllHostsIterator();
449 !host_iterator.IsAtEnd();
450 host_iterator.Advance()) {
451 content::RenderProcessHost* host = host_iterator.GetCurrentValue();
452 host->Send(new PrerenderMsg_OnPrerenderRemoveAliases(alias_urls_));
453 }
454
455 // If we still have a WebContents, clean up anything we need to and then
456 // destroy it.
457 if (prerender_contents_.get())
458 delete ReleasePrerenderContents();
459 }
460
AddObserver(Observer * observer)461 void PrerenderContents::AddObserver(Observer* observer) {
462 DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
463 observer_list_.AddObserver(observer);
464 }
465
RemoveObserver(Observer * observer)466 void PrerenderContents::RemoveObserver(Observer* observer) {
467 observer_list_.RemoveObserver(observer);
468 }
469
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)470 void PrerenderContents::Observe(int type,
471 const content::NotificationSource& source,
472 const content::NotificationDetails& details) {
473 switch (type) {
474 // TODO(davidben): Try to remove this in favor of relying on
475 // FINAL_STATUS_PROFILE_DESTROYED.
476 case chrome::NOTIFICATION_APP_TERMINATING:
477 Destroy(FINAL_STATUS_APP_TERMINATING);
478 return;
479
480 case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
481 if (prerender_contents_.get()) {
482 DCHECK_EQ(content::Source<WebContents>(source).ptr(),
483 prerender_contents_.get());
484
485 content::Details<RenderViewHost> new_render_view_host(details);
486 OnRenderViewHostCreated(new_render_view_host.ptr());
487
488 // Make sure the size of the RenderViewHost has been passed to the new
489 // RenderView. Otherwise, the size may not be sent until the
490 // RenderViewReady event makes it from the render process to the UI
491 // thread of the browser process. When the RenderView receives its
492 // size, is also sets itself to be visible, which would then break the
493 // visibility API.
494 new_render_view_host->WasResized();
495 prerender_contents_->WasHidden();
496 }
497 break;
498 }
499
500 default:
501 NOTREACHED() << "Unexpected notification sent.";
502 break;
503 }
504 }
505
OnRenderViewHostCreated(RenderViewHost * new_render_view_host)506 void PrerenderContents::OnRenderViewHostCreated(
507 RenderViewHost* new_render_view_host) {
508 }
509
CreateWebContents(SessionStorageNamespace * session_storage_namespace)510 WebContents* PrerenderContents::CreateWebContents(
511 SessionStorageNamespace* session_storage_namespace) {
512 // TODO(ajwong): Remove the temporary map once prerendering is aware of
513 // multiple session storage namespaces per tab.
514 content::SessionStorageNamespaceMap session_storage_namespace_map;
515 session_storage_namespace_map[std::string()] = session_storage_namespace;
516 return WebContents::CreateWithSessionStorage(
517 WebContents::CreateParams(profile_), session_storage_namespace_map);
518 }
519
NotifyPrerenderStart()520 void PrerenderContents::NotifyPrerenderStart() {
521 DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
522 FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStart(this));
523 }
524
NotifyPrerenderStopLoading()525 void PrerenderContents::NotifyPrerenderStopLoading() {
526 FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStopLoading(this));
527 }
528
NotifyPrerenderDomContentLoaded()529 void PrerenderContents::NotifyPrerenderDomContentLoaded() {
530 FOR_EACH_OBSERVER(Observer, observer_list_,
531 OnPrerenderDomContentLoaded(this));
532 }
533
NotifyPrerenderStop()534 void PrerenderContents::NotifyPrerenderStop() {
535 DCHECK_NE(FINAL_STATUS_MAX, final_status_);
536 FOR_EACH_OBSERVER(Observer, observer_list_, OnPrerenderStop(this));
537 observer_list_.Clear();
538 }
539
NotifyPrerenderCreatedMatchCompleteReplacement(PrerenderContents * replacement)540 void PrerenderContents::NotifyPrerenderCreatedMatchCompleteReplacement(
541 PrerenderContents* replacement) {
542 FOR_EACH_OBSERVER(Observer, observer_list_,
543 OnPrerenderCreatedMatchCompleteReplacement(this,
544 replacement));
545 }
546
OnMessageReceived(const IPC::Message & message)547 bool PrerenderContents::OnMessageReceived(const IPC::Message& message) {
548 bool handled = true;
549 // The following messages we do want to consume.
550 IPC_BEGIN_MESSAGE_MAP(PrerenderContents, message)
551 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CancelPrerenderForPrinting,
552 OnCancelPrerenderForPrinting)
553 IPC_MESSAGE_UNHANDLED(handled = false)
554 IPC_END_MESSAGE_MAP()
555
556 return handled;
557 }
558
CheckURL(const GURL & url)559 bool PrerenderContents::CheckURL(const GURL& url) {
560 if (!url.SchemeIsHTTPOrHTTPS()) {
561 DCHECK_NE(MATCH_COMPLETE_REPLACEMENT_PENDING, match_complete_status_);
562 Destroy(FINAL_STATUS_UNSUPPORTED_SCHEME);
563 return false;
564 }
565 if (match_complete_status_ != MATCH_COMPLETE_REPLACEMENT_PENDING &&
566 prerender_manager_->HasRecentlyBeenNavigatedTo(origin(), url)) {
567 Destroy(FINAL_STATUS_RECENTLY_VISITED);
568 return false;
569 }
570 return true;
571 }
572
AddAliasURL(const GURL & url)573 bool PrerenderContents::AddAliasURL(const GURL& url) {
574 if (!CheckURL(url))
575 return false;
576
577 alias_urls_.push_back(url);
578
579 for (content::RenderProcessHost::iterator host_iterator =
580 content::RenderProcessHost::AllHostsIterator();
581 !host_iterator.IsAtEnd();
582 host_iterator.Advance()) {
583 content::RenderProcessHost* host = host_iterator.GetCurrentValue();
584 host->Send(new PrerenderMsg_OnPrerenderAddAlias(url));
585 }
586
587 return true;
588 }
589
Matches(const GURL & url,const SessionStorageNamespace * session_storage_namespace) const590 bool PrerenderContents::Matches(
591 const GURL& url,
592 const SessionStorageNamespace* session_storage_namespace) const {
593 if (session_storage_namespace &&
594 session_storage_namespace_id_ != session_storage_namespace->id()) {
595 return false;
596 }
597 return std::count_if(alias_urls_.begin(), alias_urls_.end(),
598 std::bind2nd(std::equal_to<GURL>(), url)) != 0;
599 }
600
RenderProcessGone(base::TerminationStatus status)601 void PrerenderContents::RenderProcessGone(base::TerminationStatus status) {
602 Destroy(FINAL_STATUS_RENDERER_CRASHED);
603 }
604
RenderFrameCreated(content::RenderFrameHost * render_frame_host)605 void PrerenderContents::RenderFrameCreated(
606 content::RenderFrameHost* render_frame_host) {
607 // When a new RenderFrame is created for a prerendering WebContents, tell the
608 // new RenderFrame it's being used for prerendering before any navigations
609 // occur. Note that this is always triggered before the first navigation, so
610 // there's no need to send the message just after the WebContents is created.
611 render_frame_host->Send(new PrerenderMsg_SetIsPrerendering(
612 render_frame_host->GetRoutingID(), true));
613 }
614
DidStopLoading(content::RenderViewHost * render_view_host)615 void PrerenderContents::DidStopLoading(
616 content::RenderViewHost* render_view_host) {
617 has_stopped_loading_ = true;
618 NotifyPrerenderStopLoading();
619 }
620
DocumentLoadedInFrame(content::RenderFrameHost * render_frame_host)621 void PrerenderContents::DocumentLoadedInFrame(
622 content::RenderFrameHost* render_frame_host) {
623 if (!render_frame_host->GetParent())
624 NotifyPrerenderDomContentLoaded();
625 }
626
DidStartProvisionalLoadForFrame(content::RenderFrameHost * render_frame_host,const GURL & validated_url,bool is_error_page,bool is_iframe_srcdoc)627 void PrerenderContents::DidStartProvisionalLoadForFrame(
628 content::RenderFrameHost* render_frame_host,
629 const GURL& validated_url,
630 bool is_error_page,
631 bool is_iframe_srcdoc) {
632 if (!render_frame_host->GetParent()) {
633 if (!CheckURL(validated_url))
634 return;
635
636 // Usually, this event fires if the user clicks or enters a new URL.
637 // Neither of these can happen in the case of an invisible prerender.
638 // So the cause is: Some JavaScript caused a new URL to be loaded. In that
639 // case, the spinner would start again in the browser, so we must reset
640 // has_stopped_loading_ so that the spinner won't be stopped.
641 has_stopped_loading_ = false;
642 has_finished_loading_ = false;
643 }
644 }
645
DidFinishLoad(content::RenderFrameHost * render_frame_host,const GURL & validated_url)646 void PrerenderContents::DidFinishLoad(
647 content::RenderFrameHost* render_frame_host,
648 const GURL& validated_url) {
649 if (!render_frame_host->GetParent())
650 has_finished_loading_ = true;
651 }
652
DidNavigateMainFrame(const content::LoadCommittedDetails & details,const content::FrameNavigateParams & params)653 void PrerenderContents::DidNavigateMainFrame(
654 const content::LoadCommittedDetails& details,
655 const content::FrameNavigateParams& params) {
656 // If the prerender made a second navigation entry, abort the prerender. This
657 // avoids having to correctly implement a complex history merging case (this
658 // interacts with location.replace) and correctly synchronize with the
659 // renderer. The final status may be monitored to see we need to revisit this
660 // decision. This does not affect client redirects as those do not push new
661 // history entries. (Calls to location.replace, navigations before onload, and
662 // <meta http-equiv=refresh> with timeouts under 1 second do not create
663 // entries in Blink.)
664 if (prerender_contents_->GetController().GetEntryCount() > 1) {
665 Destroy(FINAL_STATUS_NEW_NAVIGATION_ENTRY);
666 return;
667 }
668
669 // Add each redirect as an alias. |params.url| is included in
670 // |params.redirects|.
671 //
672 // TODO(davidben): We do not correctly patch up history for renderer-initated
673 // navigations which add history entries. http://crbug.com/305660.
674 for (size_t i = 0; i < params.redirects.size(); i++) {
675 if (!AddAliasURL(params.redirects[i]))
676 return;
677 }
678 }
679
DidGetRedirectForResourceRequest(RenderViewHost * render_view_host,const content::ResourceRedirectDetails & details)680 void PrerenderContents::DidGetRedirectForResourceRequest(
681 RenderViewHost* render_view_host,
682 const content::ResourceRedirectDetails& details) {
683 // DidGetRedirectForResourceRequest can come for any resource on a page. If
684 // it's a redirect on the top-level resource, the name needs to be remembered
685 // for future matching, and if it redirects to an https resource, it needs to
686 // be canceled. If a subresource is redirected, nothing changes.
687 if (details.resource_type != content::RESOURCE_TYPE_MAIN_FRAME)
688 return;
689 CheckURL(details.new_url);
690 }
691
Destroy(FinalStatus final_status)692 void PrerenderContents::Destroy(FinalStatus final_status) {
693 DCHECK_NE(final_status, FINAL_STATUS_USED);
694
695 if (prerendering_has_been_cancelled_)
696 return;
697
698 SetFinalStatus(final_status);
699
700 prerendering_has_been_cancelled_ = true;
701 prerender_manager_->AddToHistory(this);
702 prerender_manager_->MoveEntryToPendingDelete(this, final_status);
703
704 // Note that if this PrerenderContents was made into a MatchComplete
705 // replacement by MoveEntryToPendingDelete, NotifyPrerenderStop will
706 // not reach the PrerenderHandle. Rather
707 // OnPrerenderCreatedMatchCompleteReplacement will propogate that
708 // information to the referer.
709 if (!prerender_manager_->IsControlGroup(experiment_id()) &&
710 (prerendering_has_started() ||
711 match_complete_status() == MATCH_COMPLETE_REPLACEMENT)) {
712 NotifyPrerenderStop();
713 }
714 }
715
MaybeGetProcessMetrics()716 base::ProcessMetrics* PrerenderContents::MaybeGetProcessMetrics() {
717 if (process_metrics_.get() == NULL) {
718 // If a PrenderContents hasn't started prerending, don't be fully formed.
719 if (!GetRenderViewHost() || !GetRenderViewHost()->GetProcess())
720 return NULL;
721 base::ProcessHandle handle = GetRenderViewHost()->GetProcess()->GetHandle();
722 if (handle == base::kNullProcessHandle)
723 return NULL;
724 #if !defined(OS_MACOSX)
725 process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
726 #else
727 process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(
728 handle,
729 content::BrowserChildProcessHost::GetPortProvider()));
730 #endif
731 }
732
733 return process_metrics_.get();
734 }
735
DestroyWhenUsingTooManyResources()736 void PrerenderContents::DestroyWhenUsingTooManyResources() {
737 base::ProcessMetrics* metrics = MaybeGetProcessMetrics();
738 if (metrics == NULL)
739 return;
740
741 size_t private_bytes, shared_bytes;
742 if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes) &&
743 private_bytes > prerender_manager_->config().max_bytes) {
744 Destroy(FINAL_STATUS_MEMORY_LIMIT_EXCEEDED);
745 }
746 }
747
ReleasePrerenderContents()748 WebContents* PrerenderContents::ReleasePrerenderContents() {
749 prerender_contents_->SetDelegate(NULL);
750 content::WebContentsObserver::Observe(NULL);
751 if (alias_session_storage_namespace.get())
752 alias_session_storage_namespace->RemoveTransactionLogProcessId(child_id_);
753 return prerender_contents_.release();
754 }
755
GetRenderViewHostMutable()756 RenderViewHost* PrerenderContents::GetRenderViewHostMutable() {
757 return const_cast<RenderViewHost*>(GetRenderViewHost());
758 }
759
GetRenderViewHost() const760 const RenderViewHost* PrerenderContents::GetRenderViewHost() const {
761 if (!prerender_contents_.get())
762 return NULL;
763 return prerender_contents_->GetRenderViewHost();
764 }
765
DidNavigate(const history::HistoryAddPageArgs & add_page_args)766 void PrerenderContents::DidNavigate(
767 const history::HistoryAddPageArgs& add_page_args) {
768 add_page_vector_.push_back(add_page_args);
769 }
770
CommitHistory(WebContents * tab)771 void PrerenderContents::CommitHistory(WebContents* tab) {
772 HistoryTabHelper* history_tab_helper = HistoryTabHelper::FromWebContents(tab);
773 for (size_t i = 0; i < add_page_vector_.size(); ++i)
774 history_tab_helper->UpdateHistoryForNavigation(add_page_vector_[i]);
775 }
776
GetAsValue() const777 base::Value* PrerenderContents::GetAsValue() const {
778 if (!prerender_contents_.get())
779 return NULL;
780 base::DictionaryValue* dict_value = new base::DictionaryValue();
781 dict_value->SetString("url", prerender_url_.spec());
782 base::TimeTicks current_time = base::TimeTicks::Now();
783 base::TimeDelta duration = current_time - load_start_time_;
784 dict_value->SetInteger("duration", duration.InSeconds());
785 dict_value->SetBoolean("is_loaded", prerender_contents_ &&
786 !prerender_contents_->IsLoading());
787 return dict_value;
788 }
789
IsCrossSiteNavigationPending() const790 bool PrerenderContents::IsCrossSiteNavigationPending() const {
791 if (!prerender_contents_)
792 return false;
793 return (prerender_contents_->GetSiteInstance() !=
794 prerender_contents_->GetPendingSiteInstance());
795 }
796
PrepareForUse()797 void PrerenderContents::PrepareForUse() {
798 SetFinalStatus(FINAL_STATUS_USED);
799
800 if (prerender_contents_.get()) {
801 prerender_contents_->SendToAllFrames(
802 new PrerenderMsg_SetIsPrerendering(MSG_ROUTING_NONE, false));
803 }
804
805 NotifyPrerenderStop();
806
807 BrowserThread::PostTask(
808 BrowserThread::IO,
809 FROM_HERE,
810 base::Bind(&ResumeThrottles, resource_throttles_));
811 resource_throttles_.clear();
812 }
813
GetSessionStorageNamespace() const814 SessionStorageNamespace* PrerenderContents::GetSessionStorageNamespace() const {
815 if (!prerender_contents())
816 return NULL;
817 return prerender_contents()->GetController().
818 GetDefaultSessionStorageNamespace();
819 }
820
OnCancelPrerenderForPrinting()821 void PrerenderContents::OnCancelPrerenderForPrinting() {
822 Destroy(FINAL_STATUS_WINDOW_PRINT);
823 }
824
RecordCookieEvent(CookieEvent event,bool is_main_frame_http_request,bool is_third_party_cookie,bool is_for_blocking_resource,base::Time earliest_create_date)825 void PrerenderContents::RecordCookieEvent(CookieEvent event,
826 bool is_main_frame_http_request,
827 bool is_third_party_cookie,
828 bool is_for_blocking_resource,
829 base::Time earliest_create_date) {
830 // We don't care about sent cookies that were created after this prerender
831 // started.
832 // The reason is that for the purpose of the histograms emitted, we only care
833 // about cookies that existed before the prerender was started, but not
834 // about cookies that were created as part of the prerender. Using the
835 // earliest creation timestamp of all cookies provided by the cookie monster
836 // is a heuristic that yields the desired result pretty closely.
837 // In particular, we pretend no other WebContents make changes to the cookies
838 // relevant to the prerender, which may not actually always be the case, but
839 // hopefully most of the times.
840 if (event == COOKIE_EVENT_SEND && earliest_create_date > start_time_)
841 return;
842
843 InternalCookieEvent internal_event = INTERNAL_COOKIE_EVENT_MAX;
844
845 if (is_main_frame_http_request) {
846 if (event == COOKIE_EVENT_SEND) {
847 internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_SEND;
848 } else {
849 internal_event = INTERNAL_COOKIE_EVENT_MAIN_FRAME_CHANGE;
850 }
851 } else {
852 if (event == COOKIE_EVENT_SEND) {
853 internal_event = INTERNAL_COOKIE_EVENT_OTHER_SEND;
854 } else {
855 internal_event = INTERNAL_COOKIE_EVENT_OTHER_CHANGE;
856 }
857 }
858
859 DCHECK_GE(internal_event, 0);
860 DCHECK_LT(internal_event, INTERNAL_COOKIE_EVENT_MAX);
861
862 cookie_status_ |= (1 << internal_event);
863
864 DCHECK_GE(cookie_status_, 0);
865 DCHECK_LT(cookie_status_, kNumCookieStatuses);
866
867 CookieSendType send_type = COOKIE_SEND_TYPE_NONE;
868 if (event == COOKIE_EVENT_SEND) {
869 if (!is_third_party_cookie) {
870 send_type = COOKIE_SEND_TYPE_FIRST_PARTY;
871 } else {
872 if (is_for_blocking_resource) {
873 send_type = COOKIE_SEND_TYPE_THIRD_PARTY_BLOCKING_RESOURCE;
874 } else {
875 send_type = COOKIE_SEND_TYPE_THIRD_PARTY;
876 }
877 }
878 }
879 DCHECK_GE(send_type, 0);
880 DCHECK_LT(send_type, COOKIE_SEND_TYPE_MAX);
881
882 if (cookie_send_type_ < send_type)
883 cookie_send_type_ = send_type;
884 }
885
AddResourceThrottle(const base::WeakPtr<PrerenderResourceThrottle> & throttle)886 void PrerenderContents::AddResourceThrottle(
887 const base::WeakPtr<PrerenderResourceThrottle>& throttle) {
888 resource_throttles_.push_back(throttle);
889 }
890
AddNetworkBytes(int64 bytes)891 void PrerenderContents::AddNetworkBytes(int64 bytes) {
892 network_bytes_ += bytes;
893 }
894
895 } // namespace prerender
896