1// Copyright 2013 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 "base/command_line.h"
6#include "base/files/file_path.h"
7#include "base/strings/utf_string_conversions.h"
8#include "base/time/time.h"
9#include "content/browser/frame_host/cross_site_transferring_request.h"
10#include "content/browser/frame_host/navigation_before_commit_info.h"
11#include "content/browser/frame_host/navigation_controller_impl.h"
12#include "content/browser/frame_host/navigation_entry_impl.h"
13#include "content/browser/frame_host/navigation_request.h"
14#include "content/browser/frame_host/navigator.h"
15#include "content/browser/frame_host/navigator_impl.h"
16#include "content/browser/frame_host/render_frame_host_manager.h"
17#include "content/browser/site_instance_impl.h"
18#include "content/browser/webui/web_ui_controller_factory_registry.h"
19#include "content/common/view_messages.h"
20#include "content/public/browser/notification_details.h"
21#include "content/public/browser/notification_service.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/browser/notification_types.h"
24#include "content/public/browser/render_process_host.h"
25#include "content/public/browser/render_widget_host_iterator.h"
26#include "content/public/browser/web_contents_delegate.h"
27#include "content/public/browser/web_contents_observer.h"
28#include "content/public/browser/web_ui_controller.h"
29#include "content/public/common/bindings_policy.h"
30#include "content/public/common/content_switches.h"
31#include "content/public/common/javascript_message_type.h"
32#include "content/public/common/url_constants.h"
33#include "content/public/common/url_utils.h"
34#include "content/public/test/mock_render_process_host.h"
35#include "content/public/test/test_notification_tracker.h"
36#include "content/test/test_content_browser_client.h"
37#include "content/test/test_content_client.h"
38#include "content/test/test_render_frame_host.h"
39#include "content/test/test_render_view_host.h"
40#include "content/test/test_web_contents.h"
41#include "testing/gtest/include/gtest/gtest.h"
42#include "ui/base/page_transition_types.h"
43
44namespace content {
45namespace {
46
47class RenderFrameHostManagerTestWebUIControllerFactory
48    : public WebUIControllerFactory {
49 public:
50  RenderFrameHostManagerTestWebUIControllerFactory()
51    : should_create_webui_(false) {
52  }
53  virtual ~RenderFrameHostManagerTestWebUIControllerFactory() {}
54
55  void set_should_create_webui(bool should_create_webui) {
56    should_create_webui_ = should_create_webui;
57  }
58
59  // WebUIFactory implementation.
60  virtual WebUIController* CreateWebUIControllerForURL(
61      WebUI* web_ui, const GURL& url) const OVERRIDE {
62    if (!(should_create_webui_ && HasWebUIScheme(url)))
63      return NULL;
64    return new WebUIController(web_ui);
65  }
66
67   virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
68      const GURL& url) const OVERRIDE {
69    return WebUI::kNoWebUI;
70  }
71
72  virtual bool UseWebUIForURL(BrowserContext* browser_context,
73                              const GURL& url) const OVERRIDE {
74    return HasWebUIScheme(url);
75  }
76
77  virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
78                                      const GURL& url) const OVERRIDE {
79    return HasWebUIScheme(url);
80  }
81
82 private:
83  bool should_create_webui_;
84
85  DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerTestWebUIControllerFactory);
86};
87
88class BeforeUnloadFiredWebContentsDelegate : public WebContentsDelegate {
89 public:
90  BeforeUnloadFiredWebContentsDelegate() {}
91  virtual ~BeforeUnloadFiredWebContentsDelegate() {}
92
93  virtual void BeforeUnloadFired(WebContents* web_contents,
94                                 bool proceed,
95                                 bool* proceed_to_fire_unload) OVERRIDE {
96    *proceed_to_fire_unload = proceed;
97  }
98
99 private:
100  DISALLOW_COPY_AND_ASSIGN(BeforeUnloadFiredWebContentsDelegate);
101};
102
103// This observer keeps track of the last deleted RenderViewHost to avoid
104// accessing it and causing use-after-free condition.
105class RenderViewHostDeletedObserver : public WebContentsObserver {
106 public:
107  RenderViewHostDeletedObserver(RenderViewHost* rvh)
108      : WebContentsObserver(WebContents::FromRenderViewHost(rvh)),
109        process_id_(rvh->GetProcess()->GetID()),
110        routing_id_(rvh->GetRoutingID()),
111        deleted_(false) {
112  }
113
114  virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE {
115    if (render_view_host->GetProcess()->GetID() == process_id_ &&
116        render_view_host->GetRoutingID() == routing_id_) {
117      deleted_ = true;
118    }
119  }
120
121  bool deleted() {
122    return deleted_;
123  }
124
125 private:
126  int process_id_;
127  int routing_id_;
128  bool deleted_;
129
130  DISALLOW_COPY_AND_ASSIGN(RenderViewHostDeletedObserver);
131};
132
133// This observer keeps track of the last deleted RenderFrameHost to avoid
134// accessing it and causing use-after-free condition.
135class RenderFrameHostDeletedObserver : public WebContentsObserver {
136 public:
137  RenderFrameHostDeletedObserver(RenderFrameHost* rfh)
138      : WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
139        process_id_(rfh->GetProcess()->GetID()),
140        routing_id_(rfh->GetRoutingID()),
141        deleted_(false) {
142  }
143
144  virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE {
145    if (render_frame_host->GetProcess()->GetID() == process_id_ &&
146        render_frame_host->GetRoutingID() == routing_id_) {
147      deleted_ = true;
148    }
149  }
150
151  bool deleted() {
152    return deleted_;
153  }
154
155 private:
156  int process_id_;
157  int routing_id_;
158  bool deleted_;
159
160  DISALLOW_COPY_AND_ASSIGN(RenderFrameHostDeletedObserver);
161};
162
163
164// This observer is used to check whether IPC messages are being filtered for
165// swapped out RenderFrameHost objects. It observes the plugin crash and favicon
166// update events, which the FilterMessagesWhileSwappedOut test simulates being
167// sent. The test is successful if the event is not observed.
168// See http://crbug.com/351815
169class PluginFaviconMessageObserver : public WebContentsObserver {
170 public:
171  PluginFaviconMessageObserver(WebContents* web_contents)
172      : WebContentsObserver(web_contents),
173        plugin_crashed_(false),
174        favicon_received_(false) { }
175
176  virtual void PluginCrashed(const base::FilePath& plugin_path,
177                             base::ProcessId plugin_pid) OVERRIDE {
178    plugin_crashed_ = true;
179  }
180
181  virtual void DidUpdateFaviconURL(
182      const std::vector<FaviconURL>& candidates) OVERRIDE {
183    favicon_received_ = true;
184  }
185
186  bool plugin_crashed() {
187    return plugin_crashed_;
188  }
189
190  bool favicon_received() {
191    return favicon_received_;
192  }
193
194 private:
195  bool plugin_crashed_;
196  bool favicon_received_;
197
198  DISALLOW_COPY_AND_ASSIGN(PluginFaviconMessageObserver);
199};
200
201// Ensures that RenderFrameDeleted and RenderFrameCreated are called in a
202// consistent manner.
203class FrameLifetimeConsistencyChecker : public WebContentsObserver {
204 public:
205  explicit FrameLifetimeConsistencyChecker(WebContentsImpl* web_contents)
206      : WebContentsObserver(web_contents) {
207    RenderViewCreated(web_contents->GetRenderViewHost());
208    RenderFrameCreated(web_contents->GetMainFrame());
209  }
210
211  virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE {
212    std::pair<int, int> routing_pair =
213        std::make_pair(render_frame_host->GetProcess()->GetID(),
214                       render_frame_host->GetRoutingID());
215    bool was_live_already = !live_routes_.insert(routing_pair).second;
216    bool was_used_before = deleted_routes_.count(routing_pair) != 0;
217
218    if (was_live_already) {
219      FAIL() << "RenderFrameCreated called more than once for routing pair: "
220             << Format(render_frame_host);
221    } else if (was_used_before) {
222      FAIL() << "RenderFrameCreated called for routing pair "
223             << Format(render_frame_host) << " that was previously deleted.";
224    }
225  }
226
227  virtual void RenderFrameDeleted(RenderFrameHost* render_frame_host) OVERRIDE {
228    std::pair<int, int> routing_pair =
229        std::make_pair(render_frame_host->GetProcess()->GetID(),
230                       render_frame_host->GetRoutingID());
231    bool was_live = live_routes_.erase(routing_pair);
232    bool was_dead_already = !deleted_routes_.insert(routing_pair).second;
233
234    if (was_dead_already) {
235      FAIL() << "RenderFrameDeleted called more than once for routing pair "
236             << Format(render_frame_host);
237    } else if (!was_live) {
238      FAIL() << "RenderFrameDeleted called for routing pair "
239             << Format(render_frame_host)
240             << " for which RenderFrameCreated was never called";
241    }
242  }
243
244 private:
245  std::string Format(RenderFrameHost* render_frame_host) {
246    return base::StringPrintf(
247        "(%d, %d -> %s )",
248        render_frame_host->GetProcess()->GetID(),
249        render_frame_host->GetRoutingID(),
250        render_frame_host->GetSiteInstance()->GetSiteURL().spec().c_str());
251  }
252  std::set<std::pair<int, int> > live_routes_;
253  std::set<std::pair<int, int> > deleted_routes_;
254};
255
256}  // namespace
257
258class RenderFrameHostManagerTest
259    : public RenderViewHostImplTestHarness {
260 public:
261  virtual void SetUp() OVERRIDE {
262    RenderViewHostImplTestHarness::SetUp();
263    WebUIControllerFactory::RegisterFactory(&factory_);
264    lifetime_checker_.reset(new FrameLifetimeConsistencyChecker(contents()));
265  }
266
267  virtual void TearDown() OVERRIDE {
268    lifetime_checker_.reset();
269    RenderViewHostImplTestHarness::TearDown();
270    WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
271  }
272
273  void set_should_create_webui(bool should_create_webui) {
274    factory_.set_should_create_webui(should_create_webui);
275  }
276
277  void NavigateActiveAndCommit(const GURL& url) {
278    // Note: we navigate the active RenderFrameHost because previous navigations
279    // won't have committed yet, so NavigateAndCommit does the wrong thing
280    // for us.
281    controller().LoadURL(
282        url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
283    TestRenderViewHost* old_rvh = test_rvh();
284
285    // Simulate the BeforeUnload_ACK that is received from the current renderer
286    // for a cross-site navigation.
287    if (old_rvh != active_rvh()) {
288      old_rvh->SendBeforeUnloadACK(true);
289      EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, old_rvh->rvh_state());
290    }
291
292    // Commit the navigation with a new page ID.
293    int32 max_page_id = contents()->GetMaxPageIDForSiteInstance(
294        active_rvh()->GetSiteInstance());
295
296    // Use an observer to avoid accessing a deleted renderer later on when the
297    // state is being checked.
298    RenderViewHostDeletedObserver rvh_observer(old_rvh);
299    active_test_rvh()->SendNavigate(max_page_id + 1, url);
300
301    // Make sure that we start to run the unload handler at the time of commit.
302    bool expecting_rvh_shutdown = false;
303    if (old_rvh != active_rvh() && !rvh_observer.deleted()) {
304      if (!static_cast<SiteInstanceImpl*>(
305              old_rvh->GetSiteInstance())->active_view_count()) {
306        expecting_rvh_shutdown = true;
307        EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SHUTDOWN,
308                  old_rvh->rvh_state());
309      } else {
310        EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SWAP_OUT,
311                  old_rvh->rvh_state());
312      }
313    }
314
315    // Simulate the swap out ACK coming from the pending renderer.  This should
316    // either shut down the old RVH or leave it in a swapped out state.
317    if (old_rvh != active_rvh()) {
318      old_rvh->OnSwappedOut(false);
319      if (expecting_rvh_shutdown) {
320        EXPECT_TRUE(rvh_observer.deleted());
321      } else {
322        EXPECT_EQ(RenderViewHostImpl::STATE_SWAPPED_OUT,
323                  old_rvh->rvh_state());
324      }
325    }
326  }
327
328  bool ShouldSwapProcesses(RenderFrameHostManager* manager,
329                           const NavigationEntryImpl* current_entry,
330                           const NavigationEntryImpl* new_entry) const {
331    CHECK(new_entry);
332    BrowserContext* browser_context =
333        manager->delegate_->GetControllerForRenderManager().GetBrowserContext();
334    const GURL& current_effective_url = current_entry ?
335        SiteInstanceImpl::GetEffectiveURL(browser_context,
336                                          current_entry->GetURL()) :
337        manager->render_frame_host_->GetSiteInstance()->GetSiteURL();
338    bool current_is_view_source_mode = current_entry ?
339        current_entry->IsViewSourceMode() : new_entry->IsViewSourceMode();
340    return manager->ShouldSwapBrowsingInstancesForNavigation(
341        current_effective_url,
342        current_is_view_source_mode,
343        new_entry->site_instance(),
344        SiteInstanceImpl::GetEffectiveURL(browser_context, new_entry->GetURL()),
345        new_entry->IsViewSourceMode());
346  }
347
348  // Creates a test RenderViewHost that's swapped out.
349  TestRenderViewHost* CreateSwappedOutRenderViewHost() {
350    const GURL kChromeURL("chrome://foo");
351    const GURL kDestUrl("http://www.google.com/");
352
353    // Navigate our first tab to a chrome url and then to the destination.
354    NavigateActiveAndCommit(kChromeURL);
355    TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
356
357    // Navigate to a cross-site URL.
358    contents()->GetController().LoadURL(
359        kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
360    EXPECT_TRUE(contents()->cross_navigation_pending());
361
362    // Manually increase the number of active views in the
363    // SiteInstance that ntp_rfh belongs to, to prevent it from being
364    // destroyed when it gets swapped out.
365    static_cast<SiteInstanceImpl*>(ntp_rfh->GetSiteInstance())->
366        increment_active_view_count();
367
368    TestRenderFrameHost* dest_rfh = contents()->GetPendingMainFrame();
369    CHECK(dest_rfh);
370    EXPECT_NE(ntp_rfh, dest_rfh);
371
372    // BeforeUnload finishes.
373    ntp_rfh->GetRenderViewHost()->SendBeforeUnloadACK(true);
374
375    dest_rfh->SendNavigate(101, kDestUrl);
376    ntp_rfh->OnSwappedOut(false);
377
378    EXPECT_TRUE(ntp_rfh->GetRenderViewHost()->IsSwappedOut());
379    return ntp_rfh->GetRenderViewHost();
380  }
381
382  NavigationRequest* GetNavigationRequestForRenderFrameManager(
383      RenderFrameHostManager* manager) const {
384    return manager->navigation_request_for_testing();
385  }
386
387  void EnableBrowserSideNavigation() {
388    CommandLine::ForCurrentProcess()->AppendSwitch(
389        switches::kEnableBrowserSideNavigation);
390  }
391 private:
392  RenderFrameHostManagerTestWebUIControllerFactory factory_;
393  scoped_ptr<FrameLifetimeConsistencyChecker> lifetime_checker_;
394};
395
396// Tests that when you navigate from a chrome:// url to another page, and
397// then do that same thing in another tab, that the two resulting pages have
398// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
399// a regression test for bug 9364.
400TEST_F(RenderFrameHostManagerTest, NewTabPageProcesses) {
401  set_should_create_webui(true);
402  const GURL kChromeUrl("chrome://foo");
403  const GURL kDestUrl("http://www.google.com/");
404
405  // Navigate our first tab to the chrome url and then to the destination,
406  // ensuring we grant bindings to the chrome URL.
407  NavigateActiveAndCommit(kChromeUrl);
408  EXPECT_TRUE(active_rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
409  NavigateActiveAndCommit(kDestUrl);
410
411  EXPECT_FALSE(contents()->GetPendingMainFrame());
412
413  // Make a second tab.
414  scoped_ptr<TestWebContents> contents2(
415      TestWebContents::Create(browser_context(), NULL));
416
417  // Load the two URLs in the second tab. Note that the first navigation creates
418  // a RFH that's not pending (since there is no cross-site transition), so
419  // we use the committed one.
420  contents2->GetController().LoadURL(
421      kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
422  TestRenderFrameHost* ntp_rfh2 = contents2->GetMainFrame();
423  EXPECT_FALSE(contents2->cross_navigation_pending());
424  ntp_rfh2->SendNavigate(100, kChromeUrl);
425
426  // The second one is the opposite, creating a cross-site transition and
427  // requiring a beforeunload ack.
428  contents2->GetController().LoadURL(
429      kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
430  EXPECT_TRUE(contents2->cross_navigation_pending());
431  TestRenderFrameHost* dest_rfh2 = contents2->GetPendingMainFrame();
432  ASSERT_TRUE(dest_rfh2);
433
434  ntp_rfh2->GetRenderViewHost()->SendBeforeUnloadACK(true);
435  dest_rfh2->SendNavigate(101, kDestUrl);
436
437  // The two RFH's should be different in every way.
438  EXPECT_NE(contents()->GetMainFrame()->GetProcess(), dest_rfh2->GetProcess());
439  EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
440            dest_rfh2->GetSiteInstance());
441  EXPECT_FALSE(dest_rfh2->GetSiteInstance()->IsRelatedSiteInstance(
442                   contents()->GetMainFrame()->GetSiteInstance()));
443
444  // Navigate both to the new tab page, and verify that they share a
445  // RenderProcessHost (not a SiteInstance).
446  NavigateActiveAndCommit(kChromeUrl);
447  EXPECT_FALSE(contents()->GetPendingMainFrame());
448
449  contents2->GetController().LoadURL(
450      kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
451  dest_rfh2->GetRenderViewHost()->SendBeforeUnloadACK(true);
452  contents2->GetPendingMainFrame()->SendNavigate(102, kChromeUrl);
453
454  EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
455            contents2->GetMainFrame()->GetSiteInstance());
456  EXPECT_EQ(contents()->GetMainFrame()->GetSiteInstance()->GetProcess(),
457            contents2->GetMainFrame()->GetSiteInstance()->GetProcess());
458}
459
460// Ensure that the browser ignores most IPC messages that arrive from a
461// RenderViewHost that has been swapped out.  We do not want to take
462// action on requests from a non-active renderer.  The main exception is
463// for synchronous messages, which cannot be ignored without leaving the
464// renderer in a stuck state.  See http://crbug.com/93427.
465TEST_F(RenderFrameHostManagerTest, FilterMessagesWhileSwappedOut) {
466  const GURL kChromeURL("chrome://foo");
467  const GURL kDestUrl("http://www.google.com/");
468  std::vector<FaviconURL> icons;
469
470  // Navigate our first tab to a chrome url and then to the destination.
471  NavigateActiveAndCommit(kChromeURL);
472  TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
473
474  // Send an update favicon message and make sure it works.
475  const base::string16 ntp_title = base::ASCIIToUTF16("NTP Title");
476  {
477    PluginFaviconMessageObserver observer(contents());
478    EXPECT_TRUE(ntp_rfh->GetRenderViewHost()->OnMessageReceived(
479                    ViewHostMsg_UpdateFaviconURL(
480                        ntp_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
481    EXPECT_TRUE(observer.favicon_received());
482  }
483  // Create one more view in the same SiteInstance where ntp_rfh
484  // exists so that it doesn't get deleted on navigation to another
485  // site.
486  static_cast<SiteInstanceImpl*>(ntp_rfh->GetSiteInstance())->
487      increment_active_view_count();
488
489
490  // Navigate to a cross-site URL.
491  NavigateActiveAndCommit(kDestUrl);
492  TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
493  ASSERT_TRUE(dest_rfh);
494  EXPECT_NE(ntp_rfh, dest_rfh);
495
496  // The new RVH should be able to update its favicon.
497  const base::string16 dest_title = base::ASCIIToUTF16("Google");
498  {
499    PluginFaviconMessageObserver observer(contents());
500    EXPECT_TRUE(
501        dest_rfh->GetRenderViewHost()->OnMessageReceived(
502            ViewHostMsg_UpdateFaviconURL(
503                dest_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
504    EXPECT_TRUE(observer.favicon_received());
505  }
506
507  // The old renderer, being slow, now updates the favicon. It should be
508  // filtered out and not take effect.
509  EXPECT_TRUE(ntp_rfh->GetRenderViewHost()->IsSwappedOut());
510  {
511    PluginFaviconMessageObserver observer(contents());
512    EXPECT_TRUE(
513        ntp_rfh->GetRenderViewHost()->OnMessageReceived(
514            ViewHostMsg_UpdateFaviconURL(
515                dest_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
516    EXPECT_FALSE(observer.favicon_received());
517  }
518
519  // The same logic should apply to RenderFrameHosts as well and routing through
520  // swapped out RFH shouldn't be allowed. Use a PluginCrashObserver to check
521  // if the IPC message is allowed through or not.
522  {
523    PluginFaviconMessageObserver observer(contents());
524    EXPECT_TRUE(ntp_rfh->OnMessageReceived(
525                    FrameHostMsg_PluginCrashed(
526                        ntp_rfh->GetRoutingID(), base::FilePath(), 0)));
527    EXPECT_FALSE(observer.plugin_crashed());
528  }
529
530  // We cannot filter out synchronous IPC messages, because the renderer would
531  // be left waiting for a reply.  We pick RunBeforeUnloadConfirm as an example
532  // that can run easily within a unit test, and that needs to receive a reply
533  // without showing an actual dialog.
534  MockRenderProcessHost* ntp_process_host =
535      static_cast<MockRenderProcessHost*>(ntp_rfh->GetProcess());
536  ntp_process_host->sink().ClearMessages();
537  const base::string16 msg = base::ASCIIToUTF16("Message");
538  bool result = false;
539  base::string16 unused;
540  FrameHostMsg_RunBeforeUnloadConfirm before_unload_msg(
541      ntp_rfh->GetRoutingID(), kChromeURL, msg, false, &result, &unused);
542  // Enable pumping for check in BrowserMessageFilter::CheckCanDispatchOnUI.
543  before_unload_msg.EnableMessagePumping();
544  EXPECT_TRUE(ntp_rfh->OnMessageReceived(before_unload_msg));
545  EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
546
547  // Also test RunJavaScriptMessage.
548  ntp_process_host->sink().ClearMessages();
549  FrameHostMsg_RunJavaScriptMessage js_msg(
550      ntp_rfh->GetRoutingID(), msg, msg, kChromeURL,
551      JAVASCRIPT_MESSAGE_TYPE_CONFIRM, &result, &unused);
552  js_msg.EnableMessagePumping();
553  EXPECT_TRUE(ntp_rfh->OnMessageReceived(js_msg));
554  EXPECT_TRUE(ntp_process_host->sink().GetUniqueMessageMatching(IPC_REPLY_ID));
555}
556
557TEST_F(RenderFrameHostManagerTest, WhiteListSwapCompositorFrame) {
558  TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
559  TestRenderWidgetHostView* swapped_out_rwhv =
560      static_cast<TestRenderWidgetHostView*>(swapped_out_rvh->GetView());
561  EXPECT_FALSE(swapped_out_rwhv->did_swap_compositor_frame());
562
563  MockRenderProcessHost* process_host =
564      static_cast<MockRenderProcessHost*>(swapped_out_rvh->GetProcess());
565  process_host->sink().ClearMessages();
566
567  cc::CompositorFrame frame;
568  ViewHostMsg_SwapCompositorFrame msg(
569      rvh()->GetRoutingID(), 0, frame, std::vector<IPC::Message>());
570
571  EXPECT_TRUE(swapped_out_rvh->OnMessageReceived(msg));
572  EXPECT_TRUE(swapped_out_rwhv->did_swap_compositor_frame());
573}
574
575// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
576// widgets.
577TEST_F(RenderFrameHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
578  TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
579  EXPECT_TRUE(swapped_out_rvh->IsSwappedOut());
580
581  scoped_ptr<RenderWidgetHostIterator> widgets(
582      RenderWidgetHost::GetRenderWidgetHosts());
583  // We know that there is the only one active widget. Another view is
584  // now swapped out, so the swapped out view is not included in the
585  // list.
586  RenderWidgetHost* widget = widgets->GetNextHost();
587  EXPECT_FALSE(widgets->GetNextHost());
588  RenderViewHost* rvh = RenderViewHost::From(widget);
589  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
590            static_cast<RenderViewHostImpl*>(rvh)->rvh_state());
591}
592
593// Test if RenderViewHost::GetRenderWidgetHosts() returns a subset of
594// RenderViewHostImpl::GetAllRenderWidgetHosts().
595// RenderViewHost::GetRenderWidgetHosts() returns only active widgets, but
596// RenderViewHostImpl::GetAllRenderWidgetHosts() returns everything
597// including swapped out ones.
598TEST_F(RenderFrameHostManagerTest,
599       GetRenderWidgetHostsWithinGetAllRenderWidgetHosts) {
600  TestRenderViewHost* swapped_out_rvh = CreateSwappedOutRenderViewHost();
601  EXPECT_TRUE(swapped_out_rvh->IsSwappedOut());
602
603  scoped_ptr<RenderWidgetHostIterator> widgets(
604      RenderWidgetHost::GetRenderWidgetHosts());
605
606  while (RenderWidgetHost* w = widgets->GetNextHost()) {
607    bool found = false;
608    scoped_ptr<RenderWidgetHostIterator> all_widgets(
609        RenderWidgetHostImpl::GetAllRenderWidgetHosts());
610    while (RenderWidgetHost* widget = all_widgets->GetNextHost()) {
611      if (w == widget) {
612        found = true;
613        break;
614      }
615    }
616    EXPECT_TRUE(found);
617  }
618}
619
620// Test if SiteInstanceImpl::active_view_count() is correctly updated
621// as views in a SiteInstance get swapped out and in.
622TEST_F(RenderFrameHostManagerTest, ActiveViewCountWhileSwappingInandOut) {
623  const GURL kUrl1("http://www.google.com/");
624  const GURL kUrl2("http://www.chromium.org/");
625
626  // Navigate to an initial URL.
627  contents()->NavigateAndCommit(kUrl1);
628  TestRenderViewHost* rvh1 = test_rvh();
629
630  SiteInstanceImpl* instance1 =
631      static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance());
632  EXPECT_EQ(instance1->active_view_count(), 1U);
633
634  // Create 2 new tabs and simulate them being the opener chain for the main
635  // tab.  They should be in the same SiteInstance.
636  scoped_ptr<TestWebContents> opener1(
637      TestWebContents::Create(browser_context(), instance1));
638  contents()->SetOpener(opener1.get());
639
640  scoped_ptr<TestWebContents> opener2(
641      TestWebContents::Create(browser_context(), instance1));
642  opener1->SetOpener(opener2.get());
643
644  EXPECT_EQ(instance1->active_view_count(), 3U);
645
646  // Navigate to a cross-site URL (different SiteInstance but same
647  // BrowsingInstance).
648  contents()->NavigateAndCommit(kUrl2);
649  TestRenderViewHost* rvh2 = test_rvh();
650  SiteInstanceImpl* instance2 =
651      static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance());
652
653  // rvh2 is on chromium.org which is different from google.com on
654  // which other tabs are.
655  EXPECT_EQ(instance2->active_view_count(), 1U);
656
657  // There are two active views on google.com now.
658  EXPECT_EQ(instance1->active_view_count(), 2U);
659
660  // Navigate to the original origin (google.com).
661  contents()->NavigateAndCommit(kUrl1);
662
663  EXPECT_EQ(instance1->active_view_count(), 3U);
664}
665
666// This deletes a WebContents when the given RVH is deleted. This is
667// only for testing whether deleting an RVH does not cause any UaF in
668// other parts of the system. For now, this class is only used for the
669// next test cases to detect the bug mentioned at
670// http://crbug.com/259859.
671class RenderViewHostDestroyer : public WebContentsObserver {
672 public:
673  RenderViewHostDestroyer(RenderViewHost* render_view_host,
674                          WebContents* web_contents)
675      : WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)),
676        render_view_host_(render_view_host),
677        web_contents_(web_contents) {}
678
679  virtual void RenderViewDeleted(
680      RenderViewHost* render_view_host) OVERRIDE {
681    if (render_view_host == render_view_host_)
682      delete web_contents_;
683  }
684
685 private:
686  RenderViewHost* render_view_host_;
687  WebContents* web_contents_;
688
689  DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
690};
691
692// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
693// RenderWidget that has been freed while deleting a RenderViewHost in
694// a previous iteration. This is a regression test for
695// http://crbug.com/259859.
696TEST_F(RenderFrameHostManagerTest,
697       DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance) {
698  const GURL kChromeURL("chrome://newtab");
699  const GURL kUrl1("http://www.google.com");
700  const GURL kUrl2("http://www.chromium.org");
701
702  // Navigate our first tab to a chrome url and then to the destination.
703  NavigateActiveAndCommit(kChromeURL);
704  TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
705
706  // Create one more tab and navigate to kUrl1.  web_contents is not
707  // wrapped as scoped_ptr since it intentionally deleted by destroyer
708  // below as part of this test.
709  TestWebContents* web_contents =
710      TestWebContents::Create(browser_context(), ntp_rfh->GetSiteInstance());
711  web_contents->NavigateAndCommit(kUrl1);
712  RenderViewHostDestroyer destroyer(ntp_rfh->GetRenderViewHost(),
713                                    web_contents);
714
715  // This causes the first tab to navigate to kUrl2, which destroys
716  // the ntp_rfh in ShutdownRenderViewHostsInSiteInstance(). When
717  // ntp_rfh is destroyed, it also destroys the RVHs in web_contents
718  // too. This can test whether
719  // SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
720  // touch any object freed in this way or not while iterating through
721  // all widgets.
722  contents()->NavigateAndCommit(kUrl2);
723}
724
725// When there is an error with the specified page, renderer exits view-source
726// mode. See WebFrameImpl::DidFail(). We check by this test that
727// EnableViewSourceMode message is sent on every navigation regardless
728// RenderView is being newly created or reused.
729TEST_F(RenderFrameHostManagerTest, AlwaysSendEnableViewSourceMode) {
730  const GURL kChromeUrl("chrome://foo");
731  const GURL kUrl("view-source:http://foo");
732
733  // We have to navigate to some page at first since without this, the first
734  // navigation will reuse the SiteInstance created by Init(), and the second
735  // one will create a new SiteInstance. Because current_instance and
736  // new_instance will be different, a new RenderViewHost will be created for
737  // the second navigation. We have to avoid this in order to exercise the
738  // target code patch.
739  NavigateActiveAndCommit(kChromeUrl);
740
741  // Navigate.
742  controller().LoadURL(
743      kUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
744  // Simulate response from RenderFrame for DispatchBeforeUnload.
745  base::TimeTicks now = base::TimeTicks::Now();
746  contents()->GetMainFrame()->OnMessageReceived(FrameHostMsg_BeforeUnload_ACK(
747      contents()->GetMainFrame()->GetRoutingID(), true, now, now));
748  ASSERT_TRUE(contents()->GetPendingMainFrame())
749      << "Expected new pending RenderFrameHost to be created.";
750  RenderFrameHost* last_rfh = contents()->GetPendingMainFrame();
751  int32 new_id =
752      contents()->GetMaxPageIDForSiteInstance(last_rfh->GetSiteInstance()) + 1;
753  contents()->GetPendingMainFrame()->SendNavigate(new_id, kUrl);
754  EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
755  ASSERT_TRUE(controller().GetLastCommittedEntry());
756  EXPECT_TRUE(kUrl == controller().GetLastCommittedEntry()->GetURL());
757  EXPECT_FALSE(controller().GetPendingEntry());
758  // Because we're using TestWebContents and TestRenderViewHost in this
759  // unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no
760  // EnableViewSourceMode message, here.
761
762  // Clear queued messages before load.
763  process()->sink().ClearMessages();
764  // Navigate, again.
765  controller().LoadURL(
766      kUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
767  // The same RenderViewHost should be reused.
768  EXPECT_FALSE(contents()->GetPendingMainFrame());
769  EXPECT_TRUE(last_rfh == contents()->GetMainFrame());
770  // Navigate using the returned page_id.
771  contents()->GetMainFrame()->SendNavigate(new_id, kUrl);
772  EXPECT_EQ(controller().GetLastCommittedEntryIndex(), 1);
773  EXPECT_FALSE(controller().GetPendingEntry());
774  // New message should be sent out to make sure to enter view-source mode.
775  EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
776      ViewMsg_EnableViewSourceMode::ID));
777}
778
779// Tests the Init function by checking the initial RenderViewHost.
780TEST_F(RenderFrameHostManagerTest, Init) {
781  // Using TestBrowserContext.
782  SiteInstanceImpl* instance =
783      static_cast<SiteInstanceImpl*>(SiteInstance::Create(browser_context()));
784  EXPECT_FALSE(instance->HasSite());
785
786  scoped_ptr<TestWebContents> web_contents(
787      TestWebContents::Create(browser_context(), instance));
788
789  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
790  RenderViewHostImpl* rvh = manager->current_host();
791  RenderFrameHostImpl* rfh = manager->current_frame_host();
792  ASSERT_TRUE(rvh);
793  ASSERT_TRUE(rfh);
794  EXPECT_EQ(rvh, rfh->render_view_host());
795  EXPECT_EQ(instance, rvh->GetSiteInstance());
796  EXPECT_EQ(web_contents.get(), rvh->GetDelegate());
797  EXPECT_EQ(web_contents.get(), rfh->delegate());
798  EXPECT_TRUE(manager->GetRenderWidgetHostView());
799  EXPECT_FALSE(manager->pending_render_view_host());
800}
801
802// Tests the Navigate function. We navigate three sites consecutively and check
803// how the pending/committed RenderViewHost are modified.
804TEST_F(RenderFrameHostManagerTest, Navigate) {
805  TestNotificationTracker notifications;
806
807  SiteInstance* instance = SiteInstance::Create(browser_context());
808
809  scoped_ptr<TestWebContents> web_contents(
810      TestWebContents::Create(browser_context(), instance));
811  notifications.ListenFor(NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
812                          Source<WebContents>(web_contents.get()));
813
814  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
815  RenderFrameHostImpl* host;
816
817  // 1) The first navigation. --------------------------
818  const GURL kUrl1("http://www.google.com/");
819  NavigationEntryImpl entry1(
820      NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
821      base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
822      false /* is_renderer_init */);
823  host = manager->Navigate(entry1);
824
825  // The RenderFrameHost created in Init will be reused.
826  EXPECT_TRUE(host == manager->current_frame_host());
827  EXPECT_FALSE(manager->pending_frame_host());
828
829  // Commit.
830  manager->DidNavigateFrame(host);
831  // Commit to SiteInstance should be delayed until RenderView commit.
832  EXPECT_TRUE(host == manager->current_frame_host());
833  ASSERT_TRUE(host);
834  EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
835      HasSite());
836  static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
837
838  // 2) Navigate to next site. -------------------------
839  const GURL kUrl2("http://www.google.com/foo");
840  NavigationEntryImpl entry2(
841      NULL /* instance */, -1 /* page_id */, kUrl2,
842      Referrer(kUrl1, blink::WebReferrerPolicyDefault),
843      base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
844      true /* is_renderer_init */);
845  host = manager->Navigate(entry2);
846
847  // The RenderFrameHost created in Init will be reused.
848  EXPECT_TRUE(host == manager->current_frame_host());
849  EXPECT_FALSE(manager->pending_frame_host());
850
851  // Commit.
852  manager->DidNavigateFrame(host);
853  EXPECT_TRUE(host == manager->current_frame_host());
854  ASSERT_TRUE(host);
855  EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
856      HasSite());
857
858  // 3) Cross-site navigate to next site. --------------
859  const GURL kUrl3("http://webkit.org/");
860  NavigationEntryImpl entry3(
861      NULL /* instance */, -1 /* page_id */, kUrl3,
862      Referrer(kUrl2, blink::WebReferrerPolicyDefault),
863      base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
864      false /* is_renderer_init */);
865  host = manager->Navigate(entry3);
866
867  // A new RenderFrameHost should be created.
868  EXPECT_TRUE(manager->pending_frame_host());
869  ASSERT_EQ(host, manager->pending_frame_host());
870
871  notifications.Reset();
872
873  // Commit.
874  manager->DidNavigateFrame(manager->pending_frame_host());
875  EXPECT_TRUE(host == manager->current_frame_host());
876  ASSERT_TRUE(host);
877  EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
878      HasSite());
879  // Check the pending RenderFrameHost has been committed.
880  EXPECT_FALSE(manager->pending_frame_host());
881
882  // We should observe a notification.
883  EXPECT_TRUE(
884      notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
885}
886
887// Tests WebUI creation.
888TEST_F(RenderFrameHostManagerTest, WebUI) {
889  set_should_create_webui(true);
890  SiteInstance* instance = SiteInstance::Create(browser_context());
891
892  scoped_ptr<TestWebContents> web_contents(
893      TestWebContents::Create(browser_context(), instance));
894  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
895
896  EXPECT_FALSE(manager->current_host()->IsRenderViewLive());
897
898  const GURL kUrl("chrome://foo");
899  NavigationEntryImpl entry(NULL /* instance */, -1 /* page_id */, kUrl,
900                            Referrer(), base::string16() /* title */,
901                            ui::PAGE_TRANSITION_TYPED,
902                            false /* is_renderer_init */);
903  RenderFrameHostImpl* host = manager->Navigate(entry);
904
905  // We commit the pending RenderFrameHost immediately because the previous
906  // RenderFrameHost was not live.  We test a case where it is live in
907  // WebUIInNewTab.
908  EXPECT_TRUE(host);
909  EXPECT_EQ(host, manager->current_frame_host());
910  EXPECT_FALSE(manager->pending_frame_host());
911
912  // It's important that the site instance get set on the Web UI page as soon
913  // as the navigation starts, rather than lazily after it commits, so we don't
914  // try to re-use the SiteInstance/process for non Web UI things that may
915  // get loaded in between.
916  EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
917      HasSite());
918  EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());
919
920  // The Web UI is committed immediately because the RenderViewHost has not been
921  // used yet. UpdateStateForNavigate() took the short cut path.
922  EXPECT_FALSE(manager->pending_web_ui());
923  EXPECT_TRUE(manager->web_ui());
924
925  // Commit.
926  manager->DidNavigateFrame(host);
927  EXPECT_TRUE(
928      host->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
929}
930
931// Tests that we can open a WebUI link in a new tab from a WebUI page and still
932// grant the correct bindings.  http://crbug.com/189101.
933TEST_F(RenderFrameHostManagerTest, WebUIInNewTab) {
934  set_should_create_webui(true);
935  SiteInstance* blank_instance = SiteInstance::Create(browser_context());
936
937  // Create a blank tab.
938  scoped_ptr<TestWebContents> web_contents1(
939      TestWebContents::Create(browser_context(), blank_instance));
940  RenderFrameHostManager* manager1 =
941      web_contents1->GetRenderManagerForTesting();
942  // Test the case that new RVH is considered live.
943  manager1->current_host()->CreateRenderView(
944      base::string16(), -1, MSG_ROUTING_NONE, -1, false);
945  EXPECT_TRUE(manager1->current_host()->IsRenderViewLive());
946  EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive());
947
948  // Navigate to a WebUI page.
949  const GURL kUrl1("chrome://foo");
950  NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
951                             Referrer(), base::string16() /* title */,
952                             ui::PAGE_TRANSITION_TYPED,
953                             false /* is_renderer_init */);
954  RenderFrameHostImpl* host1 = manager1->Navigate(entry1);
955
956  // We should have a pending navigation to the WebUI RenderViewHost.
957  // It should already have bindings.
958  EXPECT_EQ(host1, manager1->pending_frame_host());
959  EXPECT_NE(host1, manager1->current_frame_host());
960  EXPECT_TRUE(
961      host1->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
962
963  // Commit and ensure we still have bindings.
964  manager1->DidNavigateFrame(host1);
965  SiteInstance* webui_instance = host1->GetSiteInstance();
966  EXPECT_EQ(host1, manager1->current_frame_host());
967  EXPECT_TRUE(
968      host1->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
969
970  // Now simulate clicking a link that opens in a new tab.
971  scoped_ptr<TestWebContents> web_contents2(
972      TestWebContents::Create(browser_context(), webui_instance));
973  RenderFrameHostManager* manager2 =
974      web_contents2->GetRenderManagerForTesting();
975  // Make sure the new RVH is considered live.  This is usually done in
976  // RenderWidgetHost::Init when opening a new tab from a link.
977  manager2->current_host()->CreateRenderView(
978      base::string16(), -1, MSG_ROUTING_NONE, -1, false);
979
980  const GURL kUrl2("chrome://foo/bar");
981  NavigationEntryImpl entry2(NULL /* instance */, -1 /* page_id */, kUrl2,
982                             Referrer(), base::string16() /* title */,
983                             ui::PAGE_TRANSITION_LINK,
984                             true /* is_renderer_init */);
985  RenderFrameHostImpl* host2 = manager2->Navigate(entry2);
986
987  // No cross-process transition happens because we are already in the right
988  // SiteInstance.  We should grant bindings immediately.
989  EXPECT_EQ(host2, manager2->current_frame_host());
990  EXPECT_TRUE(
991      host2->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
992
993  manager2->DidNavigateFrame(host2);
994}
995
996// Tests that we don't end up in an inconsistent state if a page does a back and
997// then reload. http://crbug.com/51680
998TEST_F(RenderFrameHostManagerTest, PageDoesBackAndReload) {
999  const GURL kUrl1("http://www.google.com/");
1000  const GURL kUrl2("http://www.evil-site.com/");
1001
1002  // Navigate to a safe site, then an evil site.
1003  // This will switch RenderFrameHosts.  We cannot assert that the first and
1004  // second RFHs are different, though, because the first one may be promptly
1005  // deleted.
1006  contents()->NavigateAndCommit(kUrl1);
1007  contents()->NavigateAndCommit(kUrl2);
1008  TestRenderFrameHost* evil_rfh = contents()->GetMainFrame();
1009
1010  // Now let's simulate the evil page calling history.back().
1011  contents()->OnGoToEntryAtOffset(-1);
1012  // We should have a new pending RFH.
1013  // Note that in this case, the navigation has not committed, so evil_rfh will
1014  // not be deleted yet.
1015  EXPECT_NE(evil_rfh, contents()->GetPendingMainFrame());
1016  EXPECT_NE(evil_rfh->GetRenderViewHost(),
1017            contents()->GetPendingMainFrame()->GetRenderViewHost());
1018
1019  // Before that RFH has committed, the evil page reloads itself.
1020  FrameHostMsg_DidCommitProvisionalLoad_Params params;
1021  params.page_id = 1;
1022  params.url = kUrl2;
1023  params.transition = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
1024  params.should_update_history = false;
1025  params.gesture = NavigationGestureAuto;
1026  params.was_within_same_page = false;
1027  params.is_post = false;
1028  params.page_state = PageState::CreateFromURL(kUrl2);
1029
1030  contents()->GetFrameTree()->root()->navigator()->DidNavigate(evil_rfh,
1031                                                               params);
1032
1033  // That should have cancelled the pending RFH, and the evil RFH should be the
1034  // current one.
1035  EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
1036      pending_render_view_host() == NULL);
1037  EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_frame_host() ==
1038              NULL);
1039  EXPECT_EQ(evil_rfh,
1040            contents()->GetRenderManagerForTesting()->current_frame_host());
1041  EXPECT_EQ(evil_rfh->GetRenderViewHost(),
1042            contents()->GetRenderManagerForTesting()->current_host());
1043
1044  // Also we should not have a pending navigation entry.
1045  EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
1046  NavigationEntry* entry = contents()->GetController().GetVisibleEntry();
1047  ASSERT_TRUE(entry != NULL);
1048  EXPECT_EQ(kUrl2, entry->GetURL());
1049}
1050
1051// Ensure that we can go back and forward even if a SwapOut ACK isn't received.
1052// See http://crbug.com/93427.
1053TEST_F(RenderFrameHostManagerTest, NavigateAfterMissingSwapOutACK) {
1054  const GURL kUrl1("http://www.google.com/");
1055  const GURL kUrl2("http://www.chromium.org/");
1056
1057  // Navigate to two pages.
1058  contents()->NavigateAndCommit(kUrl1);
1059  TestRenderViewHost* rvh1 = test_rvh();
1060
1061  // Keep active_view_count nonzero so that no swapped out views in
1062  // this SiteInstance get forcefully deleted.
1063  static_cast<SiteInstanceImpl*>(rvh1->GetSiteInstance())->
1064      increment_active_view_count();
1065
1066  contents()->NavigateAndCommit(kUrl2);
1067  TestRenderViewHost* rvh2 = test_rvh();
1068  static_cast<SiteInstanceImpl*>(rvh2->GetSiteInstance())->
1069      increment_active_view_count();
1070
1071  // Now go back, but suppose the SwapOut_ACK isn't received.  This shouldn't
1072  // happen, but we have seen it when going back quickly across many entries
1073  // (http://crbug.com/93427).
1074  contents()->GetController().GoBack();
1075  EXPECT_TRUE(rvh2->is_waiting_for_beforeunload_ack());
1076  contents()->ProceedWithCrossSiteNavigation();
1077  EXPECT_FALSE(rvh2->is_waiting_for_beforeunload_ack());
1078
1079  // The back navigation commits.
1080  const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
1081  rvh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
1082  EXPECT_TRUE(rvh2->IsWaitingForUnloadACK());
1083  EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SWAP_OUT, rvh2->rvh_state());
1084
1085  // We should be able to navigate forward.
1086  contents()->GetController().GoForward();
1087  contents()->ProceedWithCrossSiteNavigation();
1088  const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry();
1089  rvh2->SendNavigate(entry2->GetPageID(), entry2->GetURL());
1090  EXPECT_EQ(rvh2, rvh());
1091  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, rvh2->rvh_state());
1092  EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SWAP_OUT, rvh1->rvh_state());
1093  rvh1->OnSwappedOut(false);
1094  EXPECT_TRUE(rvh1->IsSwappedOut());
1095  EXPECT_EQ(RenderViewHostImpl::STATE_SWAPPED_OUT, rvh1->rvh_state());
1096}
1097
1098// Test that we create swapped out RVHs for the opener chain when navigating an
1099// opened tab cross-process.  This allows us to support certain cross-process
1100// JavaScript calls (http://crbug.com/99202).
1101TEST_F(RenderFrameHostManagerTest, CreateSwappedOutOpenerRVHs) {
1102  const GURL kUrl1("http://www.google.com/");
1103  const GURL kUrl2("http://www.chromium.org/");
1104  const GURL kChromeUrl("chrome://foo");
1105
1106  // Navigate to an initial URL.
1107  contents()->NavigateAndCommit(kUrl1);
1108  RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
1109  TestRenderViewHost* rvh1 = test_rvh();
1110
1111  // Create 2 new tabs and simulate them being the opener chain for the main
1112  // tab.  They should be in the same SiteInstance.
1113  scoped_ptr<TestWebContents> opener1(
1114      TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
1115  RenderFrameHostManager* opener1_manager =
1116      opener1->GetRenderManagerForTesting();
1117  contents()->SetOpener(opener1.get());
1118
1119  scoped_ptr<TestWebContents> opener2(
1120      TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
1121  RenderFrameHostManager* opener2_manager =
1122      opener2->GetRenderManagerForTesting();
1123  opener1->SetOpener(opener2.get());
1124
1125  // Navigate to a cross-site URL (different SiteInstance but same
1126  // BrowsingInstance).
1127  contents()->NavigateAndCommit(kUrl2);
1128  TestRenderViewHost* rvh2 = test_rvh();
1129  EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
1130  EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
1131                  rvh2->GetSiteInstance()));
1132
1133  // Ensure rvh1 is placed on swapped out list of the current tab.
1134  EXPECT_TRUE(manager->IsRVHOnSwappedOutList(rvh1));
1135  EXPECT_EQ(rvh1,
1136            manager->GetSwappedOutRenderViewHost(rvh1->GetSiteInstance()));
1137
1138  // Ensure a swapped out RVH is created in the first opener tab.
1139  TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
1140      opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
1141  EXPECT_TRUE(opener1_manager->IsRVHOnSwappedOutList(opener1_rvh));
1142  EXPECT_TRUE(opener1_rvh->IsSwappedOut());
1143
1144  // Ensure a swapped out RVH is created in the second opener tab.
1145  TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
1146      opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
1147  EXPECT_TRUE(opener2_manager->IsRVHOnSwappedOutList(opener2_rvh));
1148  EXPECT_TRUE(opener2_rvh->IsSwappedOut());
1149
1150  // Navigate to a cross-BrowsingInstance URL.
1151  contents()->NavigateAndCommit(kChromeUrl);
1152  TestRenderViewHost* rvh3 = test_rvh();
1153  EXPECT_NE(rvh1->GetSiteInstance(), rvh3->GetSiteInstance());
1154  EXPECT_FALSE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
1155                   rvh3->GetSiteInstance()));
1156
1157  // No scripting is allowed across BrowsingInstances, so we should not create
1158  // swapped out RVHs for the opener chain in this case.
1159  EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
1160                   rvh3->GetSiteInstance()));
1161  EXPECT_FALSE(opener2_manager->GetSwappedOutRenderViewHost(
1162                   rvh3->GetSiteInstance()));
1163}
1164
1165// Test that a page can disown the opener of the WebContents.
1166TEST_F(RenderFrameHostManagerTest, DisownOpener) {
1167  const GURL kUrl1("http://www.google.com/");
1168  const GURL kUrl2("http://www.chromium.org/");
1169
1170  // Navigate to an initial URL.
1171  contents()->NavigateAndCommit(kUrl1);
1172  TestRenderFrameHost* rfh1 = main_test_rfh();
1173
1174  // Create a new tab and simulate having it be the opener for the main tab.
1175  scoped_ptr<TestWebContents> opener1(
1176      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
1177  contents()->SetOpener(opener1.get());
1178  EXPECT_TRUE(contents()->HasOpener());
1179
1180  // Navigate to a cross-site URL (different SiteInstance but same
1181  // BrowsingInstance).
1182  contents()->NavigateAndCommit(kUrl2);
1183  TestRenderFrameHost* rfh2 = main_test_rfh();
1184  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
1185
1186  // Disown the opener from rfh2.
1187  rfh2->DidDisownOpener();
1188
1189  // Ensure the opener is cleared.
1190  EXPECT_FALSE(contents()->HasOpener());
1191}
1192
1193// Test that a page can disown a same-site opener of the WebContents.
1194TEST_F(RenderFrameHostManagerTest, DisownSameSiteOpener) {
1195  const GURL kUrl1("http://www.google.com/");
1196
1197  // Navigate to an initial URL.
1198  contents()->NavigateAndCommit(kUrl1);
1199  TestRenderFrameHost* rfh1 = main_test_rfh();
1200
1201  // Create a new tab and simulate having it be the opener for the main tab.
1202  scoped_ptr<TestWebContents> opener1(
1203      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
1204  contents()->SetOpener(opener1.get());
1205  EXPECT_TRUE(contents()->HasOpener());
1206
1207  // Disown the opener from rfh1.
1208  rfh1->DidDisownOpener();
1209
1210  // Ensure the opener is cleared even if it is in the same process.
1211  EXPECT_FALSE(contents()->HasOpener());
1212}
1213
1214// Test that a page can disown the opener just as a cross-process navigation is
1215// in progress.
1216TEST_F(RenderFrameHostManagerTest, DisownOpenerDuringNavigation) {
1217  const GURL kUrl1("http://www.google.com/");
1218  const GURL kUrl2("http://www.chromium.org/");
1219
1220  // Navigate to an initial URL.
1221  contents()->NavigateAndCommit(kUrl1);
1222  TestRenderFrameHost* rfh1 = main_test_rfh();
1223
1224  // Create a new tab and simulate having it be the opener for the main tab.
1225  scoped_ptr<TestWebContents> opener1(
1226      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
1227  contents()->SetOpener(opener1.get());
1228  EXPECT_TRUE(contents()->HasOpener());
1229
1230  // Navigate to a cross-site URL (different SiteInstance but same
1231  // BrowsingInstance).
1232  contents()->NavigateAndCommit(kUrl2);
1233  TestRenderFrameHost* rfh2 = main_test_rfh();
1234  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
1235
1236  // Start a back navigation so that rfh1 becomes the pending RFH.
1237  contents()->GetController().GoBack();
1238  contents()->ProceedWithCrossSiteNavigation();
1239
1240  // Disown the opener from rfh2.
1241  rfh2->DidDisownOpener();
1242
1243  // Ensure the opener is cleared.
1244  EXPECT_FALSE(contents()->HasOpener());
1245
1246  // The back navigation commits.
1247  const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
1248  rfh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
1249
1250  // Ensure the opener is still cleared.
1251  EXPECT_FALSE(contents()->HasOpener());
1252}
1253
1254// Test that a page can disown the opener just after a cross-process navigation
1255// commits.
1256TEST_F(RenderFrameHostManagerTest, DisownOpenerAfterNavigation) {
1257  const GURL kUrl1("http://www.google.com/");
1258  const GURL kUrl2("http://www.chromium.org/");
1259
1260  // Navigate to an initial URL.
1261  contents()->NavigateAndCommit(kUrl1);
1262  TestRenderFrameHost* rfh1 = main_test_rfh();
1263
1264  // Create a new tab and simulate having it be the opener for the main tab.
1265  scoped_ptr<TestWebContents> opener1(
1266      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
1267  contents()->SetOpener(opener1.get());
1268  EXPECT_TRUE(contents()->HasOpener());
1269
1270  // Navigate to a cross-site URL (different SiteInstance but same
1271  // BrowsingInstance).
1272  contents()->NavigateAndCommit(kUrl2);
1273  TestRenderFrameHost* rfh2 = main_test_rfh();
1274  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
1275
1276  // Commit a back navigation before the DidDisownOpener message arrives.
1277  // rfh1 will be kept alive because of the opener tab.
1278  contents()->GetController().GoBack();
1279  contents()->ProceedWithCrossSiteNavigation();
1280  const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
1281  rfh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
1282
1283  // Disown the opener from rfh2.
1284  rfh2->DidDisownOpener();
1285  EXPECT_FALSE(contents()->HasOpener());
1286}
1287
1288// Test that we clean up swapped out RenderViewHosts when a process hosting
1289// those associated RenderViews crashes. http://crbug.com/258993
1290TEST_F(RenderFrameHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash) {
1291  const GURL kUrl1("http://www.google.com/");
1292  const GURL kUrl2("http://www.chromium.org/");
1293
1294  // Navigate to an initial URL.
1295  contents()->NavigateAndCommit(kUrl1);
1296  TestRenderViewHost* rvh1 = test_rvh();
1297
1298  // Create a new tab as an opener for the main tab.
1299  scoped_ptr<TestWebContents> opener1(
1300      TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
1301  RenderFrameHostManager* opener1_manager =
1302      opener1->GetRenderManagerForTesting();
1303  contents()->SetOpener(opener1.get());
1304
1305  // Make sure the new opener RVH is considered live.
1306  opener1_manager->current_host()->CreateRenderView(
1307      base::string16(), -1, MSG_ROUTING_NONE, -1, false);
1308  EXPECT_TRUE(opener1_manager->current_host()->IsRenderViewLive());
1309  EXPECT_TRUE(opener1_manager->current_frame_host()->IsRenderFrameLive());
1310
1311  // Use a cross-process navigation in the opener to swap out the old RVH.
1312  EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
1313      rvh1->GetSiteInstance()));
1314  opener1->NavigateAndCommit(kUrl2);
1315  EXPECT_TRUE(opener1_manager->GetSwappedOutRenderViewHost(
1316      rvh1->GetSiteInstance()));
1317
1318  // Fake a process crash.
1319  RenderProcessHost::RendererClosedDetails details(
1320      rvh1->GetProcess()->GetHandle(),
1321      base::TERMINATION_STATUS_PROCESS_CRASHED,
1322      0);
1323  NotificationService::current()->Notify(
1324      NOTIFICATION_RENDERER_PROCESS_CLOSED,
1325      Source<RenderProcessHost>(rvh1->GetProcess()),
1326      Details<RenderProcessHost::RendererClosedDetails>(&details));
1327  rvh1->set_render_view_created(false);
1328
1329  // Ensure that the swapped out RenderViewHost has been deleted.
1330  EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
1331      rvh1->GetSiteInstance()));
1332
1333  // Reload the initial tab. This should recreate the opener's swapped out RVH
1334  // in the original SiteInstance.
1335  contents()->GetController().Reload(true);
1336  EXPECT_EQ(opener1_manager->GetSwappedOutRenderViewHost(
1337                rvh1->GetSiteInstance())->GetRoutingID(),
1338            test_rvh()->opener_route_id());
1339}
1340
1341// Test that RenderViewHosts created for WebUI navigations are properly
1342// granted WebUI bindings even if an unprivileged swapped out RenderViewHost
1343// is in the same process (http://crbug.com/79918).
1344TEST_F(RenderFrameHostManagerTest, EnableWebUIWithSwappedOutOpener) {
1345  set_should_create_webui(true);
1346  const GURL kSettingsUrl("chrome://chrome/settings");
1347  const GURL kPluginUrl("chrome://plugins");
1348
1349  // Navigate to an initial WebUI URL.
1350  contents()->NavigateAndCommit(kSettingsUrl);
1351
1352  // Ensure the RVH has WebUI bindings.
1353  TestRenderViewHost* rvh1 = test_rvh();
1354  EXPECT_TRUE(rvh1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
1355
1356  // Create a new tab and simulate it being the opener for the main
1357  // tab.  It should be in the same SiteInstance.
1358  scoped_ptr<TestWebContents> opener1(
1359      TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
1360  RenderFrameHostManager* opener1_manager =
1361      opener1->GetRenderManagerForTesting();
1362  contents()->SetOpener(opener1.get());
1363
1364  // Navigate to a different WebUI URL (different SiteInstance, same
1365  // BrowsingInstance).
1366  contents()->NavigateAndCommit(kPluginUrl);
1367  TestRenderViewHost* rvh2 = test_rvh();
1368  EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
1369  EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
1370                  rvh2->GetSiteInstance()));
1371
1372  // Ensure a swapped out RVH is created in the first opener tab.
1373  TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
1374      opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
1375  EXPECT_TRUE(opener1_manager->IsRVHOnSwappedOutList(opener1_rvh));
1376  EXPECT_TRUE(opener1_rvh->IsSwappedOut());
1377
1378  // Ensure the new RVH has WebUI bindings.
1379  EXPECT_TRUE(rvh2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
1380}
1381
1382// Test that we reuse the same guest SiteInstance if we navigate across sites.
1383TEST_F(RenderFrameHostManagerTest, NoSwapOnGuestNavigations) {
1384  TestNotificationTracker notifications;
1385
1386  GURL guest_url(std::string(kGuestScheme).append("://abc123"));
1387  SiteInstance* instance =
1388      SiteInstance::CreateForURL(browser_context(), guest_url);
1389  scoped_ptr<TestWebContents> web_contents(
1390      TestWebContents::Create(browser_context(), instance));
1391
1392  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
1393
1394  RenderFrameHostImpl* host;
1395
1396  // 1) The first navigation. --------------------------
1397  const GURL kUrl1("http://www.google.com/");
1398  NavigationEntryImpl entry1(
1399      NULL /* instance */, -1 /* page_id */, kUrl1, Referrer(),
1400      base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
1401      false /* is_renderer_init */);
1402  host = manager->Navigate(entry1);
1403
1404  // The RenderFrameHost created in Init will be reused.
1405  EXPECT_TRUE(host == manager->current_frame_host());
1406  EXPECT_FALSE(manager->pending_frame_host());
1407  EXPECT_EQ(manager->current_frame_host()->GetSiteInstance(), instance);
1408
1409  // Commit.
1410  manager->DidNavigateFrame(host);
1411  // Commit to SiteInstance should be delayed until RenderView commit.
1412  EXPECT_EQ(host, manager->current_frame_host());
1413  ASSERT_TRUE(host);
1414  EXPECT_TRUE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
1415      HasSite());
1416
1417  // 2) Navigate to a different domain. -------------------------
1418  // Guests stay in the same process on navigation.
1419  const GURL kUrl2("http://www.chromium.org");
1420  NavigationEntryImpl entry2(
1421      NULL /* instance */, -1 /* page_id */, kUrl2,
1422      Referrer(kUrl1, blink::WebReferrerPolicyDefault),
1423      base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
1424      true /* is_renderer_init */);
1425  host = manager->Navigate(entry2);
1426
1427  // The RenderFrameHost created in Init will be reused.
1428  EXPECT_EQ(host, manager->current_frame_host());
1429  EXPECT_FALSE(manager->pending_frame_host());
1430
1431  // Commit.
1432  manager->DidNavigateFrame(host);
1433  EXPECT_EQ(host, manager->current_frame_host());
1434  ASSERT_TRUE(host);
1435  EXPECT_EQ(static_cast<SiteInstanceImpl*>(host->GetSiteInstance()),
1436      instance);
1437}
1438
1439// Test that we cancel a pending RVH if we close the tab while it's pending.
1440// http://crbug.com/294697.
1441TEST_F(RenderFrameHostManagerTest, NavigateWithEarlyClose) {
1442  TestNotificationTracker notifications;
1443
1444  SiteInstance* instance = SiteInstance::Create(browser_context());
1445
1446  BeforeUnloadFiredWebContentsDelegate delegate;
1447  scoped_ptr<TestWebContents> web_contents(
1448      TestWebContents::Create(browser_context(), instance));
1449  web_contents->SetDelegate(&delegate);
1450  notifications.ListenFor(NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
1451                          Source<WebContents>(web_contents.get()));
1452
1453  RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
1454
1455  // 1) The first navigation. --------------------------
1456  const GURL kUrl1("http://www.google.com/");
1457  NavigationEntryImpl entry1(NULL /* instance */, -1 /* page_id */, kUrl1,
1458                             Referrer(), base::string16() /* title */,
1459                             ui::PAGE_TRANSITION_TYPED,
1460                             false /* is_renderer_init */);
1461  RenderFrameHostImpl* host = manager->Navigate(entry1);
1462
1463  // The RenderFrameHost created in Init will be reused.
1464  EXPECT_EQ(host, manager->current_frame_host());
1465  EXPECT_FALSE(manager->pending_frame_host());
1466
1467  // We should observe a notification.
1468  EXPECT_TRUE(
1469      notifications.Check1AndReset(NOTIFICATION_RENDER_VIEW_HOST_CHANGED));
1470  notifications.Reset();
1471
1472  // Commit.
1473  manager->DidNavigateFrame(host);
1474
1475  // Commit to SiteInstance should be delayed until RenderFrame commits.
1476  EXPECT_EQ(host, manager->current_frame_host());
1477  EXPECT_FALSE(static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->
1478      HasSite());
1479  static_cast<SiteInstanceImpl*>(host->GetSiteInstance())->SetSite(kUrl1);
1480
1481  // 2) Cross-site navigate to next site. -------------------------
1482  const GURL kUrl2("http://www.example.com");
1483  NavigationEntryImpl entry2(
1484      NULL /* instance */, -1 /* page_id */, kUrl2, Referrer(),
1485      base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
1486      false /* is_renderer_init */);
1487  RenderFrameHostImpl* host2 = manager->Navigate(entry2);
1488
1489  // A new RenderFrameHost should be created.
1490  ASSERT_EQ(host2, manager->pending_frame_host());
1491  EXPECT_NE(host2, host);
1492
1493  EXPECT_EQ(host, manager->current_frame_host());
1494  EXPECT_FALSE(manager->current_frame_host()->is_swapped_out());
1495  EXPECT_EQ(host2, manager->pending_frame_host());
1496
1497  // 3) Close the tab. -------------------------
1498  notifications.ListenFor(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
1499                          Source<RenderWidgetHost>(host2->render_view_host()));
1500  manager->OnBeforeUnloadACK(false, true, base::TimeTicks());
1501
1502  EXPECT_TRUE(
1503      notifications.Check1AndReset(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED));
1504  EXPECT_FALSE(manager->pending_frame_host());
1505  EXPECT_EQ(host, manager->current_frame_host());
1506}
1507
1508// Tests that the RenderFrameHost is properly deleted when the SwapOutACK is
1509// received.  (SwapOut and the corresponding ACK always occur after commit.)
1510// Also tests that an early SwapOutACK is properly ignored.
1511TEST_F(RenderFrameHostManagerTest, DeleteFrameAfterSwapOutACK) {
1512  const GURL kUrl1("http://www.google.com/");
1513  const GURL kUrl2("http://www.chromium.org/");
1514
1515  // Navigate to the first page.
1516  contents()->NavigateAndCommit(kUrl1);
1517  TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
1518  RenderViewHostDeletedObserver rvh_deleted_observer(rfh1->GetRenderViewHost());
1519  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1520            rfh1->GetRenderViewHost()->rvh_state());
1521
1522  // Navigate to new site, simulating onbeforeunload approval.
1523  controller().LoadURL(
1524      kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1525  base::TimeTicks now = base::TimeTicks::Now();
1526  contents()->GetMainFrame()->OnMessageReceived(
1527      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1528  EXPECT_TRUE(contents()->cross_navigation_pending());
1529  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1530            rfh1->GetRenderViewHost()->rvh_state());
1531  TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
1532
1533  // Simulate the swap out ack, unexpectedly early (before commit).  It should
1534  // have no effect.
1535  rfh1->OnSwappedOut(false);
1536  EXPECT_TRUE(contents()->cross_navigation_pending());
1537  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1538            rfh1->GetRenderViewHost()->rvh_state());
1539
1540  // The new page commits.
1541  contents()->TestDidNavigate(rfh2, 1, kUrl2, ui::PAGE_TRANSITION_TYPED);
1542  EXPECT_FALSE(contents()->cross_navigation_pending());
1543  EXPECT_EQ(rfh2, contents()->GetMainFrame());
1544  EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
1545  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1546            rfh2->GetRenderViewHost()->rvh_state());
1547  EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SHUTDOWN,
1548            rfh1->GetRenderViewHost()->rvh_state());
1549
1550  // Simulate the swap out ack.
1551  rfh1->OnSwappedOut(false);
1552
1553  // rfh1 should have been deleted.
1554  EXPECT_TRUE(rvh_deleted_observer.deleted());
1555  rfh1 = NULL;
1556}
1557
1558// Tests that the RenderFrameHost is properly swapped out when the SwapOut ACK
1559// is received.  (SwapOut and the corresponding ACK always occur after commit.)
1560TEST_F(RenderFrameHostManagerTest, SwapOutFrameAfterSwapOutACK) {
1561  const GURL kUrl1("http://www.google.com/");
1562  const GURL kUrl2("http://www.chromium.org/");
1563
1564  // Navigate to the first page.
1565  contents()->NavigateAndCommit(kUrl1);
1566  TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
1567  RenderViewHostDeletedObserver rvh_deleted_observer(rfh1->GetRenderViewHost());
1568  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1569            rfh1->GetRenderViewHost()->rvh_state());
1570
1571  // Increment the number of active views in SiteInstanceImpl so that rfh1 is
1572  // not deleted on swap out.
1573  static_cast<SiteInstanceImpl*>(
1574      rfh1->GetSiteInstance())->increment_active_view_count();
1575
1576  // Navigate to new site, simulating onbeforeunload approval.
1577  controller().LoadURL(
1578      kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1579  base::TimeTicks now = base::TimeTicks::Now();
1580  contents()->GetMainFrame()->OnMessageReceived(
1581      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1582  EXPECT_TRUE(contents()->cross_navigation_pending());
1583  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1584            rfh1->GetRenderViewHost()->rvh_state());
1585  TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
1586
1587  // The new page commits.
1588  contents()->TestDidNavigate(rfh2, 1, kUrl2, ui::PAGE_TRANSITION_TYPED);
1589  EXPECT_FALSE(contents()->cross_navigation_pending());
1590  EXPECT_EQ(rfh2, contents()->GetMainFrame());
1591  EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
1592  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1593            rfh2->GetRenderViewHost()->rvh_state());
1594  EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SWAP_OUT,
1595            rfh1->GetRenderViewHost()->rvh_state());
1596
1597  // Simulate the swap out ack.
1598  rfh1->OnSwappedOut(false);
1599
1600  // rfh1 should be swapped out.
1601  EXPECT_FALSE(rvh_deleted_observer.deleted());
1602  EXPECT_TRUE(rfh1->GetRenderViewHost()->IsSwappedOut());
1603}
1604
1605// Test that the RenderViewHost is properly swapped out if a navigation in the
1606// new renderer commits before sending the SwapOut message to the old renderer.
1607// This simulates a cross-site navigation to a synchronously committing URL
1608// (e.g., a data URL) and ensures it works properly.
1609TEST_F(RenderFrameHostManagerTest,
1610       CommitNewNavigationBeforeSendingSwapOut) {
1611  const GURL kUrl1("http://www.google.com/");
1612  const GURL kUrl2("http://www.chromium.org/");
1613
1614  // Navigate to the first page.
1615  contents()->NavigateAndCommit(kUrl1);
1616  TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
1617  RenderViewHostDeletedObserver rvh_deleted_observer(rfh1->GetRenderViewHost());
1618  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1619            rfh1->GetRenderViewHost()->rvh_state());
1620
1621  // Increment the number of active views in SiteInstanceImpl so that rfh1 is
1622  // not deleted on swap out.
1623  static_cast<SiteInstanceImpl*>(
1624      rfh1->GetSiteInstance())->increment_active_view_count();
1625
1626  // Navigate to new site, simulating onbeforeunload approval.
1627  controller().LoadURL(
1628      kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1629  base::TimeTicks now = base::TimeTicks::Now();
1630  rfh1->OnMessageReceived(
1631      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1632  EXPECT_TRUE(contents()->cross_navigation_pending());
1633  TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
1634
1635  // The new page commits.
1636  contents()->TestDidNavigate(rfh2, 1, kUrl2, ui::PAGE_TRANSITION_TYPED);
1637  EXPECT_FALSE(contents()->cross_navigation_pending());
1638  EXPECT_EQ(rfh2, contents()->GetMainFrame());
1639  EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
1640  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT,
1641            rfh2->GetRenderViewHost()->rvh_state());
1642  EXPECT_EQ(RenderViewHostImpl::STATE_PENDING_SWAP_OUT,
1643            rfh1->GetRenderViewHost()->rvh_state());
1644
1645  // Simulate the swap out ack.
1646  rfh1->OnSwappedOut(false);
1647
1648  // rfh1 should be swapped out.
1649  EXPECT_FALSE(rvh_deleted_observer.deleted());
1650  EXPECT_TRUE(rfh1->GetRenderViewHost()->IsSwappedOut());
1651}
1652
1653// Test that a RenderFrameHost is properly deleted or swapped out when a
1654// cross-site navigation is cancelled.
1655TEST_F(RenderFrameHostManagerTest,
1656       CancelPendingProperlyDeletesOrSwaps) {
1657  const GURL kUrl1("http://www.google.com/");
1658  const GURL kUrl2("http://www.chromium.org/");
1659  RenderFrameHostImpl* pending_rfh = NULL;
1660  base::TimeTicks now = base::TimeTicks::Now();
1661
1662  // Navigate to the first page.
1663  contents()->NavigateAndCommit(kUrl1);
1664  TestRenderViewHost* rvh1 = test_rvh();
1665  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, rvh1->rvh_state());
1666
1667  // Navigate to a new site, starting a cross-site navigation.
1668  controller().LoadURL(
1669      kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1670  {
1671    pending_rfh = contents()->GetFrameTree()->root()->render_manager()
1672        ->pending_frame_host();
1673    RenderFrameHostDeletedObserver rvh_deleted_observer(pending_rfh);
1674
1675    // Cancel the navigation by simulating a declined beforeunload dialog.
1676    contents()->GetMainFrame()->OnMessageReceived(
1677        FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
1678    EXPECT_FALSE(contents()->cross_navigation_pending());
1679
1680    // Since the pending RFH is the only one for the new SiteInstance, it should
1681    // be deleted.
1682    EXPECT_TRUE(rvh_deleted_observer.deleted());
1683  }
1684
1685  // Start another cross-site navigation.
1686  controller().LoadURL(
1687      kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1688  {
1689    pending_rfh = contents()->GetFrameTree()->root()->render_manager()
1690        ->pending_frame_host();
1691    RenderFrameHostDeletedObserver rvh_deleted_observer(pending_rfh);
1692
1693    // Increment the number of active views in the new SiteInstance, which will
1694    // cause the pending RFH to be swapped out instead of deleted.
1695    static_cast<SiteInstanceImpl*>(
1696        pending_rfh->GetSiteInstance())->increment_active_view_count();
1697
1698    contents()->GetMainFrame()->OnMessageReceived(
1699        FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
1700    EXPECT_FALSE(contents()->cross_navigation_pending());
1701    EXPECT_FALSE(rvh_deleted_observer.deleted());
1702  }
1703}
1704
1705// PlzNavigate: Test that a proper NavigationRequest is created by
1706// BeginNavigation.
1707TEST_F(RenderFrameHostManagerTest, BrowserSideNavigationBeginNavigation) {
1708  const GURL kUrl1("http://www.google.com/");
1709  const GURL kUrl2("http://www.chromium.org/");
1710  const GURL kUrl3("http://www.gmail.com/");
1711  const int64 kFirstNavRequestID = 1;
1712
1713  // TODO(clamy): we should be enabling browser side navigations here
1714  // when CommitNavigation is properly implemented.
1715  // Navigate to the first page.
1716  contents()->NavigateAndCommit(kUrl1);
1717
1718  EnableBrowserSideNavigation();
1719  // Add a subframe.
1720  TestRenderFrameHost* subframe_rfh = static_cast<TestRenderFrameHost*>(
1721      contents()->GetFrameTree()->AddFrame(
1722          contents()->GetFrameTree()->root(),
1723          contents()->GetMainFrame()->GetProcess()->GetID(),
1724          14, "Child"));
1725
1726  // Simulate a BeginNavigation IPC on the subframe.
1727  subframe_rfh->SendBeginNavigationWithURL(kUrl2);
1728  NavigationRequest* subframe_request =
1729      GetNavigationRequestForRenderFrameManager(
1730          subframe_rfh->frame_tree_node()->render_manager());
1731  ASSERT_TRUE(subframe_request);
1732  EXPECT_EQ(kUrl2, subframe_request->info().navigation_params.url);
1733  // First party for cookies url should be that of the main frame.
1734  EXPECT_EQ(
1735      kUrl1, subframe_request->info().first_party_for_cookies);
1736  EXPECT_FALSE(subframe_request->info().is_main_frame);
1737  EXPECT_TRUE(subframe_request->info().parent_is_main_frame);
1738  EXPECT_EQ(kFirstNavRequestID, subframe_request->navigation_request_id());
1739
1740  // Simulate a BeginNavigation IPC on the main frame.
1741  contents()->GetMainFrame()->SendBeginNavigationWithURL(kUrl3);
1742  NavigationRequest* main_request = GetNavigationRequestForRenderFrameManager(
1743      contents()->GetMainFrame()->frame_tree_node()->render_manager());
1744  ASSERT_TRUE(main_request);
1745  EXPECT_EQ(kUrl3, main_request->info().navigation_params.url);
1746  EXPECT_EQ(kUrl3, main_request->info().first_party_for_cookies);
1747  EXPECT_TRUE(main_request->info().is_main_frame);
1748  EXPECT_FALSE(main_request->info().parent_is_main_frame);
1749  EXPECT_EQ(kFirstNavRequestID + 1, main_request->navigation_request_id());
1750}
1751
1752// PlzNavigate: Test that RequestNavigation creates a NavigationRequest and that
1753// RenderFrameHost is not modified when the navigation commits.
1754TEST_F(RenderFrameHostManagerTest,
1755       BrowserSideNavigationRequestNavigationNoLiveRenderer) {
1756  const GURL kUrl("http://www.google.com/");
1757
1758  EnableBrowserSideNavigation();
1759  EXPECT_FALSE(main_test_rfh()->render_view_host()->IsRenderViewLive());
1760  contents()->GetController().LoadURL(
1761      kUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
1762  RenderFrameHostManager* render_manager =
1763      main_test_rfh()->frame_tree_node()->render_manager();
1764  NavigationRequest* main_request =
1765      GetNavigationRequestForRenderFrameManager(render_manager);
1766  // A NavigationRequest should have been generated.
1767  EXPECT_TRUE(main_request != NULL);
1768  RenderFrameHostImpl* rfh = main_test_rfh();
1769
1770  // Now commit the same url.
1771  NavigationBeforeCommitInfo commit_info;
1772  commit_info.navigation_url = kUrl;
1773  commit_info.navigation_request_id = main_request->navigation_request_id();
1774  render_manager->CommitNavigation(commit_info);
1775  main_request = GetNavigationRequestForRenderFrameManager(render_manager);
1776
1777  // The main RFH should not have been changed, and the renderer should have
1778  // been initialized.
1779  EXPECT_EQ(rfh, main_test_rfh());
1780  EXPECT_TRUE(main_test_rfh()->render_view_host()->IsRenderViewLive());
1781}
1782
1783// PlzNavigate: Test that a new RenderFrameHost is created when doing a cross
1784// site navigation.
1785TEST_F(RenderFrameHostManagerTest,
1786       BrowserSideNavigationCrossSiteNavigation) {
1787  const GURL kUrl1("http://www.chromium.org/");
1788  const GURL kUrl2("http://www.google.com/");
1789
1790  // TODO(clamy): we should be enabling browser side navigations here
1791  // when CommitNavigation is properly implemented.
1792  // Navigate to the first page.
1793  contents()->NavigateAndCommit(kUrl1);
1794  TestRenderViewHost* rvh1 = test_rvh();
1795  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, rvh1->rvh_state());
1796  RenderFrameHostImpl* rfh = main_test_rfh();
1797  RenderFrameHostManager* render_manager =
1798      main_test_rfh()->frame_tree_node()->render_manager();
1799
1800  EnableBrowserSideNavigation();
1801  // Navigate to a different site.
1802  main_test_rfh()->SendBeginNavigationWithURL(kUrl2);
1803  NavigationRequest* main_request =
1804      GetNavigationRequestForRenderFrameManager(render_manager);
1805  ASSERT_TRUE(main_request);
1806
1807  NavigationBeforeCommitInfo commit_info;
1808  commit_info.navigation_url = kUrl2;
1809  commit_info.navigation_request_id = main_request->navigation_request_id();
1810  render_manager->CommitNavigation(commit_info);
1811  EXPECT_NE(main_test_rfh(), rfh);
1812  EXPECT_TRUE(main_test_rfh()->render_view_host()->IsRenderViewLive());
1813}
1814
1815// PlzNavigate: Test that a navigation commit is ignored if another request has
1816// been issued in the meantime.
1817// TODO(carlosk): add checks to assert that the cancel call was sent to
1818// ResourceDispatcherHost in the IO thread by extending
1819// ResourceDispatcherHostDelegate (like in cross_site_transfer_browsertest.cc
1820// and plugin_browsertest.cc).
1821TEST_F(RenderFrameHostManagerTest,
1822       BrowserSideNavigationIgnoreStaleNavigationCommit) {
1823  const GURL kUrl0("http://www.wikipedia.org/");
1824  const GURL kUrl0_site = SiteInstance::GetSiteForURL(browser_context(), kUrl0);
1825  const GURL kUrl1("http://www.chromium.org/");
1826  const GURL kUrl2("http://www.google.com/");
1827  const GURL kUrl2_site = SiteInstance::GetSiteForURL(browser_context(), kUrl2);
1828
1829  // Initialization.
1830  contents()->NavigateAndCommit(kUrl0);
1831  RenderFrameHostManager* render_manager =
1832      main_test_rfh()->frame_tree_node()->render_manager();
1833  EnableBrowserSideNavigation();
1834  EXPECT_EQ(kUrl0_site, main_test_rfh()->GetSiteInstance()->GetSiteURL());
1835
1836  // Request navigation to the 1st URL and gather data.
1837  main_test_rfh()->SendBeginNavigationWithURL(kUrl1);
1838  NavigationRequest* request1 =
1839      GetNavigationRequestForRenderFrameManager(render_manager);
1840  ASSERT_TRUE(request1);
1841  int64 request_id1 = request1->navigation_request_id();
1842
1843  // Request navigation to the 2nd URL and gather more data.
1844  main_test_rfh()->SendBeginNavigationWithURL(kUrl2);
1845  NavigationRequest* request2 =
1846      GetNavigationRequestForRenderFrameManager(render_manager);
1847  ASSERT_TRUE(request2);
1848  int64 request_id2 = request2->navigation_request_id();
1849  EXPECT_NE(request_id1, request_id2);
1850
1851  // Confirms that a stale commit is ignored by the RHFM.
1852  NavigationBeforeCommitInfo nbc_info;
1853  nbc_info.navigation_url = kUrl1;
1854  nbc_info.navigation_request_id = request_id1;
1855  render_manager->CommitNavigation(nbc_info);
1856  EXPECT_EQ(kUrl0_site, main_test_rfh()->GetSiteInstance()->GetSiteURL());
1857
1858  // Confirms that a valid, request-matching commit is correctly processed.
1859  nbc_info.navigation_url = kUrl2;
1860  nbc_info.navigation_request_id = request_id2;
1861  render_manager->CommitNavigation(nbc_info);
1862  EXPECT_EQ(kUrl2_site, main_test_rfh()->GetSiteInstance()->GetSiteURL());
1863}
1864
1865}  // namespace content
1866