web_contents_impl_unittest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 "base/logging.h"
6#include "base/strings/utf_string_conversions.h"
7#include "content/browser/frame_host/cross_site_transferring_request.h"
8#include "content/browser/frame_host/interstitial_page_impl.h"
9#include "content/browser/frame_host/navigation_entry_impl.h"
10#include "content/browser/renderer_host/render_view_host_impl.h"
11#include "content/browser/site_instance_impl.h"
12#include "content/browser/webui/web_ui_controller_factory_registry.h"
13#include "content/common/frame_messages.h"
14#include "content/common/input/synthetic_web_input_event_builders.h"
15#include "content/common/view_messages.h"
16#include "content/public/browser/global_request_id.h"
17#include "content/public/browser/interstitial_page_delegate.h"
18#include "content/public/browser/navigation_details.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
21#include "content/public/browser/render_widget_host_view.h"
22#include "content/public/browser/web_contents_delegate.h"
23#include "content/public/browser/web_contents_observer.h"
24#include "content/public/browser/web_ui_controller.h"
25#include "content/public/common/bindings_policy.h"
26#include "content/public/common/content_constants.h"
27#include "content/public/common/url_constants.h"
28#include "content/public/common/url_utils.h"
29#include "content/public/test/mock_render_process_host.h"
30#include "content/public/test/test_utils.h"
31#include "content/test/test_content_browser_client.h"
32#include "content/test/test_content_client.h"
33#include "content/test/test_render_view_host.h"
34#include "content/test/test_web_contents.h"
35#include "testing/gtest/include/gtest/gtest.h"
36
37namespace content {
38namespace {
39
40const char kTestWebUIUrl[] = "chrome://blah";
41
42class WebContentsImplTestWebUIControllerFactory
43    : public WebUIControllerFactory {
44 public:
45  virtual WebUIController* CreateWebUIControllerForURL(
46      WebUI* web_ui, const GURL& url) const OVERRIDE {
47    if (!UseWebUI(url))
48      return NULL;
49    return new WebUIController(web_ui);
50  }
51
52  virtual WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
53      const GURL& url) const OVERRIDE {
54    return WebUI::kNoWebUI;
55  }
56
57  virtual bool UseWebUIForURL(BrowserContext* browser_context,
58                              const GURL& url) const OVERRIDE {
59    return UseWebUI(url);
60  }
61
62  virtual bool UseWebUIBindingsForURL(BrowserContext* browser_context,
63                                      const GURL& url) const OVERRIDE {
64    return UseWebUI(url);
65  }
66
67 private:
68  bool UseWebUI(const GURL& url) const {
69    return url == GURL(kTestWebUIUrl);
70  }
71};
72
73class TestInterstitialPage;
74
75class TestInterstitialPageDelegate : public InterstitialPageDelegate {
76 public:
77  explicit TestInterstitialPageDelegate(TestInterstitialPage* interstitial_page)
78      : interstitial_page_(interstitial_page) {}
79  virtual void CommandReceived(const std::string& command) OVERRIDE;
80  virtual std::string GetHTMLContents() OVERRIDE { return std::string(); }
81  virtual void OnDontProceed() OVERRIDE;
82  virtual void OnProceed() OVERRIDE;
83 private:
84  TestInterstitialPage* interstitial_page_;
85};
86
87class TestInterstitialPage : public InterstitialPageImpl {
88 public:
89  enum InterstitialState {
90    INVALID = 0,    // Hasn't yet been initialized.
91    UNDECIDED,      // Initialized, but no decision taken yet.
92    OKED,           // Proceed was called.
93    CANCELED        // DontProceed was called.
94  };
95
96  class Delegate {
97   public:
98    virtual void TestInterstitialPageDeleted(
99        TestInterstitialPage* interstitial) = 0;
100
101   protected:
102    virtual ~Delegate() {}
103  };
104
105  // IMPORTANT NOTE: if you pass stack allocated values for |state| and
106  // |deleted| (like all interstitial related tests do at this point), make sure
107  // to create an instance of the TestInterstitialPageStateGuard class on the
108  // stack in your test.  This will ensure that the TestInterstitialPage states
109  // are cleared when the test finishes.
110  // Not doing so will cause stack trashing if your test does not hide the
111  // interstitial, as in such a case it will be destroyed in the test TearDown
112  // method and will dereference the |deleted| local variable which by then is
113  // out of scope.
114  TestInterstitialPage(WebContentsImpl* contents,
115                       bool new_navigation,
116                       const GURL& url,
117                       InterstitialState* state,
118                       bool* deleted)
119      : InterstitialPageImpl(
120            contents,
121            static_cast<RenderWidgetHostDelegate*>(contents),
122            new_navigation, url, new TestInterstitialPageDelegate(this)),
123        state_(state),
124        deleted_(deleted),
125        command_received_count_(0),
126        delegate_(NULL) {
127    *state_ = UNDECIDED;
128    *deleted_ = false;
129  }
130
131  virtual ~TestInterstitialPage() {
132    if (deleted_)
133      *deleted_ = true;
134    if (delegate_)
135      delegate_->TestInterstitialPageDeleted(this);
136  }
137
138  void OnDontProceed() {
139    if (state_)
140      *state_ = CANCELED;
141  }
142  void OnProceed() {
143    if (state_)
144      *state_ = OKED;
145  }
146
147  int command_received_count() const {
148    return command_received_count_;
149  }
150
151  void TestDomOperationResponse(const std::string& json_string) {
152    if (enabled())
153      CommandReceived();
154  }
155
156  void TestDidNavigate(int page_id, const GURL& url) {
157    FrameHostMsg_DidCommitProvisionalLoad_Params params;
158    InitNavigateParams(&params, page_id, url, PAGE_TRANSITION_TYPED);
159    DidNavigate(GetRenderViewHostForTesting(), params);
160  }
161
162  void TestRenderViewTerminated(base::TerminationStatus status,
163                                int error_code) {
164    RenderViewTerminated(GetRenderViewHostForTesting(), status, error_code);
165  }
166
167  bool is_showing() const {
168    return static_cast<TestRenderWidgetHostView*>(
169        GetRenderViewHostForTesting()->GetView())->is_showing();
170  }
171
172  void ClearStates() {
173    state_ = NULL;
174    deleted_ = NULL;
175    delegate_ = NULL;
176  }
177
178  void CommandReceived() {
179    command_received_count_++;
180  }
181
182  void set_delegate(Delegate* delegate) {
183    delegate_ = delegate;
184  }
185
186 protected:
187  virtual WebContentsView* CreateWebContentsView() OVERRIDE {
188    return NULL;
189  }
190
191 private:
192  InterstitialState* state_;
193  bool* deleted_;
194  int command_received_count_;
195  Delegate* delegate_;
196};
197
198void TestInterstitialPageDelegate::CommandReceived(const std::string& command) {
199  interstitial_page_->CommandReceived();
200}
201
202void TestInterstitialPageDelegate::OnDontProceed() {
203  interstitial_page_->OnDontProceed();
204}
205
206void TestInterstitialPageDelegate::OnProceed() {
207  interstitial_page_->OnProceed();
208}
209
210class TestInterstitialPageStateGuard : public TestInterstitialPage::Delegate {
211 public:
212  explicit TestInterstitialPageStateGuard(
213      TestInterstitialPage* interstitial_page)
214      : interstitial_page_(interstitial_page) {
215    DCHECK(interstitial_page_);
216    interstitial_page_->set_delegate(this);
217  }
218  virtual ~TestInterstitialPageStateGuard() {
219    if (interstitial_page_)
220      interstitial_page_->ClearStates();
221  }
222
223  virtual void TestInterstitialPageDeleted(
224      TestInterstitialPage* interstitial) OVERRIDE {
225    DCHECK(interstitial_page_ == interstitial);
226    interstitial_page_ = NULL;
227  }
228
229 private:
230  TestInterstitialPage* interstitial_page_;
231};
232
233class WebContentsImplTestBrowserClient : public TestContentBrowserClient {
234 public:
235  WebContentsImplTestBrowserClient()
236      : assign_site_for_url_(false) {}
237
238  virtual ~WebContentsImplTestBrowserClient() {}
239
240  virtual bool ShouldAssignSiteForURL(const GURL& url) OVERRIDE {
241    return assign_site_for_url_;
242  }
243
244  void set_assign_site_for_url(bool assign) {
245    assign_site_for_url_ = assign;
246  }
247
248 private:
249  bool assign_site_for_url_;
250};
251
252class WebContentsImplTest : public RenderViewHostImplTestHarness {
253 public:
254  virtual void SetUp() {
255    RenderViewHostImplTestHarness::SetUp();
256    WebUIControllerFactory::RegisterFactory(&factory_);
257  }
258
259  virtual void TearDown() {
260    WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
261    RenderViewHostImplTestHarness::TearDown();
262  }
263
264 private:
265  WebContentsImplTestWebUIControllerFactory factory_;
266};
267
268class TestWebContentsObserver : public WebContentsObserver {
269 public:
270  explicit TestWebContentsObserver(WebContents* contents)
271      : WebContentsObserver(contents) {
272  }
273  virtual ~TestWebContentsObserver() {}
274
275  virtual void DidFinishLoad(int64 frame_id,
276                             const GURL& validated_url,
277                             bool is_main_frame,
278                             RenderViewHost* render_view_host) OVERRIDE {
279    last_url_ = validated_url;
280  }
281  virtual void DidFailLoad(int64 frame_id,
282                           const GURL& validated_url,
283                           bool is_main_frame,
284                           int error_code,
285                           const base::string16& error_description,
286                           RenderViewHost* render_view_host) OVERRIDE {
287    last_url_ = validated_url;
288  }
289
290  const GURL& last_url() const { return last_url_; }
291
292 private:
293  GURL last_url_;
294
295  DISALLOW_COPY_AND_ASSIGN(TestWebContentsObserver);
296};
297
298// Pretends to be a normal browser that receives toggles and transitions to/from
299// a fullscreened state.
300class FakeFullscreenDelegate : public WebContentsDelegate {
301 public:
302  FakeFullscreenDelegate() : fullscreened_contents_(NULL) {}
303  virtual ~FakeFullscreenDelegate() {}
304
305  virtual void ToggleFullscreenModeForTab(WebContents* web_contents,
306                                          bool enter_fullscreen) OVERRIDE {
307    fullscreened_contents_ = enter_fullscreen ? web_contents : NULL;
308  }
309
310  virtual bool IsFullscreenForTabOrPending(const WebContents* web_contents)
311      const OVERRIDE {
312    return fullscreened_contents_ && web_contents == fullscreened_contents_;
313  }
314
315 private:
316  WebContents* fullscreened_contents_;
317
318  DISALLOW_COPY_AND_ASSIGN(FakeFullscreenDelegate);
319};
320
321}  // namespace
322
323// Test to make sure that title updates get stripped of whitespace.
324TEST_F(WebContentsImplTest, UpdateTitle) {
325  NavigationControllerImpl& cont =
326      static_cast<NavigationControllerImpl&>(controller());
327  FrameHostMsg_DidCommitProvisionalLoad_Params params;
328  InitNavigateParams(&params, 0, GURL(kAboutBlankURL), PAGE_TRANSITION_TYPED);
329
330  LoadCommittedDetails details;
331  cont.RendererDidNavigate(main_test_rfh(), params, &details);
332
333  contents()->UpdateTitle(rvh(), 0,
334                          base::ASCIIToUTF16("    Lots O' Whitespace\n"),
335                          base::i18n::LEFT_TO_RIGHT);
336  EXPECT_EQ(base::ASCIIToUTF16("Lots O' Whitespace"), contents()->GetTitle());
337}
338
339TEST_F(WebContentsImplTest, DontUseTitleFromPendingEntry) {
340  const GURL kGURL("chrome://blah");
341  controller().LoadURL(
342      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
343  EXPECT_EQ(base::string16(), contents()->GetTitle());
344}
345
346TEST_F(WebContentsImplTest, UseTitleFromPendingEntryIfSet) {
347  const GURL kGURL("chrome://blah");
348  const base::string16 title = base::ASCIIToUTF16("My Title");
349  controller().LoadURL(
350      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
351
352  NavigationEntry* entry = controller().GetVisibleEntry();
353  ASSERT_EQ(kGURL, entry->GetURL());
354  entry->SetTitle(title);
355
356  EXPECT_EQ(title, contents()->GetTitle());
357}
358
359// Test view source mode for a webui page.
360TEST_F(WebContentsImplTest, NTPViewSource) {
361  NavigationControllerImpl& cont =
362      static_cast<NavigationControllerImpl&>(controller());
363  const char kUrl[] = "view-source:chrome://blah";
364  const GURL kGURL(kUrl);
365
366  process()->sink().ClearMessages();
367
368  cont.LoadURL(
369      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
370  rvh()->GetDelegate()->RenderViewCreated(rvh());
371  // Did we get the expected message?
372  EXPECT_TRUE(process()->sink().GetFirstMessageMatching(
373      ViewMsg_EnableViewSourceMode::ID));
374
375  FrameHostMsg_DidCommitProvisionalLoad_Params params;
376  InitNavigateParams(&params, 0, kGURL, PAGE_TRANSITION_TYPED);
377  LoadCommittedDetails details;
378  cont.RendererDidNavigate(main_test_rfh(), params, &details);
379  // Also check title and url.
380  EXPECT_EQ(base::ASCIIToUTF16(kUrl), contents()->GetTitle());
381}
382
383// Test to ensure UpdateMaxPageID is working properly.
384TEST_F(WebContentsImplTest, UpdateMaxPageID) {
385  SiteInstance* instance1 = contents()->GetSiteInstance();
386  scoped_refptr<SiteInstance> instance2(SiteInstance::Create(NULL));
387
388  // Starts at -1.
389  EXPECT_EQ(-1, contents()->GetMaxPageID());
390  EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance1));
391  EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
392
393  // Make sure max_page_id_ is monotonically increasing per SiteInstance.
394  contents()->UpdateMaxPageID(3);
395  contents()->UpdateMaxPageID(1);
396  EXPECT_EQ(3, contents()->GetMaxPageID());
397  EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1));
398  EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
399
400  contents()->UpdateMaxPageIDForSiteInstance(instance2.get(), 7);
401  EXPECT_EQ(3, contents()->GetMaxPageID());
402  EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1));
403  EXPECT_EQ(7, contents()->GetMaxPageIDForSiteInstance(instance2.get()));
404}
405
406// Test simple same-SiteInstance navigation.
407TEST_F(WebContentsImplTest, SimpleNavigation) {
408  TestRenderViewHost* orig_rvh = test_rvh();
409  SiteInstance* instance1 = contents()->GetSiteInstance();
410  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
411
412  // Navigate to URL
413  const GURL url("http://www.google.com");
414  controller().LoadURL(
415      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
416  EXPECT_FALSE(contents()->cross_navigation_pending());
417  EXPECT_EQ(instance1, orig_rvh->GetSiteInstance());
418  // Controller's pending entry will have a NULL site instance until we assign
419  // it in DidNavigate.
420  EXPECT_TRUE(
421      NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())->
422          site_instance() == NULL);
423
424  // DidNavigate from the page
425  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
426  EXPECT_FALSE(contents()->cross_navigation_pending());
427  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
428  EXPECT_EQ(instance1, orig_rvh->GetSiteInstance());
429  // Controller's entry should now have the SiteInstance, or else we won't be
430  // able to find it later.
431  EXPECT_EQ(
432      instance1,
433      NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())->
434          site_instance());
435}
436
437// Test that we reject NavigateToEntry if the url is over kMaxURLChars.
438TEST_F(WebContentsImplTest, NavigateToExcessivelyLongURL) {
439  // Construct a URL that's kMaxURLChars + 1 long of all 'a's.
440  const GURL url(std::string("http://example.org/").append(
441      GetMaxURLChars() + 1, 'a'));
442
443  controller().LoadURL(
444      url, Referrer(), PAGE_TRANSITION_GENERATED, std::string());
445  EXPECT_TRUE(controller().GetVisibleEntry() == NULL);
446}
447
448// Test that navigating across a site boundary creates a new RenderViewHost
449// with a new SiteInstance.  Going back should do the same.
450TEST_F(WebContentsImplTest, CrossSiteBoundaries) {
451  contents()->transition_cross_site = true;
452  TestRenderViewHost* orig_rvh = test_rvh();
453  RenderFrameHostImpl* orig_rfh =
454      contents()->GetFrameTree()->root()->current_frame_host();
455  int orig_rvh_delete_count = 0;
456  orig_rvh->set_delete_counter(&orig_rvh_delete_count);
457  SiteInstance* instance1 = contents()->GetSiteInstance();
458
459  // Navigate to URL.  First URL should use first RenderViewHost.
460  const GURL url("http://www.google.com");
461  controller().LoadURL(
462      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
463  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
464
465  // Keep the number of active views in orig_rvh's SiteInstance
466  // non-zero so that orig_rvh doesn't get deleted when it gets
467  // swapped out.
468  static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
469      increment_active_view_count();
470
471  EXPECT_FALSE(contents()->cross_navigation_pending());
472  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
473  EXPECT_EQ(url, contents()->GetLastCommittedURL());
474  EXPECT_EQ(url, contents()->GetVisibleURL());
475
476  // Navigate to new site
477  const GURL url2("http://www.yahoo.com");
478  controller().LoadURL(
479      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
480  EXPECT_TRUE(contents()->cross_navigation_pending());
481  EXPECT_EQ(url, contents()->GetLastCommittedURL());
482  EXPECT_EQ(url2, contents()->GetVisibleURL());
483  TestRenderViewHost* pending_rvh =
484      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
485  int pending_rvh_delete_count = 0;
486  pending_rvh->set_delete_counter(&pending_rvh_delete_count);
487  RenderFrameHostImpl* pending_rfh = contents()->GetFrameTree()->root()->
488      render_manager()->pending_frame_host();
489
490  // Navigations should be suspended in pending_rvh until BeforeUnloadACK.
491  EXPECT_TRUE(pending_rvh->are_navigations_suspended());
492  orig_rvh->SendBeforeUnloadACK(true);
493  EXPECT_FALSE(pending_rvh->are_navigations_suspended());
494
495  // DidNavigate from the pending page
496  contents()->TestDidNavigate(
497      pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
498  SiteInstance* instance2 = contents()->GetSiteInstance();
499
500  // Keep the number of active views in pending_rvh's SiteInstance
501  // non-zero so that orig_rvh doesn't get deleted when it gets
502  // swapped out.
503  static_cast<SiteInstanceImpl*>(pending_rvh->GetSiteInstance())->
504      increment_active_view_count();
505
506  EXPECT_FALSE(contents()->cross_navigation_pending());
507  EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
508  EXPECT_EQ(url2, contents()->GetLastCommittedURL());
509  EXPECT_EQ(url2, contents()->GetVisibleURL());
510  EXPECT_NE(instance1, instance2);
511  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
512  // We keep the original RFH around, swapped out.
513  EXPECT_TRUE(contents()->GetRenderManagerForTesting()->IsOnSwappedOutList(
514      orig_rfh));
515  EXPECT_EQ(orig_rvh_delete_count, 0);
516
517  // Going back should switch SiteInstances again.  The first SiteInstance is
518  // stored in the NavigationEntry, so it should be the same as at the start.
519  // We should use the same RVH as before, swapping it back in.
520  controller().GoBack();
521  TestRenderViewHost* goback_rvh =
522      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
523  EXPECT_EQ(orig_rvh, goback_rvh);
524  EXPECT_TRUE(contents()->cross_navigation_pending());
525
526  // Navigations should be suspended in goback_rvh until BeforeUnloadACK.
527  EXPECT_TRUE(goback_rvh->are_navigations_suspended());
528  pending_rvh->SendBeforeUnloadACK(true);
529  EXPECT_FALSE(goback_rvh->are_navigations_suspended());
530
531  // DidNavigate from the back action
532  contents()->TestDidNavigate(
533      goback_rvh, 1, url2, PAGE_TRANSITION_TYPED);
534  EXPECT_FALSE(contents()->cross_navigation_pending());
535  EXPECT_EQ(goback_rvh, contents()->GetRenderViewHost());
536  EXPECT_EQ(instance1, contents()->GetSiteInstance());
537  // The pending RFH should now be swapped out, not deleted.
538  EXPECT_TRUE(contents()->GetRenderManagerForTesting()->
539      IsOnSwappedOutList(pending_rfh));
540  EXPECT_EQ(pending_rvh_delete_count, 0);
541  pending_rvh->OnSwappedOut(false);
542
543  // Close contents and ensure RVHs are deleted.
544  DeleteContents();
545  EXPECT_EQ(orig_rvh_delete_count, 1);
546  EXPECT_EQ(pending_rvh_delete_count, 1);
547}
548
549// Test that navigating across a site boundary after a crash creates a new
550// RVH without requiring a cross-site transition (i.e., PENDING state).
551TEST_F(WebContentsImplTest, CrossSiteBoundariesAfterCrash) {
552  contents()->transition_cross_site = true;
553  TestRenderViewHost* orig_rvh = test_rvh();
554  int orig_rvh_delete_count = 0;
555  orig_rvh->set_delete_counter(&orig_rvh_delete_count);
556  SiteInstance* instance1 = contents()->GetSiteInstance();
557
558  // Navigate to URL.  First URL should use first RenderViewHost.
559  const GURL url("http://www.google.com");
560  controller().LoadURL(
561      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
562  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
563
564  EXPECT_FALSE(contents()->cross_navigation_pending());
565  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
566
567  // Crash the renderer.
568  orig_rvh->set_render_view_created(false);
569
570  // Navigate to new site.  We should not go into PENDING.
571  const GURL url2("http://www.yahoo.com");
572  controller().LoadURL(
573      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
574  RenderViewHost* new_rvh = rvh();
575  EXPECT_FALSE(contents()->cross_navigation_pending());
576  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
577  EXPECT_NE(orig_rvh, new_rvh);
578  EXPECT_EQ(orig_rvh_delete_count, 1);
579
580  // DidNavigate from the new page
581  contents()->TestDidNavigate(new_rvh, 1, url2, PAGE_TRANSITION_TYPED);
582  SiteInstance* instance2 = contents()->GetSiteInstance();
583
584  EXPECT_FALSE(contents()->cross_navigation_pending());
585  EXPECT_EQ(new_rvh, rvh());
586  EXPECT_NE(instance1, instance2);
587  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
588
589  // Close contents and ensure RVHs are deleted.
590  DeleteContents();
591  EXPECT_EQ(orig_rvh_delete_count, 1);
592}
593
594// Test that opening a new contents in the same SiteInstance and then navigating
595// both contentses to a new site will place both contentses in a single
596// SiteInstance.
597TEST_F(WebContentsImplTest, NavigateTwoTabsCrossSite) {
598  contents()->transition_cross_site = true;
599  TestRenderViewHost* orig_rvh = test_rvh();
600  SiteInstance* instance1 = contents()->GetSiteInstance();
601
602  // Navigate to URL.  First URL should use first RenderViewHost.
603  const GURL url("http://www.google.com");
604  controller().LoadURL(
605      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
606  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
607
608  // Open a new contents with the same SiteInstance, navigated to the same site.
609  scoped_ptr<TestWebContents> contents2(
610      TestWebContents::Create(browser_context(), instance1));
611  contents2->transition_cross_site = true;
612  contents2->GetController().LoadURL(url, Referrer(),
613                                    PAGE_TRANSITION_TYPED,
614                                    std::string());
615  // Need this page id to be 2 since the site instance is the same (which is the
616  // scope of page IDs) and we want to consider this a new page.
617  contents2->TestDidNavigate(
618      contents2->GetRenderViewHost(), 2, url, PAGE_TRANSITION_TYPED);
619
620  // Navigate first contents to a new site.
621  const GURL url2a("http://www.yahoo.com");
622  controller().LoadURL(
623      url2a, Referrer(), PAGE_TRANSITION_TYPED, std::string());
624  orig_rvh->SendBeforeUnloadACK(true);
625  TestRenderViewHost* pending_rvh_a =
626      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
627  contents()->TestDidNavigate(
628      pending_rvh_a, 1, url2a, PAGE_TRANSITION_TYPED);
629  SiteInstance* instance2a = contents()->GetSiteInstance();
630  EXPECT_NE(instance1, instance2a);
631
632  // Navigate second contents to the same site as the first tab.
633  const GURL url2b("http://mail.yahoo.com");
634  contents2->GetController().LoadURL(url2b, Referrer(),
635                                    PAGE_TRANSITION_TYPED,
636                                    std::string());
637  TestRenderViewHost* rvh2 =
638      static_cast<TestRenderViewHost*>(contents2->GetRenderViewHost());
639  rvh2->SendBeforeUnloadACK(true);
640  TestRenderViewHost* pending_rvh_b =
641      static_cast<TestRenderViewHost*>(contents2->GetPendingRenderViewHost());
642  EXPECT_TRUE(pending_rvh_b != NULL);
643  EXPECT_TRUE(contents2->cross_navigation_pending());
644
645  // NOTE(creis): We used to be in danger of showing a crash page here if the
646  // second contents hadn't navigated somewhere first (bug 1145430).  That case
647  // is now covered by the CrossSiteBoundariesAfterCrash test.
648  contents2->TestDidNavigate(
649      pending_rvh_b, 2, url2b, PAGE_TRANSITION_TYPED);
650  SiteInstance* instance2b = contents2->GetSiteInstance();
651  EXPECT_NE(instance1, instance2b);
652
653  // Both contentses should now be in the same SiteInstance.
654  EXPECT_EQ(instance2a, instance2b);
655}
656
657TEST_F(WebContentsImplTest, NavigateDoesNotUseUpSiteInstance) {
658  WebContentsImplTestBrowserClient browser_client;
659  SetBrowserClientForTesting(&browser_client);
660
661  contents()->transition_cross_site = true;
662  TestRenderViewHost* orig_rvh = test_rvh();
663  RenderFrameHostImpl* orig_rfh =
664      contents()->GetFrameTree()->root()->current_frame_host();
665  int orig_rvh_delete_count = 0;
666  orig_rvh->set_delete_counter(&orig_rvh_delete_count);
667  SiteInstanceImpl* orig_instance =
668      static_cast<SiteInstanceImpl*>(contents()->GetSiteInstance());
669
670  browser_client.set_assign_site_for_url(false);
671  // Navigate to an URL that will not assign a new SiteInstance.
672  const GURL native_url("non-site-url://stuffandthings");
673  controller().LoadURL(
674      native_url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
675  contents()->TestDidNavigate(orig_rvh, 1, native_url, PAGE_TRANSITION_TYPED);
676
677  EXPECT_FALSE(contents()->cross_navigation_pending());
678  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
679  EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
680  EXPECT_EQ(native_url, contents()->GetVisibleURL());
681  EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
682  EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL());
683  EXPECT_FALSE(orig_instance->HasSite());
684
685  browser_client.set_assign_site_for_url(true);
686  // Navigate to new site (should keep same site instance).
687  const GURL url("http://www.google.com");
688  controller().LoadURL(
689      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
690  EXPECT_FALSE(contents()->cross_navigation_pending());
691  EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
692  EXPECT_EQ(url, contents()->GetVisibleURL());
693  EXPECT_FALSE(contents()->GetPendingRenderViewHost());
694  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
695
696  // Keep the number of active views in orig_rvh's SiteInstance
697  // non-zero so that orig_rvh doesn't get deleted when it gets
698  // swapped out.
699  static_cast<SiteInstanceImpl*>(orig_rvh->GetSiteInstance())->
700      increment_active_view_count();
701
702  EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
703  EXPECT_TRUE(
704      contents()->GetSiteInstance()->GetSiteURL().DomainIs("google.com"));
705  EXPECT_EQ(url, contents()->GetLastCommittedURL());
706
707  // Navigate to another new site (should create a new site instance).
708  const GURL url2("http://www.yahoo.com");
709  controller().LoadURL(
710      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
711  EXPECT_TRUE(contents()->cross_navigation_pending());
712  EXPECT_EQ(url, contents()->GetLastCommittedURL());
713  EXPECT_EQ(url2, contents()->GetVisibleURL());
714  TestRenderViewHost* pending_rvh =
715      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
716  int pending_rvh_delete_count = 0;
717  pending_rvh->set_delete_counter(&pending_rvh_delete_count);
718
719  // Navigations should be suspended in pending_rvh until BeforeUnloadACK.
720  EXPECT_TRUE(pending_rvh->are_navigations_suspended());
721  orig_rvh->SendBeforeUnloadACK(true);
722  EXPECT_FALSE(pending_rvh->are_navigations_suspended());
723
724  // DidNavigate from the pending page.
725  contents()->TestDidNavigate(
726      pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
727  SiteInstance* new_instance = contents()->GetSiteInstance();
728
729  EXPECT_FALSE(contents()->cross_navigation_pending());
730  EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
731  EXPECT_EQ(url2, contents()->GetLastCommittedURL());
732  EXPECT_EQ(url2, contents()->GetVisibleURL());
733  EXPECT_NE(new_instance, orig_instance);
734  EXPECT_FALSE(contents()->GetPendingRenderViewHost());
735  // We keep the original RFH around, swapped out.
736  EXPECT_TRUE(contents()->GetRenderManagerForTesting()->IsOnSwappedOutList(
737      orig_rfh));
738  EXPECT_EQ(orig_rvh_delete_count, 0);
739  orig_rvh->OnSwappedOut(false);
740
741  // Close contents and ensure RVHs are deleted.
742  DeleteContents();
743  EXPECT_EQ(orig_rvh_delete_count, 1);
744  EXPECT_EQ(pending_rvh_delete_count, 1);
745}
746
747// Test that we can find an opener RVH even if it's pending.
748// http://crbug.com/176252.
749TEST_F(WebContentsImplTest, FindOpenerRVHWhenPending) {
750  contents()->transition_cross_site = true;
751  TestRenderViewHost* orig_rvh = test_rvh();
752
753  // Navigate to a URL.
754  const GURL url("http://www.google.com");
755  controller().LoadURL(
756      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
757  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
758
759  // Start to navigate first tab to a new site, so that it has a pending RVH.
760  const GURL url2("http://www.yahoo.com");
761  controller().LoadURL(
762      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
763  orig_rvh->SendBeforeUnloadACK(true);
764  TestRenderViewHost* pending_rvh =
765      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
766
767  // While it is still pending, simulate opening a new tab with the first tab
768  // as its opener.  This will call WebContentsImpl::CreateOpenerRenderViews
769  // on the opener to ensure that an RVH exists.
770  int opener_routing_id = contents()->CreateOpenerRenderViews(
771      pending_rvh->GetSiteInstance());
772
773  // We should find the pending RVH and not create a new one.
774  EXPECT_EQ(pending_rvh->GetRoutingID(), opener_routing_id);
775}
776
777// Tests that WebContentsImpl uses the current URL, not the SiteInstance's site,
778// to determine whether a navigation is cross-site.
779TEST_F(WebContentsImplTest, CrossSiteComparesAgainstCurrentPage) {
780  contents()->transition_cross_site = true;
781  RenderViewHost* orig_rvh = rvh();
782  SiteInstance* instance1 = contents()->GetSiteInstance();
783
784  // Navigate to URL.
785  const GURL url("http://www.google.com");
786  controller().LoadURL(
787      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
788  contents()->TestDidNavigate(
789      orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
790
791  // Open a related contents to a second site.
792  scoped_ptr<TestWebContents> contents2(
793      TestWebContents::Create(browser_context(), instance1));
794  contents2->transition_cross_site = true;
795  const GURL url2("http://www.yahoo.com");
796  contents2->GetController().LoadURL(url2, Referrer(),
797                                    PAGE_TRANSITION_TYPED,
798                                    std::string());
799  // The first RVH in contents2 isn't live yet, so we shortcut the cross site
800  // pending.
801  TestRenderViewHost* rvh2 = static_cast<TestRenderViewHost*>(
802      contents2->GetRenderViewHost());
803  EXPECT_FALSE(contents2->cross_navigation_pending());
804  contents2->TestDidNavigate(rvh2, 2, url2, PAGE_TRANSITION_TYPED);
805  SiteInstance* instance2 = contents2->GetSiteInstance();
806  EXPECT_NE(instance1, instance2);
807  EXPECT_FALSE(contents2->cross_navigation_pending());
808
809  // Simulate a link click in first contents to second site.  Doesn't switch
810  // SiteInstances, because we don't intercept WebKit navigations.
811  contents()->TestDidNavigate(
812      orig_rvh, 2, url2, PAGE_TRANSITION_TYPED);
813  SiteInstance* instance3 = contents()->GetSiteInstance();
814  EXPECT_EQ(instance1, instance3);
815  EXPECT_FALSE(contents()->cross_navigation_pending());
816
817  // Navigate to the new site.  Doesn't switch SiteInstancees, because we
818  // compare against the current URL, not the SiteInstance's site.
819  const GURL url3("http://mail.yahoo.com");
820  controller().LoadURL(
821      url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
822  EXPECT_FALSE(contents()->cross_navigation_pending());
823  contents()->TestDidNavigate(
824      orig_rvh, 3, url3, PAGE_TRANSITION_TYPED);
825  SiteInstance* instance4 = contents()->GetSiteInstance();
826  EXPECT_EQ(instance1, instance4);
827}
828
829// Test that the onbeforeunload and onunload handlers run when navigating
830// across site boundaries.
831TEST_F(WebContentsImplTest, CrossSiteUnloadHandlers) {
832  contents()->transition_cross_site = true;
833  TestRenderViewHost* orig_rvh = test_rvh();
834  SiteInstance* instance1 = contents()->GetSiteInstance();
835
836  // Navigate to URL.  First URL should use first RenderViewHost.
837  const GURL url("http://www.google.com");
838  controller().LoadURL(
839      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
840  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
841  EXPECT_FALSE(contents()->cross_navigation_pending());
842  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
843
844  // Navigate to new site, but simulate an onbeforeunload denial.
845  const GURL url2("http://www.yahoo.com");
846  controller().LoadURL(
847      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
848  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
849  base::TimeTicks now = base::TimeTicks::Now();
850  orig_rvh->GetMainFrame()->OnMessageReceived(
851      FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
852  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
853  EXPECT_FALSE(contents()->cross_navigation_pending());
854  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
855
856  // Navigate again, but simulate an onbeforeunload approval.
857  controller().LoadURL(
858      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
859  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
860  now = base::TimeTicks::Now();
861  orig_rvh->GetMainFrame()->OnMessageReceived(
862      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
863  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
864  EXPECT_TRUE(contents()->cross_navigation_pending());
865  TestRenderViewHost* pending_rvh = static_cast<TestRenderViewHost*>(
866      contents()->GetPendingRenderViewHost());
867
868  // We won't hear DidNavigate until the onunload handler has finished running.
869
870  // DidNavigate from the pending page.
871  contents()->TestDidNavigate(
872      pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
873  SiteInstance* instance2 = contents()->GetSiteInstance();
874  EXPECT_FALSE(contents()->cross_navigation_pending());
875  EXPECT_EQ(pending_rvh, rvh());
876  EXPECT_NE(instance1, instance2);
877  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
878}
879
880// Test that during a slow cross-site navigation, the original renderer can
881// navigate to a different URL and have it displayed, canceling the slow
882// navigation.
883TEST_F(WebContentsImplTest, CrossSiteNavigationPreempted) {
884  contents()->transition_cross_site = true;
885  TestRenderViewHost* orig_rvh = test_rvh();
886  SiteInstance* instance1 = contents()->GetSiteInstance();
887
888  // Navigate to URL.  First URL should use first RenderViewHost.
889  const GURL url("http://www.google.com");
890  controller().LoadURL(
891      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
892  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
893  EXPECT_FALSE(contents()->cross_navigation_pending());
894  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
895
896  // Navigate to new site, simulating an onbeforeunload approval.
897  const GURL url2("http://www.yahoo.com");
898  controller().LoadURL(
899      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
900  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
901  base::TimeTicks now = base::TimeTicks::Now();
902  orig_rvh->GetMainFrame()->OnMessageReceived(
903      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
904  EXPECT_TRUE(contents()->cross_navigation_pending());
905
906  // Suppose the original renderer navigates before the new one is ready.
907  orig_rvh->SendNavigate(2, GURL("http://www.google.com/foo"));
908
909  // Verify that the pending navigation is cancelled.
910  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
911  SiteInstance* instance2 = contents()->GetSiteInstance();
912  EXPECT_FALSE(contents()->cross_navigation_pending());
913  EXPECT_EQ(orig_rvh, rvh());
914  EXPECT_EQ(instance1, instance2);
915  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
916}
917
918TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) {
919  contents()->transition_cross_site = true;
920
921  // Start with a web ui page, which gets a new RVH with WebUI bindings.
922  const GURL url1("chrome://blah");
923  controller().LoadURL(
924      url1, Referrer(), PAGE_TRANSITION_TYPED, std::string());
925  TestRenderViewHost* ntp_rvh = test_rvh();
926  contents()->TestDidNavigate(ntp_rvh, 1, url1, PAGE_TRANSITION_TYPED);
927  NavigationEntry* entry1 = controller().GetLastCommittedEntry();
928  SiteInstance* instance1 = contents()->GetSiteInstance();
929
930  EXPECT_FALSE(contents()->cross_navigation_pending());
931  EXPECT_EQ(ntp_rvh, contents()->GetRenderViewHost());
932  EXPECT_EQ(url1, entry1->GetURL());
933  EXPECT_EQ(instance1,
934            NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
935  EXPECT_TRUE(ntp_rvh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
936
937  // Navigate to new site.
938  const GURL url2("http://www.google.com");
939  controller().LoadURL(
940      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
941  EXPECT_TRUE(contents()->cross_navigation_pending());
942  TestRenderViewHost* google_rvh =
943      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
944
945  // Simulate beforeunload approval.
946  EXPECT_TRUE(ntp_rvh->is_waiting_for_beforeunload_ack());
947  base::TimeTicks now = base::TimeTicks::Now();
948  ntp_rvh->GetMainFrame()->OnMessageReceived(
949      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
950
951  // DidNavigate from the pending page.
952  contents()->TestDidNavigate(
953      google_rvh, 1, url2, PAGE_TRANSITION_TYPED);
954  NavigationEntry* entry2 = controller().GetLastCommittedEntry();
955  SiteInstance* instance2 = contents()->GetSiteInstance();
956
957  EXPECT_FALSE(contents()->cross_navigation_pending());
958  EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
959  EXPECT_NE(instance1, instance2);
960  EXPECT_FALSE(contents()->GetPendingRenderViewHost());
961  EXPECT_EQ(url2, entry2->GetURL());
962  EXPECT_EQ(instance2,
963            NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
964  EXPECT_FALSE(google_rvh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
965
966  // Navigate to third page on same site.
967  const GURL url3("http://news.google.com");
968  controller().LoadURL(
969      url3, Referrer(), PAGE_TRANSITION_TYPED, std::string());
970  EXPECT_FALSE(contents()->cross_navigation_pending());
971  contents()->TestDidNavigate(
972      google_rvh, 2, url3, PAGE_TRANSITION_TYPED);
973  NavigationEntry* entry3 = controller().GetLastCommittedEntry();
974  SiteInstance* instance3 = contents()->GetSiteInstance();
975
976  EXPECT_FALSE(contents()->cross_navigation_pending());
977  EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
978  EXPECT_EQ(instance2, instance3);
979  EXPECT_FALSE(contents()->GetPendingRenderViewHost());
980  EXPECT_EQ(url3, entry3->GetURL());
981  EXPECT_EQ(instance3,
982            NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
983
984  // Go back within the site.
985  controller().GoBack();
986  EXPECT_FALSE(contents()->cross_navigation_pending());
987  EXPECT_EQ(entry2, controller().GetPendingEntry());
988
989  // Before that commits, go back again.
990  controller().GoBack();
991  EXPECT_TRUE(contents()->cross_navigation_pending());
992  EXPECT_TRUE(contents()->GetPendingRenderViewHost());
993  EXPECT_EQ(entry1, controller().GetPendingEntry());
994
995  // Simulate beforeunload approval.
996  EXPECT_TRUE(google_rvh->is_waiting_for_beforeunload_ack());
997  now = base::TimeTicks::Now();
998  google_rvh->GetMainFrame()->OnMessageReceived(
999      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1000
1001  // DidNavigate from the first back. This aborts the second back's pending RVH.
1002  contents()->TestDidNavigate(google_rvh, 1, url2, PAGE_TRANSITION_TYPED);
1003
1004  // We should commit this page and forget about the second back.
1005  EXPECT_FALSE(contents()->cross_navigation_pending());
1006  EXPECT_FALSE(controller().GetPendingEntry());
1007  EXPECT_EQ(google_rvh, contents()->GetRenderViewHost());
1008  EXPECT_EQ(url2, controller().GetLastCommittedEntry()->GetURL());
1009
1010  // We should not have corrupted the NTP entry.
1011  EXPECT_EQ(instance3,
1012            NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
1013  EXPECT_EQ(instance2,
1014            NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
1015  EXPECT_EQ(instance1,
1016            NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
1017  EXPECT_EQ(url1, entry1->GetURL());
1018}
1019
1020// Test that during a slow cross-site navigation, a sub-frame navigation in the
1021// original renderer will not cancel the slow navigation (bug 42029).
1022TEST_F(WebContentsImplTest, CrossSiteNavigationNotPreemptedByFrame) {
1023  contents()->transition_cross_site = true;
1024  TestRenderViewHost* orig_rvh = test_rvh();
1025
1026  // Navigate to URL.  First URL should use first RenderViewHost.
1027  const GURL url("http://www.google.com");
1028  controller().LoadURL(
1029      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1030  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1031  EXPECT_FALSE(contents()->cross_navigation_pending());
1032  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1033
1034  // Start navigating to new site.
1035  const GURL url2("http://www.yahoo.com");
1036  controller().LoadURL(
1037      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1038
1039  // Simulate a sub-frame navigation arriving and ensure the RVH is still
1040  // waiting for a before unload response.
1041  orig_rvh->SendNavigateWithTransition(1, GURL("http://google.com/frame"),
1042                                       PAGE_TRANSITION_AUTO_SUBFRAME);
1043  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
1044
1045  // Now simulate the onbeforeunload approval and verify the navigation is
1046  // not canceled.
1047  base::TimeTicks now = base::TimeTicks::Now();
1048  orig_rvh->GetMainFrame()->OnMessageReceived(
1049      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1050  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
1051  EXPECT_TRUE(contents()->cross_navigation_pending());
1052}
1053
1054// Test that a cross-site navigation is not preempted if the previous
1055// renderer sends a FrameNavigate message just before being told to stop.
1056// We should only preempt the cross-site navigation if the previous renderer
1057// has started a new navigation.  See http://crbug.com/79176.
1058TEST_F(WebContentsImplTest, CrossSiteNotPreemptedDuringBeforeUnload) {
1059  contents()->transition_cross_site = true;
1060
1061  // Navigate to NTP URL.
1062  const GURL url("chrome://blah");
1063  controller().LoadURL(
1064      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1065  TestRenderViewHost* orig_rvh = test_rvh();
1066  EXPECT_FALSE(contents()->cross_navigation_pending());
1067
1068  // Navigate to new site, with the beforeunload request in flight.
1069  const GURL url2("http://www.yahoo.com");
1070  controller().LoadURL(
1071      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1072  TestRenderViewHost* pending_rvh =
1073      static_cast<TestRenderViewHost*>(contents()->GetPendingRenderViewHost());
1074  EXPECT_TRUE(contents()->cross_navigation_pending());
1075  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
1076
1077  // Suppose the first navigation tries to commit now, with a
1078  // ViewMsg_Stop in flight.  This should not cancel the pending navigation,
1079  // but it should act as if the beforeunload ack arrived.
1080  orig_rvh->SendNavigate(1, GURL("chrome://blah"));
1081  EXPECT_TRUE(contents()->cross_navigation_pending());
1082  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1083  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
1084
1085  // The pending navigation should be able to commit successfully.
1086  contents()->TestDidNavigate(pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
1087  EXPECT_FALSE(contents()->cross_navigation_pending());
1088  EXPECT_EQ(pending_rvh, contents()->GetRenderViewHost());
1089}
1090
1091// Test that the original renderer cannot preempt a cross-site navigation once
1092// the unload request has been made.  At this point, the cross-site navigation
1093// is almost ready to be displayed, and the original renderer is only given a
1094// short chance to run an unload handler.  Prevents regression of bug 23942.
1095TEST_F(WebContentsImplTest, CrossSiteCantPreemptAfterUnload) {
1096  contents()->transition_cross_site = true;
1097  TestRenderViewHost* orig_rvh = test_rvh();
1098  SiteInstance* instance1 = contents()->GetSiteInstance();
1099
1100  // Navigate to URL.  First URL should use first RenderViewHost.
1101  const GURL url("http://www.google.com");
1102  controller().LoadURL(
1103      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1104  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1105  EXPECT_FALSE(contents()->cross_navigation_pending());
1106  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1107
1108  // Navigate to new site, simulating an onbeforeunload approval.
1109  const GURL url2("http://www.yahoo.com");
1110  controller().LoadURL(
1111      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1112  base::TimeTicks now = base::TimeTicks::Now();
1113  orig_rvh->GetMainFrame()->OnMessageReceived(
1114      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1115  EXPECT_TRUE(contents()->cross_navigation_pending());
1116  TestRenderViewHost* pending_rvh = static_cast<TestRenderViewHost*>(
1117      contents()->GetPendingRenderViewHost());
1118
1119  // Simulate the pending renderer's response, which leads to an unload request
1120  // being sent to orig_rvh.
1121  std::vector<GURL> url_chain;
1122  url_chain.push_back(GURL());
1123  contents()->GetRenderManagerForTesting()->OnCrossSiteResponse(
1124      contents()->GetRenderManagerForTesting()->pending_frame_host(),
1125      GlobalRequestID(0, 0), scoped_ptr<CrossSiteTransferringRequest>(),
1126      url_chain, Referrer(), PAGE_TRANSITION_TYPED, false);
1127
1128  // Suppose the original renderer navigates now, while the unload request is in
1129  // flight.  We should ignore it, wait for the unload ack, and let the pending
1130  // request continue.  Otherwise, the contents may close spontaneously or stop
1131  // responding to navigation requests.  (See bug 23942.)
1132  FrameHostMsg_DidCommitProvisionalLoad_Params params1a;
1133  InitNavigateParams(&params1a, 2, GURL("http://www.google.com/foo"),
1134                     PAGE_TRANSITION_TYPED);
1135  orig_rvh->SendNavigate(2, GURL("http://www.google.com/foo"));
1136
1137  // Verify that the pending navigation is still in progress.
1138  EXPECT_TRUE(contents()->cross_navigation_pending());
1139  EXPECT_TRUE(contents()->GetPendingRenderViewHost() != NULL);
1140
1141  // DidNavigate from the pending page should commit it.
1142  contents()->TestDidNavigate(
1143      pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
1144  SiteInstance* instance2 = contents()->GetSiteInstance();
1145  EXPECT_FALSE(contents()->cross_navigation_pending());
1146  EXPECT_EQ(pending_rvh, rvh());
1147  EXPECT_NE(instance1, instance2);
1148  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
1149}
1150
1151// Test that a cross-site navigation that doesn't commit after the unload
1152// handler doesn't leave the contents in a stuck state.  http://crbug.com/88562
1153TEST_F(WebContentsImplTest, CrossSiteNavigationCanceled) {
1154  contents()->transition_cross_site = true;
1155  TestRenderViewHost* orig_rvh = test_rvh();
1156  SiteInstance* instance1 = contents()->GetSiteInstance();
1157
1158  // Navigate to URL.  First URL should use first RenderViewHost.
1159  const GURL url("http://www.google.com");
1160  controller().LoadURL(
1161      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1162  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1163  EXPECT_FALSE(contents()->cross_navigation_pending());
1164  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1165
1166  // Navigate to new site, simulating an onbeforeunload approval.
1167  const GURL url2("http://www.yahoo.com");
1168  controller().LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1169  EXPECT_TRUE(orig_rvh->is_waiting_for_beforeunload_ack());
1170  base::TimeTicks now = base::TimeTicks::Now();
1171  orig_rvh->GetMainFrame()->OnMessageReceived(
1172      FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
1173  EXPECT_TRUE(contents()->cross_navigation_pending());
1174
1175  // Simulate swap out message when the response arrives.
1176  orig_rvh->OnSwappedOut(false);
1177
1178  // Suppose the navigation doesn't get a chance to commit, and the user
1179  // navigates in the current RVH's SiteInstance.
1180  controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1181
1182  // Verify that the pending navigation is cancelled and the renderer is no
1183  // longer swapped out.
1184  EXPECT_FALSE(orig_rvh->is_waiting_for_beforeunload_ack());
1185  SiteInstance* instance2 = contents()->GetSiteInstance();
1186  EXPECT_FALSE(contents()->cross_navigation_pending());
1187  EXPECT_EQ(orig_rvh, rvh());
1188  EXPECT_EQ(RenderViewHostImpl::STATE_DEFAULT, orig_rvh->rvh_state());
1189  EXPECT_EQ(instance1, instance2);
1190  EXPECT_TRUE(contents()->GetPendingRenderViewHost() == NULL);
1191}
1192
1193// Test that NavigationEntries have the correct page state after going
1194// forward and back.  Prevents regression for bug 1116137.
1195TEST_F(WebContentsImplTest, NavigationEntryContentState) {
1196  TestRenderViewHost* orig_rvh = test_rvh();
1197
1198  // Navigate to URL.  There should be no committed entry yet.
1199  const GURL url("http://www.google.com");
1200  controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1201  NavigationEntry* entry = controller().GetLastCommittedEntry();
1202  EXPECT_TRUE(entry == NULL);
1203
1204  // Committed entry should have page state after DidNavigate.
1205  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1206  entry = controller().GetLastCommittedEntry();
1207  EXPECT_TRUE(entry->GetPageState().IsValid());
1208
1209  // Navigate to same site.
1210  const GURL url2("http://images.google.com");
1211  controller().LoadURL(url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1212  entry = controller().GetLastCommittedEntry();
1213  EXPECT_TRUE(entry->GetPageState().IsValid());
1214
1215  // Committed entry should have page state after DidNavigate.
1216  contents()->TestDidNavigate(orig_rvh, 2, url2, PAGE_TRANSITION_TYPED);
1217  entry = controller().GetLastCommittedEntry();
1218  EXPECT_TRUE(entry->GetPageState().IsValid());
1219
1220  // Now go back.  Committed entry should still have page state.
1221  controller().GoBack();
1222  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1223  entry = controller().GetLastCommittedEntry();
1224  EXPECT_TRUE(entry->GetPageState().IsValid());
1225}
1226
1227// Test that NavigationEntries have the correct page state and SiteInstance
1228// state after opening a new window to about:blank.  Prevents regression for
1229// bugs b/1116137 and http://crbug.com/111975.
1230TEST_F(WebContentsImplTest, NavigationEntryContentStateNewWindow) {
1231  TestRenderViewHost* orig_rvh = test_rvh();
1232
1233  // When opening a new window, it is navigated to about:blank internally.
1234  // Currently, this results in two DidNavigate events.
1235  const GURL url(kAboutBlankURL);
1236  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1237  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1238
1239  // Should have a page state here.
1240  NavigationEntry* entry = controller().GetLastCommittedEntry();
1241  EXPECT_TRUE(entry->GetPageState().IsValid());
1242
1243  // The SiteInstance should be available for other navigations to use.
1244  NavigationEntryImpl* entry_impl =
1245      NavigationEntryImpl::FromNavigationEntry(entry);
1246  EXPECT_FALSE(entry_impl->site_instance()->HasSite());
1247  int32 site_instance_id = entry_impl->site_instance()->GetId();
1248
1249  // Navigating to a normal page should not cause a process swap.
1250  const GURL new_url("http://www.google.com");
1251  controller().LoadURL(new_url, Referrer(),
1252                       PAGE_TRANSITION_TYPED, std::string());
1253  EXPECT_FALSE(contents()->cross_navigation_pending());
1254  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1255  contents()->TestDidNavigate(orig_rvh, 1, new_url, PAGE_TRANSITION_TYPED);
1256  NavigationEntryImpl* entry_impl2 = NavigationEntryImpl::FromNavigationEntry(
1257      controller().GetLastCommittedEntry());
1258  EXPECT_EQ(site_instance_id, entry_impl2->site_instance()->GetId());
1259  EXPECT_TRUE(entry_impl2->site_instance()->HasSite());
1260}
1261
1262// Tests that fullscreen is exited throughout the object hierarchy when
1263// navigating to a new page.
1264TEST_F(WebContentsImplTest, NavigationExitsFullscreen) {
1265  FakeFullscreenDelegate fake_delegate;
1266  contents()->SetDelegate(&fake_delegate);
1267  TestRenderViewHost* orig_rvh = test_rvh();
1268
1269  // Navigate to a site.
1270  const GURL url("http://www.google.com");
1271  controller().LoadURL(
1272      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1273  contents()->TestDidNavigate(orig_rvh, 1, url, PAGE_TRANSITION_TYPED);
1274  EXPECT_EQ(orig_rvh, contents()->GetRenderViewHost());
1275
1276  // Toggle fullscreen mode on (as if initiated via IPC from renderer).
1277  EXPECT_FALSE(orig_rvh->IsFullscreen());
1278  EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
1279  EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1280  orig_rvh->OnMessageReceived(
1281      ViewHostMsg_ToggleFullscreen(orig_rvh->GetRoutingID(), true));
1282  EXPECT_TRUE(orig_rvh->IsFullscreen());
1283  EXPECT_TRUE(contents()->IsFullscreenForCurrentTab());
1284  EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1285
1286  // Navigate to a new site.
1287  const GURL url2("http://www.yahoo.com");
1288  controller().LoadURL(
1289      url2, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1290  RenderViewHost* const pending_rvh = contents()->GetPendingRenderViewHost();
1291  contents()->TestDidNavigate(
1292      pending_rvh, 1, url2, PAGE_TRANSITION_TYPED);
1293
1294  // Confirm fullscreen has exited.
1295  EXPECT_FALSE(orig_rvh->IsFullscreen());
1296  EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
1297  EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1298
1299  contents()->SetDelegate(NULL);
1300}
1301
1302// Tests that fullscreen is exited throughout the object hierarchy on a renderer
1303// crash.
1304TEST_F(WebContentsImplTest, CrashExitsFullscreen) {
1305  FakeFullscreenDelegate fake_delegate;
1306  contents()->SetDelegate(&fake_delegate);
1307
1308  // Navigate to a site.
1309  const GURL url("http://www.google.com");
1310  controller().LoadURL(
1311      url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1312  contents()->TestDidNavigate(test_rvh(), 1, url, PAGE_TRANSITION_TYPED);
1313  EXPECT_EQ(test_rvh(), contents()->GetRenderViewHost());
1314
1315  // Toggle fullscreen mode on (as if initiated via IPC from renderer).
1316  EXPECT_FALSE(test_rvh()->IsFullscreen());
1317  EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
1318  EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1319  test_rvh()->OnMessageReceived(
1320      ViewHostMsg_ToggleFullscreen(test_rvh()->GetRoutingID(), true));
1321  EXPECT_TRUE(test_rvh()->IsFullscreen());
1322  EXPECT_TRUE(contents()->IsFullscreenForCurrentTab());
1323  EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1324
1325  // Crash the renderer.
1326  test_rvh()->OnMessageReceived(
1327      ViewHostMsg_RenderProcessGone(
1328          0, base::TERMINATION_STATUS_PROCESS_CRASHED, -1));
1329
1330  // Confirm fullscreen has exited.
1331  EXPECT_FALSE(test_rvh()->IsFullscreen());
1332  EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
1333  EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
1334
1335  contents()->SetDelegate(NULL);
1336}
1337
1338////////////////////////////////////////////////////////////////////////////////
1339// Interstitial Tests
1340////////////////////////////////////////////////////////////////////////////////
1341
1342// Test navigating to a page (with the navigation initiated from the browser,
1343// as when a URL is typed in the location bar) that shows an interstitial and
1344// creates a new navigation entry, then hiding it without proceeding.
1345TEST_F(WebContentsImplTest,
1346       ShowInterstitialFromBrowserWithNewNavigationDontProceed) {
1347  // Navigate to a page.
1348  GURL url1("http://www.google.com");
1349  test_rvh()->SendNavigate(1, url1);
1350  EXPECT_EQ(1, controller().GetEntryCount());
1351
1352  // Initiate a browser navigation that will trigger the interstitial
1353  controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
1354                        PAGE_TRANSITION_TYPED, std::string());
1355
1356  // Show an interstitial.
1357  TestInterstitialPage::InterstitialState state =
1358      TestInterstitialPage::INVALID;
1359  bool deleted = false;
1360  GURL url2("http://interstitial");
1361  TestInterstitialPage* interstitial =
1362      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
1363  TestInterstitialPageStateGuard state_guard(interstitial);
1364  interstitial->Show();
1365  // The interstitial should not show until its navigation has committed.
1366  EXPECT_FALSE(interstitial->is_showing());
1367  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1368  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1369  // Let's commit the interstitial navigation.
1370  interstitial->TestDidNavigate(1, url2);
1371  EXPECT_TRUE(interstitial->is_showing());
1372  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1373  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1374  NavigationEntry* entry = controller().GetVisibleEntry();
1375  ASSERT_TRUE(entry != NULL);
1376  EXPECT_TRUE(entry->GetURL() == url2);
1377
1378  // Now don't proceed.
1379  interstitial->DontProceed();
1380  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1381  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1382  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1383  entry = controller().GetVisibleEntry();
1384  ASSERT_TRUE(entry != NULL);
1385  EXPECT_TRUE(entry->GetURL() == url1);
1386  EXPECT_EQ(1, controller().GetEntryCount());
1387
1388  RunAllPendingInMessageLoop();
1389  EXPECT_TRUE(deleted);
1390}
1391
1392// Test navigating to a page (with the navigation initiated from the renderer,
1393// as when clicking on a link in the page) that shows an interstitial and
1394// creates a new navigation entry, then hiding it without proceeding.
1395TEST_F(WebContentsImplTest,
1396       ShowInterstitiaFromRendererlWithNewNavigationDontProceed) {
1397  // Navigate to a page.
1398  GURL url1("http://www.google.com");
1399  test_rvh()->SendNavigate(1, url1);
1400  EXPECT_EQ(1, controller().GetEntryCount());
1401
1402  // Show an interstitial (no pending entry, the interstitial would have been
1403  // triggered by clicking on a link).
1404  TestInterstitialPage::InterstitialState state =
1405      TestInterstitialPage::INVALID;
1406  bool deleted = false;
1407  GURL url2("http://interstitial");
1408  TestInterstitialPage* interstitial =
1409      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
1410  TestInterstitialPageStateGuard state_guard(interstitial);
1411  interstitial->Show();
1412  // The interstitial should not show until its navigation has committed.
1413  EXPECT_FALSE(interstitial->is_showing());
1414  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1415  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1416  // Let's commit the interstitial navigation.
1417  interstitial->TestDidNavigate(1, url2);
1418  EXPECT_TRUE(interstitial->is_showing());
1419  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1420  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1421  NavigationEntry* entry = controller().GetVisibleEntry();
1422  ASSERT_TRUE(entry != NULL);
1423  EXPECT_TRUE(entry->GetURL() == url2);
1424
1425  // Now don't proceed.
1426  interstitial->DontProceed();
1427  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1428  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1429  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1430  entry = controller().GetVisibleEntry();
1431  ASSERT_TRUE(entry != NULL);
1432  EXPECT_TRUE(entry->GetURL() == url1);
1433  EXPECT_EQ(1, controller().GetEntryCount());
1434
1435  RunAllPendingInMessageLoop();
1436  EXPECT_TRUE(deleted);
1437}
1438
1439// Test navigating to a page that shows an interstitial without creating a new
1440// navigation entry (this happens when the interstitial is triggered by a
1441// sub-resource in the page), then hiding it without proceeding.
1442TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationDontProceed) {
1443  // Navigate to a page.
1444  GURL url1("http://www.google.com");
1445  test_rvh()->SendNavigate(1, url1);
1446  EXPECT_EQ(1, controller().GetEntryCount());
1447
1448  // Show an interstitial.
1449  TestInterstitialPage::InterstitialState state =
1450      TestInterstitialPage::INVALID;
1451  bool deleted = false;
1452  GURL url2("http://interstitial");
1453  TestInterstitialPage* interstitial =
1454      new TestInterstitialPage(contents(), false, url2, &state, &deleted);
1455  TestInterstitialPageStateGuard state_guard(interstitial);
1456  interstitial->Show();
1457  // The interstitial should not show until its navigation has committed.
1458  EXPECT_FALSE(interstitial->is_showing());
1459  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1460  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1461  // Let's commit the interstitial navigation.
1462  interstitial->TestDidNavigate(1, url2);
1463  EXPECT_TRUE(interstitial->is_showing());
1464  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1465  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1466  NavigationEntry* entry = controller().GetVisibleEntry();
1467  ASSERT_TRUE(entry != NULL);
1468  // The URL specified to the interstitial should have been ignored.
1469  EXPECT_TRUE(entry->GetURL() == url1);
1470
1471  // Now don't proceed.
1472  interstitial->DontProceed();
1473  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1474  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1475  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1476  entry = controller().GetVisibleEntry();
1477  ASSERT_TRUE(entry != NULL);
1478  EXPECT_TRUE(entry->GetURL() == url1);
1479  EXPECT_EQ(1, controller().GetEntryCount());
1480
1481  RunAllPendingInMessageLoop();
1482  EXPECT_TRUE(deleted);
1483}
1484
1485// Test navigating to a page (with the navigation initiated from the browser,
1486// as when a URL is typed in the location bar) that shows an interstitial and
1487// creates a new navigation entry, then proceeding.
1488TEST_F(WebContentsImplTest,
1489       ShowInterstitialFromBrowserNewNavigationProceed) {
1490  // Navigate to a page.
1491  GURL url1("http://www.google.com");
1492  test_rvh()->SendNavigate(1, url1);
1493  EXPECT_EQ(1, controller().GetEntryCount());
1494
1495  // Initiate a browser navigation that will trigger the interstitial
1496  controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
1497                        PAGE_TRANSITION_TYPED, std::string());
1498
1499  // Show an interstitial.
1500  TestInterstitialPage::InterstitialState state =
1501      TestInterstitialPage::INVALID;
1502  bool deleted = false;
1503  GURL url2("http://interstitial");
1504  TestInterstitialPage* interstitial =
1505      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
1506  TestInterstitialPageStateGuard state_guard(interstitial);
1507  interstitial->Show();
1508  // The interstitial should not show until its navigation has committed.
1509  EXPECT_FALSE(interstitial->is_showing());
1510  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1511  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1512  // Let's commit the interstitial navigation.
1513  interstitial->TestDidNavigate(1, url2);
1514  EXPECT_TRUE(interstitial->is_showing());
1515  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1516  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1517  NavigationEntry* entry = controller().GetVisibleEntry();
1518  ASSERT_TRUE(entry != NULL);
1519  EXPECT_TRUE(entry->GetURL() == url2);
1520
1521  // Then proceed.
1522  interstitial->Proceed();
1523  // The interstitial should show until the new navigation commits.
1524  RunAllPendingInMessageLoop();
1525  ASSERT_FALSE(deleted);
1526  EXPECT_EQ(TestInterstitialPage::OKED, state);
1527  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1528  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1529
1530  // Simulate the navigation to the page, that's when the interstitial gets
1531  // hidden.
1532  GURL url3("http://www.thepage.com");
1533  test_rvh()->SendNavigate(2, url3);
1534
1535  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1536  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1537  entry = controller().GetVisibleEntry();
1538  ASSERT_TRUE(entry != NULL);
1539  EXPECT_TRUE(entry->GetURL() == url3);
1540
1541  EXPECT_EQ(2, controller().GetEntryCount());
1542
1543  RunAllPendingInMessageLoop();
1544  EXPECT_TRUE(deleted);
1545}
1546
1547// Test navigating to a page (with the navigation initiated from the renderer,
1548// as when clicking on a link in the page) that shows an interstitial and
1549// creates a new navigation entry, then proceeding.
1550TEST_F(WebContentsImplTest,
1551       ShowInterstitialFromRendererNewNavigationProceed) {
1552  // Navigate to a page.
1553  GURL url1("http://www.google.com");
1554  test_rvh()->SendNavigate(1, url1);
1555  EXPECT_EQ(1, controller().GetEntryCount());
1556
1557  // Show an interstitial.
1558  TestInterstitialPage::InterstitialState state =
1559      TestInterstitialPage::INVALID;
1560  bool deleted = false;
1561  GURL url2("http://interstitial");
1562  TestInterstitialPage* interstitial =
1563      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
1564  TestInterstitialPageStateGuard state_guard(interstitial);
1565  interstitial->Show();
1566  // The interstitial should not show until its navigation has committed.
1567  EXPECT_FALSE(interstitial->is_showing());
1568  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1569  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1570  // Let's commit the interstitial navigation.
1571  interstitial->TestDidNavigate(1, url2);
1572  EXPECT_TRUE(interstitial->is_showing());
1573  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1574  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1575  NavigationEntry* entry = controller().GetVisibleEntry();
1576  ASSERT_TRUE(entry != NULL);
1577  EXPECT_TRUE(entry->GetURL() == url2);
1578
1579  // Then proceed.
1580  interstitial->Proceed();
1581  // The interstitial should show until the new navigation commits.
1582  RunAllPendingInMessageLoop();
1583  ASSERT_FALSE(deleted);
1584  EXPECT_EQ(TestInterstitialPage::OKED, state);
1585  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1586  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1587
1588  // Simulate the navigation to the page, that's when the interstitial gets
1589  // hidden.
1590  GURL url3("http://www.thepage.com");
1591  test_rvh()->SendNavigate(2, url3);
1592
1593  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1594  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1595  entry = controller().GetVisibleEntry();
1596  ASSERT_TRUE(entry != NULL);
1597  EXPECT_TRUE(entry->GetURL() == url3);
1598
1599  EXPECT_EQ(2, controller().GetEntryCount());
1600
1601  RunAllPendingInMessageLoop();
1602  EXPECT_TRUE(deleted);
1603}
1604
1605// Test navigating to a page that shows an interstitial without creating a new
1606// navigation entry (this happens when the interstitial is triggered by a
1607// sub-resource in the page), then proceeding.
1608TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationProceed) {
1609  // Navigate to a page so we have a navigation entry in the controller.
1610  GURL url1("http://www.google.com");
1611  test_rvh()->SendNavigate(1, url1);
1612  EXPECT_EQ(1, controller().GetEntryCount());
1613
1614  // Show an interstitial.
1615  TestInterstitialPage::InterstitialState state =
1616      TestInterstitialPage::INVALID;
1617  bool deleted = false;
1618  GURL url2("http://interstitial");
1619  TestInterstitialPage* interstitial =
1620      new TestInterstitialPage(contents(), false, url2, &state, &deleted);
1621  TestInterstitialPageStateGuard state_guard(interstitial);
1622  interstitial->Show();
1623  // The interstitial should not show until its navigation has committed.
1624  EXPECT_FALSE(interstitial->is_showing());
1625  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1626  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1627  // Let's commit the interstitial navigation.
1628  interstitial->TestDidNavigate(1, url2);
1629  EXPECT_TRUE(interstitial->is_showing());
1630  EXPECT_TRUE(contents()->ShowingInterstitialPage());
1631  EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial);
1632  NavigationEntry* entry = controller().GetVisibleEntry();
1633  ASSERT_TRUE(entry != NULL);
1634  // The URL specified to the interstitial should have been ignored.
1635  EXPECT_TRUE(entry->GetURL() == url1);
1636
1637  // Then proceed.
1638  interstitial->Proceed();
1639  // Since this is not a new navigation, the previous page is dismissed right
1640  // away and shows the original page.
1641  EXPECT_EQ(TestInterstitialPage::OKED, state);
1642  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1643  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1644  entry = controller().GetVisibleEntry();
1645  ASSERT_TRUE(entry != NULL);
1646  EXPECT_TRUE(entry->GetURL() == url1);
1647
1648  EXPECT_EQ(1, controller().GetEntryCount());
1649
1650  RunAllPendingInMessageLoop();
1651  EXPECT_TRUE(deleted);
1652}
1653
1654// Test navigating to a page that shows an interstitial, then navigating away.
1655TEST_F(WebContentsImplTest, ShowInterstitialThenNavigate) {
1656  // Show interstitial.
1657  TestInterstitialPage::InterstitialState state =
1658      TestInterstitialPage::INVALID;
1659  bool deleted = false;
1660  GURL url("http://interstitial");
1661  TestInterstitialPage* interstitial =
1662      new TestInterstitialPage(contents(), true, url, &state, &deleted);
1663  TestInterstitialPageStateGuard state_guard(interstitial);
1664  interstitial->Show();
1665  interstitial->TestDidNavigate(1, url);
1666
1667  // While interstitial showing, navigate to a new URL.
1668  const GURL url2("http://www.yahoo.com");
1669  test_rvh()->SendNavigate(1, url2);
1670
1671  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1672
1673  RunAllPendingInMessageLoop();
1674  EXPECT_TRUE(deleted);
1675}
1676
1677// Test navigating to a page that shows an interstitial, then going back.
1678TEST_F(WebContentsImplTest, ShowInterstitialThenGoBack) {
1679  // Navigate to a page so we have a navigation entry in the controller.
1680  GURL url1("http://www.google.com");
1681  test_rvh()->SendNavigate(1, url1);
1682  EXPECT_EQ(1, controller().GetEntryCount());
1683
1684  // Show interstitial.
1685  TestInterstitialPage::InterstitialState state =
1686      TestInterstitialPage::INVALID;
1687  bool deleted = false;
1688  GURL interstitial_url("http://interstitial");
1689  TestInterstitialPage* interstitial =
1690      new TestInterstitialPage(contents(), true, interstitial_url,
1691                               &state, &deleted);
1692  TestInterstitialPageStateGuard state_guard(interstitial);
1693  interstitial->Show();
1694  interstitial->TestDidNavigate(2, interstitial_url);
1695
1696  // While the interstitial is showing, go back.
1697  controller().GoBack();
1698  test_rvh()->SendNavigate(1, url1);
1699
1700  // Make sure we are back to the original page and that the interstitial is
1701  // gone.
1702  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1703  NavigationEntry* entry = controller().GetVisibleEntry();
1704  ASSERT_TRUE(entry);
1705  EXPECT_EQ(url1.spec(), entry->GetURL().spec());
1706
1707  RunAllPendingInMessageLoop();
1708  EXPECT_TRUE(deleted);
1709}
1710
1711// Test navigating to a page that shows an interstitial, has a renderer crash,
1712// and then goes back.
1713TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenGoBack) {
1714  // Navigate to a page so we have a navigation entry in the controller.
1715  GURL url1("http://www.google.com");
1716  test_rvh()->SendNavigate(1, url1);
1717  EXPECT_EQ(1, controller().GetEntryCount());
1718
1719  // Show interstitial.
1720  TestInterstitialPage::InterstitialState state =
1721      TestInterstitialPage::INVALID;
1722  bool deleted = false;
1723  GURL interstitial_url("http://interstitial");
1724  TestInterstitialPage* interstitial =
1725      new TestInterstitialPage(contents(), true, interstitial_url,
1726                               &state, &deleted);
1727  TestInterstitialPageStateGuard state_guard(interstitial);
1728  interstitial->Show();
1729  interstitial->TestDidNavigate(2, interstitial_url);
1730
1731  // Crash the renderer
1732  test_rvh()->OnMessageReceived(
1733      ViewHostMsg_RenderProcessGone(
1734          0, base::TERMINATION_STATUS_PROCESS_CRASHED, -1));
1735
1736  // While the interstitial is showing, go back.
1737  controller().GoBack();
1738  test_rvh()->SendNavigate(1, url1);
1739
1740  // Make sure we are back to the original page and that the interstitial is
1741  // gone.
1742  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1743  NavigationEntry* entry = controller().GetVisibleEntry();
1744  ASSERT_TRUE(entry);
1745  EXPECT_EQ(url1.spec(), entry->GetURL().spec());
1746
1747  RunAllPendingInMessageLoop();
1748  EXPECT_TRUE(deleted);
1749}
1750
1751// Test navigating to a page that shows an interstitial, has the renderer crash,
1752// and then navigates to the interstitial.
1753TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenNavigate) {
1754  // Navigate to a page so we have a navigation entry in the controller.
1755  GURL url1("http://www.google.com");
1756  test_rvh()->SendNavigate(1, url1);
1757  EXPECT_EQ(1, controller().GetEntryCount());
1758
1759  // Show interstitial.
1760  TestInterstitialPage::InterstitialState state =
1761      TestInterstitialPage::INVALID;
1762  bool deleted = false;
1763  GURL interstitial_url("http://interstitial");
1764  TestInterstitialPage* interstitial =
1765      new TestInterstitialPage(contents(), true, interstitial_url,
1766                               &state, &deleted);
1767  TestInterstitialPageStateGuard state_guard(interstitial);
1768  interstitial->Show();
1769
1770  // Crash the renderer
1771  test_rvh()->OnMessageReceived(
1772      ViewHostMsg_RenderProcessGone(
1773          0, base::TERMINATION_STATUS_PROCESS_CRASHED, -1));
1774
1775  interstitial->TestDidNavigate(2, interstitial_url);
1776}
1777
1778// Test navigating to a page that shows an interstitial, then close the
1779// contents.
1780TEST_F(WebContentsImplTest, ShowInterstitialThenCloseTab) {
1781  // Show interstitial.
1782  TestInterstitialPage::InterstitialState state =
1783      TestInterstitialPage::INVALID;
1784  bool deleted = false;
1785  GURL url("http://interstitial");
1786  TestInterstitialPage* interstitial =
1787      new TestInterstitialPage(contents(), true, url, &state, &deleted);
1788  TestInterstitialPageStateGuard state_guard(interstitial);
1789  interstitial->Show();
1790  interstitial->TestDidNavigate(1, url);
1791
1792  // Now close the contents.
1793  DeleteContents();
1794  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1795
1796  RunAllPendingInMessageLoop();
1797  EXPECT_TRUE(deleted);
1798}
1799
1800// Test navigating to a page that shows an interstitial, then close the
1801// contents.
1802TEST_F(WebContentsImplTest, ShowInterstitialThenCloseAndShutdown) {
1803  // Show interstitial.
1804  TestInterstitialPage::InterstitialState state =
1805      TestInterstitialPage::INVALID;
1806  bool deleted = false;
1807  GURL url("http://interstitial");
1808  TestInterstitialPage* interstitial =
1809      new TestInterstitialPage(contents(), true, url, &state, &deleted);
1810  TestInterstitialPageStateGuard state_guard(interstitial);
1811  interstitial->Show();
1812  interstitial->TestDidNavigate(1, url);
1813  RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>(
1814      interstitial->GetRenderViewHostForTesting());
1815
1816  // Now close the contents.
1817  DeleteContents();
1818  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
1819
1820  // Before the interstitial has a chance to process its shutdown task,
1821  // simulate quitting the browser.  This goes through all processes and
1822  // tells them to destruct.
1823  rvh->OnMessageReceived(
1824        ViewHostMsg_RenderProcessGone(0, 0, 0));
1825
1826  RunAllPendingInMessageLoop();
1827  EXPECT_TRUE(deleted);
1828}
1829
1830// Test that after Proceed is called and an interstitial is still shown, no more
1831// commands get executed.
1832TEST_F(WebContentsImplTest, ShowInterstitialProceedMultipleCommands) {
1833  // Navigate to a page so we have a navigation entry in the controller.
1834  GURL url1("http://www.google.com");
1835  test_rvh()->SendNavigate(1, url1);
1836  EXPECT_EQ(1, controller().GetEntryCount());
1837
1838  // Show an interstitial.
1839  TestInterstitialPage::InterstitialState state =
1840      TestInterstitialPage::INVALID;
1841  bool deleted = false;
1842  GURL url2("http://interstitial");
1843  TestInterstitialPage* interstitial =
1844      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
1845  TestInterstitialPageStateGuard state_guard(interstitial);
1846  interstitial->Show();
1847  interstitial->TestDidNavigate(1, url2);
1848
1849  // Run a command.
1850  EXPECT_EQ(0, interstitial->command_received_count());
1851  interstitial->TestDomOperationResponse("toto");
1852  EXPECT_EQ(1, interstitial->command_received_count());
1853
1854  // Then proceed.
1855  interstitial->Proceed();
1856  RunAllPendingInMessageLoop();
1857  ASSERT_FALSE(deleted);
1858
1859  // While the navigation to the new page is pending, send other commands, they
1860  // should be ignored.
1861  interstitial->TestDomOperationResponse("hello");
1862  interstitial->TestDomOperationResponse("hi");
1863  EXPECT_EQ(1, interstitial->command_received_count());
1864}
1865
1866// Test showing an interstitial while another interstitial is already showing.
1867TEST_F(WebContentsImplTest, ShowInterstitialOnInterstitial) {
1868  // Navigate to a page so we have a navigation entry in the controller.
1869  GURL start_url("http://www.google.com");
1870  test_rvh()->SendNavigate(1, start_url);
1871  EXPECT_EQ(1, controller().GetEntryCount());
1872
1873  // Show an interstitial.
1874  TestInterstitialPage::InterstitialState state1 =
1875      TestInterstitialPage::INVALID;
1876  bool deleted1 = false;
1877  GURL url1("http://interstitial1");
1878  TestInterstitialPage* interstitial1 =
1879      new TestInterstitialPage(contents(), true, url1, &state1, &deleted1);
1880  TestInterstitialPageStateGuard state_guard1(interstitial1);
1881  interstitial1->Show();
1882  interstitial1->TestDidNavigate(1, url1);
1883
1884  // Now show another interstitial.
1885  TestInterstitialPage::InterstitialState state2 =
1886      TestInterstitialPage::INVALID;
1887  bool deleted2 = false;
1888  GURL url2("http://interstitial2");
1889  TestInterstitialPage* interstitial2 =
1890      new TestInterstitialPage(contents(), true, url2, &state2, &deleted2);
1891  TestInterstitialPageStateGuard state_guard2(interstitial2);
1892  interstitial2->Show();
1893  interstitial2->TestDidNavigate(1, url2);
1894
1895  // Showing interstitial2 should have caused interstitial1 to go away.
1896  EXPECT_EQ(TestInterstitialPage::CANCELED, state1);
1897  EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
1898
1899  RunAllPendingInMessageLoop();
1900  EXPECT_TRUE(deleted1);
1901  ASSERT_FALSE(deleted2);
1902
1903  // Let's make sure interstitial2 is working as intended.
1904  interstitial2->Proceed();
1905  GURL landing_url("http://www.thepage.com");
1906  test_rvh()->SendNavigate(2, landing_url);
1907
1908  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1909  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1910  NavigationEntry* entry = controller().GetVisibleEntry();
1911  ASSERT_TRUE(entry != NULL);
1912  EXPECT_TRUE(entry->GetURL() == landing_url);
1913  EXPECT_EQ(2, controller().GetEntryCount());
1914  RunAllPendingInMessageLoop();
1915  EXPECT_TRUE(deleted2);
1916}
1917
1918// Test showing an interstitial, proceeding and then navigating to another
1919// interstitial.
1920TEST_F(WebContentsImplTest, ShowInterstitialProceedShowInterstitial) {
1921  // Navigate to a page so we have a navigation entry in the controller.
1922  GURL start_url("http://www.google.com");
1923  test_rvh()->SendNavigate(1, start_url);
1924  EXPECT_EQ(1, controller().GetEntryCount());
1925
1926  // Show an interstitial.
1927  TestInterstitialPage::InterstitialState state1 =
1928      TestInterstitialPage::INVALID;
1929  bool deleted1 = false;
1930  GURL url1("http://interstitial1");
1931  TestInterstitialPage* interstitial1 =
1932      new TestInterstitialPage(contents(), true, url1, &state1, &deleted1);
1933  TestInterstitialPageStateGuard state_guard1(interstitial1);
1934  interstitial1->Show();
1935  interstitial1->TestDidNavigate(1, url1);
1936
1937  // Take action.  The interstitial won't be hidden until the navigation is
1938  // committed.
1939  interstitial1->Proceed();
1940  EXPECT_EQ(TestInterstitialPage::OKED, state1);
1941
1942  // Now show another interstitial (simulating the navigation causing another
1943  // interstitial).
1944  TestInterstitialPage::InterstitialState state2 =
1945      TestInterstitialPage::INVALID;
1946  bool deleted2 = false;
1947  GURL url2("http://interstitial2");
1948  TestInterstitialPage* interstitial2 =
1949      new TestInterstitialPage(contents(), true, url2, &state2, &deleted2);
1950  TestInterstitialPageStateGuard state_guard2(interstitial2);
1951  interstitial2->Show();
1952  interstitial2->TestDidNavigate(1, url2);
1953
1954  // Showing interstitial2 should have caused interstitial1 to go away.
1955  EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
1956  RunAllPendingInMessageLoop();
1957  EXPECT_TRUE(deleted1);
1958  ASSERT_FALSE(deleted2);
1959
1960  // Let's make sure interstitial2 is working as intended.
1961  interstitial2->Proceed();
1962  GURL landing_url("http://www.thepage.com");
1963  test_rvh()->SendNavigate(2, landing_url);
1964
1965  RunAllPendingInMessageLoop();
1966  EXPECT_TRUE(deleted2);
1967  EXPECT_FALSE(contents()->ShowingInterstitialPage());
1968  EXPECT_TRUE(contents()->GetInterstitialPage() == NULL);
1969  NavigationEntry* entry = controller().GetVisibleEntry();
1970  ASSERT_TRUE(entry != NULL);
1971  EXPECT_TRUE(entry->GetURL() == landing_url);
1972  EXPECT_EQ(2, controller().GetEntryCount());
1973}
1974
1975// Test that navigating away from an interstitial while it's loading cause it
1976// not to show.
1977TEST_F(WebContentsImplTest, NavigateBeforeInterstitialShows) {
1978  // Show an interstitial.
1979  TestInterstitialPage::InterstitialState state =
1980      TestInterstitialPage::INVALID;
1981  bool deleted = false;
1982  GURL interstitial_url("http://interstitial");
1983  TestInterstitialPage* interstitial =
1984      new TestInterstitialPage(contents(), true, interstitial_url,
1985                               &state, &deleted);
1986  TestInterstitialPageStateGuard state_guard(interstitial);
1987  interstitial->Show();
1988
1989  // Let's simulate a navigation initiated from the browser before the
1990  // interstitial finishes loading.
1991  const GURL url("http://www.google.com");
1992  controller().LoadURL(url, Referrer(), PAGE_TRANSITION_TYPED, std::string());
1993  EXPECT_FALSE(interstitial->is_showing());
1994  RunAllPendingInMessageLoop();
1995  ASSERT_FALSE(deleted);
1996
1997  // Now let's make the interstitial navigation commit.
1998  interstitial->TestDidNavigate(1, interstitial_url);
1999
2000  // After it loaded the interstitial should be gone.
2001  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
2002
2003  RunAllPendingInMessageLoop();
2004  EXPECT_TRUE(deleted);
2005}
2006
2007// Test that a new request to show an interstitial while an interstitial is
2008// pending does not cause problems. htp://crbug/29655 and htp://crbug/9442.
2009TEST_F(WebContentsImplTest, TwoQuickInterstitials) {
2010  GURL interstitial_url("http://interstitial");
2011
2012  // Show a first interstitial.
2013  TestInterstitialPage::InterstitialState state1 =
2014      TestInterstitialPage::INVALID;
2015  bool deleted1 = false;
2016  TestInterstitialPage* interstitial1 =
2017      new TestInterstitialPage(contents(), true, interstitial_url,
2018                               &state1, &deleted1);
2019  TestInterstitialPageStateGuard state_guard1(interstitial1);
2020  interstitial1->Show();
2021
2022  // Show another interstitial on that same contents before the first one had
2023  // time to load.
2024  TestInterstitialPage::InterstitialState state2 =
2025      TestInterstitialPage::INVALID;
2026  bool deleted2 = false;
2027  TestInterstitialPage* interstitial2 =
2028      new TestInterstitialPage(contents(), true, interstitial_url,
2029                               &state2, &deleted2);
2030  TestInterstitialPageStateGuard state_guard2(interstitial2);
2031  interstitial2->Show();
2032
2033  // The first interstitial should have been closed and deleted.
2034  EXPECT_EQ(TestInterstitialPage::CANCELED, state1);
2035  // The 2nd one should still be OK.
2036  EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
2037
2038  RunAllPendingInMessageLoop();
2039  EXPECT_TRUE(deleted1);
2040  ASSERT_FALSE(deleted2);
2041
2042  // Make the interstitial navigation commit it should be showing.
2043  interstitial2->TestDidNavigate(1, interstitial_url);
2044  EXPECT_EQ(interstitial2, contents()->GetInterstitialPage());
2045}
2046
2047// Test showing an interstitial and have its renderer crash.
2048TEST_F(WebContentsImplTest, InterstitialCrasher) {
2049  // Show an interstitial.
2050  TestInterstitialPage::InterstitialState state =
2051      TestInterstitialPage::INVALID;
2052  bool deleted = false;
2053  GURL url("http://interstitial");
2054  TestInterstitialPage* interstitial =
2055      new TestInterstitialPage(contents(), true, url, &state, &deleted);
2056  TestInterstitialPageStateGuard state_guard(interstitial);
2057  interstitial->Show();
2058  // Simulate a renderer crash before the interstitial is shown.
2059  interstitial->TestRenderViewTerminated(
2060      base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
2061  // The interstitial should have been dismissed.
2062  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
2063  RunAllPendingInMessageLoop();
2064  EXPECT_TRUE(deleted);
2065
2066  // Now try again but this time crash the intersitial after it was shown.
2067  interstitial =
2068      new TestInterstitialPage(contents(), true, url, &state, &deleted);
2069  interstitial->Show();
2070  interstitial->TestDidNavigate(1, url);
2071  // Simulate a renderer crash.
2072  interstitial->TestRenderViewTerminated(
2073      base::TERMINATION_STATUS_PROCESS_CRASHED, -1);
2074  // The interstitial should have been dismissed.
2075  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
2076  RunAllPendingInMessageLoop();
2077  EXPECT_TRUE(deleted);
2078}
2079
2080// Tests that showing an interstitial as a result of a browser initiated
2081// navigation while an interstitial is showing does not remove the pending
2082// entry (see http://crbug.com/9791).
2083TEST_F(WebContentsImplTest, NewInterstitialDoesNotCancelPendingEntry) {
2084  const char kUrl[] = "http://www.badguys.com/";
2085  const GURL kGURL(kUrl);
2086
2087  // Start a navigation to a page
2088  contents()->GetController().LoadURL(
2089      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
2090
2091  // Simulate that navigation triggering an interstitial.
2092  TestInterstitialPage::InterstitialState state =
2093      TestInterstitialPage::INVALID;
2094  bool deleted = false;
2095  TestInterstitialPage* interstitial =
2096      new TestInterstitialPage(contents(), true, kGURL, &state, &deleted);
2097  TestInterstitialPageStateGuard state_guard(interstitial);
2098  interstitial->Show();
2099  interstitial->TestDidNavigate(1, kGURL);
2100
2101  // Initiate a new navigation from the browser that also triggers an
2102  // interstitial.
2103  contents()->GetController().LoadURL(
2104      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
2105  TestInterstitialPage::InterstitialState state2 =
2106      TestInterstitialPage::INVALID;
2107  bool deleted2 = false;
2108  TestInterstitialPage* interstitial2 =
2109      new TestInterstitialPage(contents(), true, kGURL, &state2, &deleted2);
2110  TestInterstitialPageStateGuard state_guard2(interstitial2);
2111  interstitial2->Show();
2112  interstitial2->TestDidNavigate(1, kGURL);
2113
2114  // Make sure we still have an entry.
2115  NavigationEntry* entry = contents()->GetController().GetPendingEntry();
2116  ASSERT_TRUE(entry);
2117  EXPECT_EQ(kUrl, entry->GetURL().spec());
2118
2119  // And that the first interstitial is gone, but not the second.
2120  EXPECT_EQ(TestInterstitialPage::CANCELED, state);
2121  EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2);
2122  RunAllPendingInMessageLoop();
2123  EXPECT_TRUE(deleted);
2124  EXPECT_FALSE(deleted2);
2125}
2126
2127// Tests that Javascript messages are not shown while an interstitial is
2128// showing.
2129TEST_F(WebContentsImplTest, NoJSMessageOnInterstitials) {
2130  const char kUrl[] = "http://www.badguys.com/";
2131  const GURL kGURL(kUrl);
2132
2133  // Start a navigation to a page
2134  contents()->GetController().LoadURL(
2135      kGURL, Referrer(), PAGE_TRANSITION_TYPED, std::string());
2136  // DidNavigate from the page
2137  contents()->TestDidNavigate(rvh(), 1, kGURL, PAGE_TRANSITION_TYPED);
2138
2139  // Simulate showing an interstitial while the page is showing.
2140  TestInterstitialPage::InterstitialState state =
2141      TestInterstitialPage::INVALID;
2142  bool deleted = false;
2143  TestInterstitialPage* interstitial =
2144      new TestInterstitialPage(contents(), true, kGURL, &state, &deleted);
2145  TestInterstitialPageStateGuard state_guard(interstitial);
2146  interstitial->Show();
2147  interstitial->TestDidNavigate(1, kGURL);
2148
2149  // While the interstitial is showing, let's simulate the hidden page
2150  // attempting to show a JS message.
2151  IPC::Message* dummy_message = new IPC::Message;
2152  bool did_suppress_message = false;
2153  contents()->RunJavaScriptMessage(contents()->GetRenderViewHost(),
2154      base::ASCIIToUTF16("This is an informative message"),
2155      base::ASCIIToUTF16("OK"),
2156      kGURL, JAVASCRIPT_MESSAGE_TYPE_ALERT, dummy_message,
2157      &did_suppress_message);
2158  EXPECT_TRUE(did_suppress_message);
2159}
2160
2161// Makes sure that if the source passed to CopyStateFromAndPrune has an
2162// interstitial it isn't copied over to the destination.
2163TEST_F(WebContentsImplTest, CopyStateFromAndPruneSourceInterstitial) {
2164  // Navigate to a page.
2165  GURL url1("http://www.google.com");
2166  test_rvh()->SendNavigate(1, url1);
2167  EXPECT_EQ(1, controller().GetEntryCount());
2168
2169  // Initiate a browser navigation that will trigger the interstitial
2170  controller().LoadURL(GURL("http://www.evil.com"), Referrer(),
2171                        PAGE_TRANSITION_TYPED, std::string());
2172
2173  // Show an interstitial.
2174  TestInterstitialPage::InterstitialState state =
2175      TestInterstitialPage::INVALID;
2176  bool deleted = false;
2177  GURL url2("http://interstitial");
2178  TestInterstitialPage* interstitial =
2179      new TestInterstitialPage(contents(), true, url2, &state, &deleted);
2180  TestInterstitialPageStateGuard state_guard(interstitial);
2181  interstitial->Show();
2182  interstitial->TestDidNavigate(1, url2);
2183  EXPECT_TRUE(interstitial->is_showing());
2184  EXPECT_EQ(2, controller().GetEntryCount());
2185
2186  // Create another NavigationController.
2187  GURL url3("http://foo2");
2188  scoped_ptr<TestWebContents> other_contents(
2189      static_cast<TestWebContents*>(CreateTestWebContents()));
2190  NavigationControllerImpl& other_controller = other_contents->GetController();
2191  other_contents->NavigateAndCommit(url3);
2192  other_contents->ExpectSetHistoryLengthAndPrune(
2193      NavigationEntryImpl::FromNavigationEntry(
2194          other_controller.GetEntryAtIndex(0))->site_instance(), 1,
2195      other_controller.GetEntryAtIndex(0)->GetPageID());
2196  other_controller.CopyStateFromAndPrune(&controller(), false);
2197
2198  // The merged controller should only have two entries: url1 and url2.
2199  ASSERT_EQ(2, other_controller.GetEntryCount());
2200  EXPECT_EQ(1, other_controller.GetCurrentEntryIndex());
2201  EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL());
2202  EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL());
2203
2204  // And the merged controller shouldn't be showing an interstitial.
2205  EXPECT_FALSE(other_contents->ShowingInterstitialPage());
2206}
2207
2208// Makes sure that CopyStateFromAndPrune cannot be called if the target is
2209// showing an interstitial.
2210TEST_F(WebContentsImplTest, CopyStateFromAndPruneTargetInterstitial) {
2211  // Navigate to a page.
2212  GURL url1("http://www.google.com");
2213  contents()->NavigateAndCommit(url1);
2214
2215  // Create another NavigationController.
2216  scoped_ptr<TestWebContents> other_contents(
2217      static_cast<TestWebContents*>(CreateTestWebContents()));
2218  NavigationControllerImpl& other_controller = other_contents->GetController();
2219
2220  // Navigate it to url2.
2221  GURL url2("http://foo2");
2222  other_contents->NavigateAndCommit(url2);
2223
2224  // Show an interstitial.
2225  TestInterstitialPage::InterstitialState state =
2226      TestInterstitialPage::INVALID;
2227  bool deleted = false;
2228  GURL url3("http://interstitial");
2229  TestInterstitialPage* interstitial =
2230      new TestInterstitialPage(other_contents.get(), true, url3, &state,
2231                               &deleted);
2232  TestInterstitialPageStateGuard state_guard(interstitial);
2233  interstitial->Show();
2234  interstitial->TestDidNavigate(1, url3);
2235  EXPECT_TRUE(interstitial->is_showing());
2236  EXPECT_EQ(2, other_controller.GetEntryCount());
2237
2238  // Ensure that we do not allow calling CopyStateFromAndPrune when an
2239  // interstitial is showing in the target.
2240  EXPECT_FALSE(other_controller.CanPruneAllButLastCommitted());
2241}
2242
2243// Regression test for http://crbug.com/168611 - the URLs passed by the
2244// DidFinishLoad and DidFailLoadWithError IPCs should get filtered.
2245TEST_F(WebContentsImplTest, FilterURLs) {
2246  TestWebContentsObserver observer(contents());
2247
2248  // A navigation to about:whatever should always look like a navigation to
2249  // about:blank
2250  GURL url_normalized(kAboutBlankURL);
2251  GURL url_from_ipc("about:whatever");
2252
2253  // We navigate the test WebContents to about:blank, since NavigateAndCommit
2254  // will use the given URL to create the NavigationEntry as well, and that
2255  // entry should contain the filtered URL.
2256  contents()->NavigateAndCommit(url_normalized);
2257
2258  // Check that an IPC with about:whatever is correctly normalized.
2259  contents()->TestDidFinishLoad(url_from_ipc, true);
2260
2261  EXPECT_EQ(url_normalized, observer.last_url());
2262
2263  // Create and navigate another WebContents.
2264  scoped_ptr<TestWebContents> other_contents(
2265      static_cast<TestWebContents*>(CreateTestWebContents()));
2266  TestWebContentsObserver other_observer(other_contents.get());
2267  other_contents->NavigateAndCommit(url_normalized);
2268
2269  // Check that an IPC with about:whatever is correctly normalized.
2270  other_contents->TestDidFailLoadWithError(
2271      url_from_ipc, true, 1, base::string16());
2272  EXPECT_EQ(url_normalized, other_observer.last_url());
2273}
2274
2275// Test that if a pending contents is deleted before it is shown, we don't
2276// crash.
2277TEST_F(WebContentsImplTest, PendingContents) {
2278  scoped_ptr<TestWebContents> other_contents(
2279      static_cast<TestWebContents*>(CreateTestWebContents()));
2280  contents()->AddPendingContents(other_contents.get());
2281  int route_id = other_contents->GetRenderViewHost()->GetRoutingID();
2282  other_contents.reset();
2283  EXPECT_EQ(NULL, contents()->GetCreatedWindow(route_id));
2284}
2285
2286TEST_F(WebContentsImplTest, CapturerOverridesPreferredSize) {
2287  const gfx::Size original_preferred_size(1024, 768);
2288  contents()->UpdatePreferredSize(original_preferred_size);
2289
2290  // With no capturers, expect the preferred size to be the one propagated into
2291  // WebContentsImpl via the RenderViewHostDelegate interface.
2292  EXPECT_EQ(contents()->GetCapturerCount(), 0);
2293  EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize());
2294
2295  // Increment capturer count, but without specifying a capture size.  Expect
2296  // a "not set" preferred size.
2297  contents()->IncrementCapturerCount(gfx::Size());
2298  EXPECT_EQ(1, contents()->GetCapturerCount());
2299  EXPECT_EQ(gfx::Size(), contents()->GetPreferredSize());
2300
2301  // Increment capturer count again, but with an overriding capture size.
2302  // Expect preferred size to now be overridden to the capture size.
2303  const gfx::Size capture_size(1280, 720);
2304  contents()->IncrementCapturerCount(capture_size);
2305  EXPECT_EQ(2, contents()->GetCapturerCount());
2306  EXPECT_EQ(capture_size, contents()->GetPreferredSize());
2307
2308  // Increment capturer count a third time, but the expect that the preferred
2309  // size is still the first capture size.
2310  const gfx::Size another_capture_size(720, 480);
2311  contents()->IncrementCapturerCount(another_capture_size);
2312  EXPECT_EQ(3, contents()->GetCapturerCount());
2313  EXPECT_EQ(capture_size, contents()->GetPreferredSize());
2314
2315  // Decrement capturer count twice, but expect the preferred size to still be
2316  // overridden.
2317  contents()->DecrementCapturerCount();
2318  contents()->DecrementCapturerCount();
2319  EXPECT_EQ(1, contents()->GetCapturerCount());
2320  EXPECT_EQ(capture_size, contents()->GetPreferredSize());
2321
2322  // Decrement capturer count, and since the count has dropped to zero, the
2323  // original preferred size should be restored.
2324  contents()->DecrementCapturerCount();
2325  EXPECT_EQ(0, contents()->GetCapturerCount());
2326  EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize());
2327}
2328
2329// Tests that GetLastActiveTime starts with a real, non-zero time and updates
2330// on activity.
2331TEST_F(WebContentsImplTest, GetLastActiveTime) {
2332  // The WebContents starts with a valid creation time.
2333  EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
2334
2335  // Reset the last active time to a known-bad value.
2336  contents()->last_active_time_ = base::TimeTicks();
2337  ASSERT_TRUE(contents()->GetLastActiveTime().is_null());
2338
2339  // Simulate activating the WebContents. The active time should update.
2340  contents()->WasShown();
2341  EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
2342}
2343
2344class ContentsZoomChangedDelegate : public WebContentsDelegate {
2345 public:
2346  ContentsZoomChangedDelegate() :
2347    contents_zoom_changed_call_count_(0),
2348    last_zoom_in_(false) {
2349  }
2350
2351  int GetAndResetContentsZoomChangedCallCount() {
2352    int count = contents_zoom_changed_call_count_;
2353    contents_zoom_changed_call_count_ = 0;
2354    return count;
2355  }
2356
2357  bool last_zoom_in() const {
2358    return last_zoom_in_;
2359  }
2360
2361  // WebContentsDelegate:
2362  virtual void ContentsZoomChange(bool zoom_in) OVERRIDE {
2363    contents_zoom_changed_call_count_++;
2364    last_zoom_in_ = zoom_in;
2365  }
2366
2367 private:
2368  int contents_zoom_changed_call_count_;
2369  bool last_zoom_in_;
2370
2371  DISALLOW_COPY_AND_ASSIGN(ContentsZoomChangedDelegate);
2372};
2373
2374// Tests that some mouseehweel events get turned into browser zoom requests.
2375TEST_F(WebContentsImplTest, HandleWheelEvent) {
2376  using blink::WebInputEvent;
2377
2378  scoped_ptr<ContentsZoomChangedDelegate> delegate(
2379      new ContentsZoomChangedDelegate());
2380  contents()->SetDelegate(delegate.get());
2381
2382  int modifiers = 0;
2383  float dy = 1;
2384  // Verify that normal mouse wheel events do nothing to change the zoom level.
2385  blink::WebMouseWheelEvent event =
2386      SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
2387  EXPECT_FALSE(contents()->HandleWheelEvent(event));
2388  EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
2389
2390  modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey;
2391  event = SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
2392  EXPECT_FALSE(contents()->HandleWheelEvent(event));
2393  EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
2394
2395  // But whenever the ctrl modifier is applied, they can increase/decrease zoom.
2396  // Except on MacOS where we never want to adjust zoom with mousewheel.
2397  modifiers = WebInputEvent::ControlKey;
2398  event = SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
2399  bool handled = contents()->HandleWheelEvent(event);
2400#if defined(OS_MACOSX)
2401  EXPECT_FALSE(handled);
2402  EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
2403#else
2404  EXPECT_TRUE(handled);
2405  EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
2406  EXPECT_TRUE(delegate->last_zoom_in());
2407#endif
2408
2409  modifiers = WebInputEvent::ControlKey | WebInputEvent::ShiftKey |
2410      WebInputEvent::AltKey;
2411  dy = -5;
2412  event = SyntheticWebMouseWheelEventBuilder::Build(2, dy, modifiers, false);
2413  handled = contents()->HandleWheelEvent(event);
2414#if defined(OS_MACOSX)
2415  EXPECT_FALSE(handled);
2416  EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
2417#else
2418  EXPECT_TRUE(handled);
2419  EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
2420  EXPECT_FALSE(delegate->last_zoom_in());
2421#endif
2422
2423  // Unless there is no vertical movement.
2424  dy = 0;
2425  event = SyntheticWebMouseWheelEventBuilder::Build(2, dy, modifiers, false);
2426  EXPECT_FALSE(contents()->HandleWheelEvent(event));
2427  EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
2428
2429  // Ensure pointers to the delegate aren't kept beyond it's lifetime.
2430  contents()->SetDelegate(NULL);
2431}
2432
2433}  // namespace content
2434