// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web/navigation/navigation_manager_impl.h"

#import <array>
#import <string>
#import <string_view>

#import "base/apple/foundation_util.h"
#import "base/functional/bind.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/escape.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "ios/web/common/features.h"
#import "ios/web/navigation/back_forward_navigation_type.h"
#import "ios/web/navigation/navigation_manager_delegate.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#import "ios/web/navigation/wk_navigation_util.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/reload_type.h"
#import "ios/web/public/session/proto/navigation.pb.h"
#import "ios/web/public/session/proto/storage.pb.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_client.h"
#import "ios/web/test/fakes/crw_fake_back_forward_list.h"
#import "ios/web/test/fakes/crw_fake_web_view_navigation_proxy.h"
#import "ios/web/test/test_url_constants.h"
#import "ios/web/web_state/ui/crw_web_view_navigation_proxy.h"
#import "ios/web/web_state/web_state_impl.h"
#import "net/base/apple/url_conversions.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "url/scheme_host_port.h"
#import "url/url_util.h"

using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace web {
namespace {

// URL scheme that will be rewritten by UrlRewriter installed in
// NavigationManagerTest fixture. Scheme will be changed to kTestWebUIScheme.
constexpr std::string_view kSchemeToRewrite =
    "navigationmanagerschemetorewrite";

// URLs used for session restoration tests.
constexpr auto kTestURLs =
    std::to_array<std::string_view>({"about://new-tab", "about://version"});

// Replaces `kSchemeToRewrite` scheme with `kTestWebUIScheme`.
bool UrlRewriter(GURL* url, BrowserState* browser_state) {
  if (url->scheme() == kSchemeToRewrite) {
    GURL::Replacements scheme_replacements;
    scheme_replacements.SetSchemeStr(kTestWebUIScheme);
    *url = url->ReplaceComponents(scheme_replacements);
    return true;
  }
  return false;
}

// Query parameter that will be appended by AppendingUrlRewriter if it is
// installed into NavigationManager by a test case.
const char kRewrittenQueryParam[] = "navigationmanagerrewrittenquery";

// Appends `kRewrittenQueryParam` to `url`.
bool AppendingUrlRewriter(GURL* url, BrowserState* browser_state) {
  GURL::Replacements query_replacements;
  query_replacements.SetQueryStr(kRewrittenQueryParam);
  *url = url->ReplaceComponents(query_replacements);
  return false;
}

// Mock class for NavigationManagerDelegate.
class MockNavigationManagerDelegate : public NavigationManagerDelegate {
 public:
  void SetWKWebView(id web_view) {
    EXPECT_OCMOCK_VERIFY(mock_web_view_);
    mock_web_view_ = web_view;
  }
  void SetWebState(WebState* web_state) { web_state_ = web_state; }
  void RemoveWebView() override {
    EXPECT_OCMOCK_VERIFY(mock_web_view_);
    // Simulate removing the web view.
    mock_web_view_ = nil;
  }

  MOCK_METHOD(void, ClearDialogs, ());
  MOCK_METHOD(void, RecordPageStateInNavigationItem, ());
  MOCK_METHOD(void, LoadCurrentItem, (NavigationInitiationType type));
  MOCK_METHOD(void, LoadIfNecessary, ());
  MOCK_METHOD(void, Reload, ());
  MOCK_METHOD(void, OnNavigationItemsPruned, (size_t));
  MOCK_METHOD(void, OnNavigationItemCommitted, (NavigationItem * item));
  MOCK_METHOD(void, SetWebStateUserAgent, (UserAgentType user_agent_type));
  MOCK_METHOD(void,
              GoToBackForwardListItem,
              (WKBackForwardListItem*,
               NavigationItem*,
               BackForwardNavigationType,
               NavigationInitiationType,
               bool));
  MOCK_METHOD(NavigationItemImpl*, GetPendingItem, ());
  MOCK_METHOD(GURL, GetCurrentURL, (), (const));
  MOCK_METHOD(void, UpdateSSLStatusForCurrentNavigationItem, ());

 private:
  WebState* GetWebState() override { return web_state_; }

  id<CRWWebViewNavigationProxy> GetWebViewNavigationProxy() const override {
    return mock_web_view_;
  }

  id mock_web_view_;
  raw_ptr<WebState> web_state_ = nullptr;
};

// Data holder for the informations to be restored in the items.
struct ItemInfoToBeRestored {
  GURL url;
  GURL virtual_url;
  UserAgentType user_agent;
};

}  // namespace

// Test fixture for NavigationManagerImpl testing.
class NavigationManagerTest : public PlatformTest,
                              public testing::WithParamInterface<bool> {
 protected:
  NavigationManagerTest() {
    scoped_feature_list_.InitWithFeatureStates(
        {{kSkipAutomaticNavigationInBackForwardListKillSwitch, GetParam()}});

    mock_web_view_ = OCMClassMock([WKWebView class]);
    mock_wk_list_ = [[CRWFakeBackForwardList alloc] init];
    OCMStub([mock_web_view_ backForwardList]).andReturn(mock_wk_list_);
    delegate_.SetWKWebView(mock_web_view_);

    // Setup rewriter.
    BrowserURLRewriter::GetInstance()->AddURLRewriter(UrlRewriter);
    url::AddStandardScheme(kSchemeToRewrite, url::SCHEME_WITH_HOST);

    manager_ =
        std::make_unique<NavigationManagerImpl>(&browser_state_, &delegate_);
  }

  ~NavigationManagerTest() override { EXPECT_OCMOCK_VERIFY(mock_web_view_); }

  NavigationManagerImpl* navigation_manager() { return manager_.get(); }

  MockNavigationManagerDelegate& navigation_manager_delegate() {
    return delegate_;
  }

  // Manipulates the underlying session state to simulate the effect of
  // GoToIndex() on the navigation manager to facilitate testing of other
  // NavigationManager APIs. NavigationManager::GoToIndex() itself is tested
  // separately by verifying expectations on the delegate method calls.
  void SimulateGoToIndex(int index) {
    [mock_wk_list_ moveCurrentToIndex:index];
  }

  // Makes delegate to return navigation item, which is stored in navigation
  // context in the real app.
  void SimulateReturningPendingItemFromDelegate(web::NavigationItemImpl* item) {
    ON_CALL(navigation_manager_delegate(), GetPendingItem())
        .WillByDefault(testing::Return(item));
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  CRWFakeBackForwardList* mock_wk_list_;
  id mock_web_view_;
  base::HistogramTester histogram_tester_;

 protected:
  FakeBrowserState browser_state_;
  FakeWebState web_state_;
  MockNavigationManagerDelegate delegate_;
  base::test::ScopedFeatureList feature_;
  std::unique_ptr<NavigationManagerImpl> manager_;

 private:
  url::ScopedSchemeRegistryForTests scoped_registry_;
};

INSTANTIATE_TEST_SUITE_P(, NavigationManagerTest, testing::Bool());

// Tests state of an empty navigation manager.
TEST_P(NavigationManagerTest, EmptyManager) {
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
  EXPECT_EQ(-1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
  EXPECT_EQ(-1, navigation_manager()->GetIndexForOffset(0));
}

// Tests that GetPendingItemIndex() returns -1 if there is no pending entry.
TEST_P(NavigationManagerTest, GetPendingItemIndexWithoutPendingEntry) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
}

// Tests that GetPendingItemIndex() returns -1 if there is a pending item.
TEST_P(NavigationManagerTest, GetPendingItemIndexWithPendingEntry) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
}

// Tests that setting and getting PendingItemIndex.
TEST_P(NavigationManagerTest, SetAndGetPendingItemIndex) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.test"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.test"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->SetPendingItemIndex(0);
  EXPECT_EQ(0, navigation_manager()->GetPendingItemIndex());
}

// Tests that GetPendingItemIndex() returns correct index.
TEST_P(NavigationManagerTest, GetPendingItemIndexWithIndexedPendingEntry) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                  backListURLs:@[ @"http://www.url.com" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
}

// Tests that NavigationManagerImpl::GetPendingItem() returns item provided by
// the delegate.
TEST_P(NavigationManagerTest, GetPendingItemFromDelegate) {
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  auto item = std::make_unique<web::NavigationItemImpl>();
  SimulateReturningPendingItemFromDelegate(item.get());
  EXPECT_EQ(item.get(), navigation_manager()->GetPendingItem());
}

// Tests that NavigationManagerImpl::GetPendingItem() ignores item provided by
// the delegate if navigation manager has own pending item.
TEST_P(NavigationManagerTest, GetPendingItemIgnoringDelegate) {
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  auto item = std::make_unique<web::NavigationItemImpl>();
  SimulateReturningPendingItemFromDelegate(item.get());

  GURL url("http://www.url.test");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_NE(item.get(), navigation_manager()->GetPendingItem());
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(url, navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that GetPendingItem() returns indexed pending item.
TEST_P(NavigationManagerTest, GetPendingItemWithIndexedPendingEntry) {
  GURL url("http://www.url.test");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.test"];
  navigation_manager()->CommitPendingItem();
  navigation_manager()->SetPendingItemIndex(0);

  auto item = std::make_unique<web::NavigationItemImpl>();
  SimulateReturningPendingItemFromDelegate(item.get());

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_NE(item.get(), navigation_manager()->GetPendingItem());
  EXPECT_EQ(url, navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that going back or negative offset is not possible without a committed
// item.
TEST_P(NavigationManagerTest, CanGoBackWithoutCommitedItem) {
  EXPECT_FALSE(navigation_manager()->CanGoBack());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(-1));
}

// Tests that going back or negative offset is not possible if there is ony one
// committed item.
TEST_P(NavigationManagerTest, CanGoBackWithSingleCommitedItem) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  EXPECT_FALSE(navigation_manager()->CanGoBack());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(-1));
}

// Tests going back possibility with multiple committed items.
TEST_P(NavigationManagerTest, CanGoBackWithMultipleCommitedItems) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                  backListURLs:@[ @"http://www.url.com" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_
        setCurrentURL:@"http://www.url.com/1"
         backListURLs:@[ @"http://www.url.com", @"http://www.url.com/0" ]
      forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_TRUE(navigation_manager()->CanGoBack());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(-1));

  SimulateGoToIndex(1);
  EXPECT_TRUE(navigation_manager()->CanGoBack());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(-1));

  SimulateGoToIndex(0);
  EXPECT_FALSE(navigation_manager()->CanGoBack());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(-1));

  SimulateGoToIndex(1);
  EXPECT_TRUE(navigation_manager()->CanGoBack());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(-1));
}

// Tests that going forward or positive offset is not possible without a
// committed item.
TEST_P(NavigationManagerTest, CanGoForwardWithoutCommitedItem) {
  EXPECT_FALSE(navigation_manager()->CanGoForward());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(1));
}

// Tests that going forward or positive offset is not possible if there is ony
// one committed item.
TEST_P(NavigationManagerTest, CanGoForwardWithSingleCommitedItem) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/"];
  navigation_manager()->CommitPendingItem();

  EXPECT_FALSE(navigation_manager()->CanGoForward());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(1));
}

// Tests going forward possibility with multiple committed items.
TEST_P(NavigationManagerTest, CanGoForwardWithMultipleCommitedEntries) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                  backListURLs:@[ @"http://www.url.com" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_
        setCurrentURL:@"http://www.url.com/1"
         backListURLs:@[ @"http://www.url.com", @"http://www.url.com/0" ]
      forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_FALSE(navigation_manager()->CanGoForward());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(1));

  SimulateGoToIndex(1);
  EXPECT_TRUE(navigation_manager()->CanGoForward());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(1));

  SimulateGoToIndex(0);
  EXPECT_TRUE(navigation_manager()->CanGoForward());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(1));

  SimulateGoToIndex(1);
  EXPECT_TRUE(navigation_manager()->CanGoForward());
  EXPECT_TRUE(navigation_manager()->CanGoToOffset(1));

  SimulateGoToIndex(2);
  EXPECT_FALSE(navigation_manager()->CanGoForward());
  EXPECT_FALSE(navigation_manager()->CanGoToOffset(1));
}

// Tests CanGoToOffset API for positive, negative and zero delta. Tested
// navigation manager will have redirect entries to make sure they are
// appropriately skipped.
TEST_P(NavigationManagerTest, OffsetsWithoutPendingIndex) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/redirect"), Referrer(),
      ui::PAGE_TRANSITION_CLIENT_REDIRECT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[
                    @"http://www.url.com/0", @"http://www.url.com/redirect"
                  ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/2"
                  backListURLs:@[
                    @"http://www.url.com/0", @"http://www.url.com/redirect",
                    @"http://www.url.com/1"
                  ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/redirect"), Referrer(),
      ui::PAGE_TRANSITION_CLIENT_REDIRECT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                  backListURLs:@[
                    @"http://www.url.com/0", @"http://www.url.com/redirect",
                    @"http://www.url.com/1", @"http://www.url.com/2"
                  ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  ASSERT_EQ(5, navigation_manager()->GetItemCount());
  ASSERT_EQ(4, navigation_manager()->GetLastCommittedItemIndex());
}

// Tests that when given a pending item, adding a new pending item replaces the
// existing pending item if their URLs are different.
TEST_P(NavigationManagerTest, ReplacePendingItemIfDifferentURL) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(existing_url, navigation_manager()->GetPendingItem()->GetURL());
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  GURL new_url1 = GURL("http://www.new1.com");
  navigation_manager()->AddPendingItem(
      new_url1, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(new_url1, navigation_manager()->GetPendingItem()->GetURL());
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  GURL new_url2 = GURL("http://www.new2.com");
  navigation_manager()->AddPendingItem(
      new_url2, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kHttpsOnlyMode);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(new_url2, navigation_manager()->GetPendingItem()->GetURL());
  EXPECT_EQ(web::HttpsUpgradeType::kHttpsOnlyMode,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests that when given a pending item, adding a new pending item with the same
// URL doesn't replace the existing pending item if new pending item is not a
// form submission.
TEST_P(NavigationManagerTest, NotReplaceSameUrlPendingItemIfNotFormSubmission) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());

  // NavigationManagerImpl assumes that AddPendingItem() is only called for
  // new navigation, so it always creates a new pending item.
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_LINK));
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  // Try again with a pending item that uses https as the default scheme.
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kHttpsOnlyMode);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(web::HttpsUpgradeType::kHttpsOnlyMode,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
}

// Tests that when given a pending item, adding a new pending item with the same
// URL replaces the existing pending item if new pending item is a form
// submission while existing pending item is not.
TEST_P(NavigationManagerTest, ReplaceSameUrlPendingItemIfFormSubmission) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_FORM_SUBMIT));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  // Try again with a pending item that uses https as the default scheme.
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kHttpsOnlyMode);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_FORM_SUBMIT));
  EXPECT_EQ(web::HttpsUpgradeType::kHttpsOnlyMode,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests that when given a pending item, adding a new pending item with the same
// URL doesn't replace the existing pending item if the user agent override
// option is INHERIT.
TEST_P(NavigationManagerTest, NotReplaceSameUrlPendingItemIfOverrideInherit) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());

  // NavigationManagerImpl assumes that AddPendingItem() is only called for
  // new navigation, so it always creates a new pending item.
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_LINK));
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests that when given a pending item, adding a new pending item with the same
// URL replaces the existing pending item.
TEST_P(NavigationManagerTest, ReplaceSameUrlPendingItem) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_RELOAD));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kHttpsOnlyMode);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_RELOAD));
  EXPECT_EQ(web::HttpsUpgradeType::kHttpsOnlyMode,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests that when given a pending item, adding a new pending item with the same
// URL replaces the existing pending item.
TEST_P(NavigationManagerTest, ReplaceSameUrlPendingItemFromDesktop) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  navigation_manager()->GetPendingItem()->SetUserAgentType(
      UserAgentType::DESKTOP);
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(web::UserAgentType::DESKTOP,
            navigation_manager()->GetPendingItem()->GetUserAgentType());
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_RELOAD));
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kHttpsOnlyMode);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_RELOAD));
  EXPECT_EQ(web::HttpsUpgradeType::kHttpsOnlyMode,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(0, navigation_manager()->GetItemCount());
}

// Tests that when the last committed item exists, adding a pending item
// succeeds if the new item's URL is different from the last committed item.
TEST_P(NavigationManagerTest, AddPendingItemIfDiffernetURL) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(existing_url,
            navigation_manager()->GetLastCommittedItem()->GetURL());
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  GURL new_url = GURL("http://www.new.com");
  navigation_manager()->AddPendingItem(
      new_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(new_url, navigation_manager()->GetPendingItem()->GetURL());
  EXPECT_EQ(web::HttpsUpgradeType::kNone,
            navigation_manager()->GetPendingItem()->GetHttpsUpgradeType());
  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests that when the last committed item exists, adding a pending item with
// the same URL fails if the new item is not form submission.
TEST_P(NavigationManagerTest, NotAddSameUrlPendingItemIfNotFormSubmission) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  // NavigationManagerImpl assumes that AddPendingItem() is only called for
  // new navigation, so it always creates a new pending item.
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(navigation_manager()->GetPendingItem(),
            navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_LINK));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests that when the last committed item exists, adding a pending item with
// the same URL updates the existing committed item if the form submission isn't
// using POST.
TEST_P(NavigationManagerTest, NotAddSameUrlPendingItemIfGETFormSubmission) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  // Add if new transition is a form submission.
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(navigation_manager()->GetPendingItem(),
            navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_FORM_SUBMIT));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests that when the last committed item exists, adding a pending item with
// the same URL creates a new pending item if the form submission is using POST.
TEST_P(NavigationManagerTest, AddSameUrlPendingItemIfPOSTFormSubmission) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  // Add if new transition is a form submission.
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_FORM_SUBMIT,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/true, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_NE(navigation_manager()->GetPendingItem(),
            navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_FORM_SUBMIT));
}

// Tests that when the last committed item exists, adding a pending item with
// the same URL fails if the user agent override option is INHERIT.
TEST_P(NavigationManagerTest, NotAddSameUrlPendingItemIfOverrideInherit) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  // NavigationManagerImpl assumes that AddPendingItem() is only called for
  // new navigation, so it always creates a new pending item.
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(navigation_manager()->GetPendingItem(),
            navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_LINK));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests that when the last committed item exists, adding a pending item with
// the same URL succeeds.
TEST_P(NavigationManagerTest, AddSameUrlPendingItem) {
  GURL existing_url = GURL("http://www.existing.com");
  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.existing.com"]);
  navigation_manager()->CommitPendingItem();

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetLastCommittedItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());

  navigation_manager()->AddPendingItem(
      existing_url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(navigation_manager()->GetPendingItem(),
            navigation_manager()->GetLastCommittedItem());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(
      navigation_manager()->GetPendingItem()->GetTransitionType(),
      ui::PAGE_TRANSITION_RELOAD));
  EXPECT_EQ(1, navigation_manager()->GetItemCount());
}

// Tests that calling `Reload` with web::ReloadType::NORMAL is no-op when there
// are no pending or committed items.
TEST_P(NavigationManagerTest, ReloadEmptyWithNormalType) {
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  ASSERT_FALSE(navigation_manager()->GetLastCommittedItem());

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(0);
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  ASSERT_FALSE(navigation_manager()->GetLastCommittedItem());
}

// Tests that calling `Reload` with web::ReloadType::NORMAL leaves the url of
// the renderer initiated pending item unchanged when there is one.
TEST_P(NavigationManagerTest, ReloadRendererPendingItemWithNormalType) {
  GURL url_before_reload = GURL("http://www.url.com");
  navigation_manager()->AddPendingItem(
      url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(url_before_reload,
            navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::NORMAL leaves the url of
// the user initiated pending item unchanged when there is one.
TEST_P(NavigationManagerTest, ReloadUserPendingItemWithNormalType) {
  GURL url_before_reload = GURL("http://www.url.com");
  navigation_manager()->AddPendingItem(
      url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(url_before_reload,
            navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::NORMAL leaves the url of
// the last committed item unchanged when there is no pending item.
TEST_P(NavigationManagerTest, ReloadLastCommittedItemWithNormalType) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  GURL url_before_reload = GURL("http://www.url.com/1");
  navigation_manager()->AddPendingItem(
      url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(url_before_reload,
            navigation_manager()->GetLastCommittedItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::NORMAL leaves the url of
// the last committed item unchanged when there is no pending item, but there
// forward items after last committed item.
TEST_P(NavigationManagerTest,
       ReloadLastCommittedItemWithNormalTypeWithForwardItems) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  GURL url_before_reload = GURL("http://www.url.com/1");
  navigation_manager()->AddPendingItem(
      url_before_reload, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_
        setCurrentURL:@"http://www.url.com/2"
         backListURLs:@[ @"http://www.url.com/0", @"http://www.url.com/1" ]
      forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  SimulateGoToIndex(1);
  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(url_before_reload,
            navigation_manager()->GetLastCommittedItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::ORIGINAL_REQUEST_URL is
// no-op when there are no pending or committed items.
TEST_P(NavigationManagerTest, ReloadEmptyWithOriginalType) {
  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  ASSERT_FALSE(navigation_manager()->GetLastCommittedItem());

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(0);
  navigation_manager()->Reload(web::ReloadType::ORIGINAL_REQUEST_URL,
                               false /* check_for_repost */);

  ASSERT_FALSE(navigation_manager()->GetPendingItem());
  ASSERT_FALSE(navigation_manager()->GetLastCommittedItem());
}

// Tests that calling `Reload` with web::ReloadType::ORIGINAL_REQUEST_URL
// changes the renderer initiated pending item's url to its original request url
// when there is one.
TEST_P(NavigationManagerTest, ReloadRendererPendingItemWithOriginalType) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  GURL expected_original_url = GURL("http://www.url.com/original");
  navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
      expected_original_url);

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::ORIGINAL_REQUEST_URL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(expected_original_url,
            navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::ORIGINAL_REQUEST_URL
// changes the user initiated pending item's url to its original request url
// when there is one.
TEST_P(NavigationManagerTest, ReloadUserPendingItemWithOriginalType) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  GURL expected_original_url = GURL("http://www.url.com/original");
  navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
      expected_original_url);

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::ORIGINAL_REQUEST_URL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(expected_original_url,
            navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::ORIGINAL_REQUEST_URL
// changes the last committed item's url to its original request url when there
// is no pending item.
TEST_P(NavigationManagerTest, ReloadLastCommittedItemWithOriginalType) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  GURL expected_original_url = GURL("http://www.url.com/1/original");
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
      expected_original_url);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1/original"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::ORIGINAL_REQUEST_URL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(expected_original_url,
            navigation_manager()->GetLastCommittedItem()->GetURL());
}

// Tests that calling `Reload` with web::ReloadType::ORIGINAL_REQUEST_URL
// changes the last committed item's url to its original request url when there
// is no pending item, but there are forward items after last committed item.
TEST_P(NavigationManagerTest,
       ReloadLastCommittedItemWithOriginalTypeWithForwardItems) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  GURL expected_original_url = GURL("http://www.url.com/1/original");
  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  navigation_manager()->GetPendingItem()->SetOriginalRequestURL(
      expected_original_url);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1/original"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/2"
                  backListURLs:@[
                    @"http://www.url.com/0", @"http://www.url.com/1/original"
                  ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  SimulateGoToIndex(1);
  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());

  EXPECT_CALL(navigation_manager_delegate(), Reload()).Times(1);
  navigation_manager()->Reload(web::ReloadType::ORIGINAL_REQUEST_URL,
                               false /* check_for_repost */);

  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(expected_original_url,
            navigation_manager()->GetLastCommittedItem()->GetURL());
}

// Tests that ReloadWithUserAgentType triggers new navigation with the expected
// user agent override.
TEST_P(NavigationManagerTest, ReloadWithUserAgentType) {
  GURL url("http://www.1.com");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  GURL virtual_url("http://www.1.com/virtual");
  navigation_manager()->GetPendingItem()->SetVirtualURL(virtual_url);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
  navigation_manager()->CommitPendingItem();
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.1.com"]);

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());
  EXPECT_CALL(navigation_manager_delegate(),
              LoadCurrentItem(NavigationInitiationType::BROWSER_INITIATED));

  navigation_manager()->ReloadWithUserAgentType(UserAgentType::DESKTOP);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  EXPECT_EQ(url, pending_item->GetURL());
  EXPECT_EQ(virtual_url, pending_item->GetVirtualURL());
  EXPECT_EQ(UserAgentType::DESKTOP, pending_item->GetUserAgentType());
}

// Tests that ReloadWithUserAgentType reloads on the last committed item before
// the redirect items.
TEST_P(NavigationManagerTest, ReloadWithUserAgentTypeOnRedirect) {
  GURL url("http://www.1.com");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.redirect.com"), Referrer(),
      ui::PAGE_TRANSITION_CLIENT_REDIRECT,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/redirect"
                  backListURLs:@[ @"http://www.1.com" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->ReloadWithUserAgentType(UserAgentType::DESKTOP);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  EXPECT_EQ(url, pending_item->GetURL());
}

// Tests that ReloadWithUserAgentType reloads on the last committed item if
// there are no item before a redirect (which happens when opening a new tab on
// a redirect).
TEST_P(NavigationManagerTest, ReloadWithUserAgentTypeOnNewTabRedirect) {
  GURL url("http://www.1.com");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_CLIENT_REDIRECT,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->ReloadWithUserAgentType(UserAgentType::DESKTOP);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  EXPECT_EQ(url, pending_item->GetURL());
}

// Tests that app-specific URLs are not rewritten for renderer-initiated loads
// or reloads unless requested by a page with app-specific url.
TEST_P(NavigationManagerTest, RewritingAppSpecificUrls) {
  // URL should not be rewritten as there is no committed URL.
  GURL url1(url::SchemeHostPort(kSchemeToRewrite, "test", 0).Serialize());
  navigation_manager()->AddPendingItem(
      url1, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(url1, navigation_manager()->GetPendingItem()->GetURL());

  // URL should not be rewritten because last committed URL is not app-specific.
  [mock_wk_list_ setCurrentURL:base::SysUTF8ToNSString(url1.spec())];
  navigation_manager()->CommitPendingItem();

  GURL url2(url::SchemeHostPort(kSchemeToRewrite, "test2", 0).Serialize());
  navigation_manager()->AddPendingItem(
      url2, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(url2, navigation_manager()->GetPendingItem()->GetURL());

  // URL should not be rewritten for user initiated reload navigations.
  GURL url_reload(
      url::SchemeHostPort(kSchemeToRewrite, "test-reload", 0).Serialize());
  navigation_manager()->AddPendingItem(
      url_reload, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(url_reload, navigation_manager()->GetPendingItem()->GetURL());

  // URL should be rewritten for user initiated navigations.
  GURL url3(url::SchemeHostPort(kSchemeToRewrite, "test3", 0).Serialize());
  navigation_manager()->AddPendingItem(
      url3, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  GURL rewritten_url3(
      url::SchemeHostPort(kTestWebUIScheme, "test3", 0).Serialize());
  EXPECT_EQ(rewritten_url3, navigation_manager()->GetPendingItem()->GetURL());

  // URL should be rewritten because last committed URL is app-specific.
  [mock_wk_list_ setCurrentURL:base::SysUTF8ToNSString(rewritten_url3.spec())
                  backListURLs:@[ base::SysUTF8ToNSString(url1.spec()) ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  GURL url4(url::SchemeHostPort(kSchemeToRewrite, "test4", 0).Serialize());
  navigation_manager()->AddPendingItem(
      url4, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  GURL rewritten_url4(
      url::SchemeHostPort(kTestWebUIScheme, "test4", 0).Serialize());
  EXPECT_EQ(rewritten_url4, navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that transient URLRewriters are applied for pending items.
TEST_P(NavigationManagerTest, ApplyTransientRewriters) {
  navigation_manager()->AddTransientURLRewriter(&AppendingUrlRewriter);
  navigation_manager()->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  EXPECT_EQ(kRewrittenQueryParam, pending_item->GetURL().GetQuery());

  // Now that the transient rewriters are consumed, the next URL should not be
  // changed.
  GURL url("http://www.1.com");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_EQ(url, navigation_manager()->GetPendingItem()->GetURL());
}

// Tests that GetIndexOfItem() returns the correct values.
TEST_P(NavigationManagerTest, GetIndexOfItem) {
  // This test manipuates the WKBackForwardListItems in mock_wk_list_ directly
  // to retain the NavigationItem association.
  WKBackForwardListItem* wk_item0 =
      [CRWFakeBackForwardList itemWithURLString:@"http://www.url.com/0"];
  WKBackForwardListItem* wk_item1 =
      [CRWFakeBackForwardList itemWithURLString:@"http://www.url.com/1"];

  // Create two items and add them to the NavigationManagerImpl.
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  mock_wk_list_.currentItem = wk_item0;
  navigation_manager()->CommitPendingItem();

  NavigationItem* item0 = navigation_manager()->GetLastCommittedItem();
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  mock_wk_list_.currentItem = wk_item1;
  mock_wk_list_.backList = @[ wk_item0 ];
  navigation_manager()->CommitPendingItem();

  NavigationItem* item1 = navigation_manager()->GetLastCommittedItem();
  // Create an item that does not exist in the NavigationManagerImpl.
  std::unique_ptr<NavigationItem> item_not_found = NavigationItem::Create();
  // Verify GetIndexOfItem() results.
  EXPECT_EQ(0, navigation_manager()->GetIndexOfItem(item0));
  EXPECT_EQ(1, navigation_manager()->GetIndexOfItem(item1));
  EXPECT_EQ(-1, navigation_manager()->GetIndexOfItem(item_not_found.get()));
}

// Tests that GetBackwardItems() and GetForwardItems() return expected entries
// when current item is in the middle of the navigation history.
TEST_P(NavigationManagerTest, TestBackwardForwardItems) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_
        setCurrentURL:@"http://www.url.com/2"
         backListURLs:@[ @"http://www.url.com/0", @"http://www.url.com/1" ]
      forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_EQ(2, navigation_manager()->GetLastCommittedItemIndex());
  std::vector<NavigationItem*> back_items =
      navigation_manager()->GetBackwardItems();
  EXPECT_EQ(2U, back_items.size());
  EXPECT_EQ("http://www.url.com/1", back_items[0]->GetURL().spec());
  EXPECT_EQ("http://www.url.com/0", back_items[1]->GetURL().spec());
  EXPECT_TRUE(navigation_manager()->GetForwardItems().empty());

  SimulateGoToIndex(1);
  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  back_items = navigation_manager()->GetBackwardItems();
  EXPECT_EQ(1U, back_items.size());
  EXPECT_EQ("http://www.url.com/0", back_items[0]->GetURL().spec());
  std::vector<NavigationItem*> forward_items =
      navigation_manager()->GetForwardItems();
  EXPECT_EQ(1U, forward_items.size());
  EXPECT_EQ("http://www.url.com/2", forward_items[0]->GetURL().spec());
}

// Tests that pending item is not considered part of session history so that
// GetBackwardItems returns the second last committed item even if there is a
// pendign item.
TEST_P(NavigationManagerTest, NewPendingItemIsHiddenFromHistory) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_TRUE(navigation_manager()->GetPendingItem());

  std::vector<NavigationItem*> back_items =
      navigation_manager()->GetBackwardItems();
  EXPECT_EQ(1U, back_items.size());
  EXPECT_EQ("http://www.url.com/0", back_items[0]->GetURL().spec());
}

TEST_P(NavigationManagerTest, PendingItemIsVisibleIfNewAndUserInitiated) {
  delegate_.SetWebState(&web_state_);
  web_state_.SetLoading(true);

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetVisibleItem());
  EXPECT_EQ("http://www.url.com/0",
            navigation_manager()->GetVisibleItem()->GetURL().spec());

  // Visible item is still the user initiated pending item even if there is a
  // committed item.
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetVisibleItem());
  EXPECT_EQ("http://www.url.com/1",
            navigation_manager()->GetVisibleItem()->GetURL().spec());
}

TEST_P(NavigationManagerTest, PendingItemIsNotVisibleIfNotUserInitiated) {
  delegate_.SetWebState(&web_state_);
  web_state_.SetLoading(true);

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_EQ(nullptr, navigation_manager()->GetVisibleItem());
}

TEST_P(NavigationManagerTest, PendingItemIsNotVisibleIfNotNewNavigation) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  // Move pending item back to index 0.
  OCMStub([mock_web_view_ URL])
      .andReturn([NSURL URLWithString:@"http://www.url.com/0"]);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"
                  backListURLs:nil
               forwardListURLs:@[ @"http://www.url.com/1" ]];
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(),
      ui::PAGE_TRANSITION_FORWARD_BACK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  ASSERT_EQ(0, navigation_manager()->GetPendingItemIndex());

  delegate_.SetWebState(&web_state_);
  web_state_.SetLoading(true);
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/0"]);
  ASSERT_TRUE(navigation_manager()->GetVisibleItem());
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/0"]);
  EXPECT_EQ("http://www.url.com/1",
            navigation_manager()->GetVisibleItem()->GetURL().spec());
}

TEST_P(NavigationManagerTest, VisibleItemDefaultsToLastCommittedItem) {
  delegate_.SetWebState(&web_state_);
  web_state_.SetLoading(true);

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/0"]);
  ASSERT_TRUE(navigation_manager()->GetVisibleItem());
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/0"]);
  EXPECT_EQ("http://www.url.com/0",
            navigation_manager()->GetVisibleItem()->GetURL().spec());
}

// Tests that `extra_headers` and `post_data` from WebLoadParams are added to
// the new navigation item if they are present.
TEST_P(NavigationManagerTest, LoadURLWithParamsWithExtraHeadersAndPostData) {
  NavigationManager::WebLoadParams params(GURL("http://www.url.com/0"));
  params.transition_type = ui::PAGE_TRANSITION_TYPED;
  params.extra_headers = @{@"Content-Type" : @"text/plain"};
  params.post_data = [NSData data];

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());
  EXPECT_CALL(navigation_manager_delegate(),
              LoadCurrentItem(NavigationInitiationType::BROWSER_INITIATED));

  navigation_manager()->LoadURLWithParams(params);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  ASSERT_TRUE(pending_item);
  EXPECT_EQ("http://www.url.com/0", pending_item->GetURL().spec());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(pending_item->GetTransitionType(),
                                           ui::PAGE_TRANSITION_TYPED));
  EXPECT_NSEQ(pending_item->GetHttpRequestHeaders(),
              @{@"Content-Type" : @"text/plain"});
  EXPECT_TRUE(pending_item->HasPostData());
}

// Tests that `internal_scroll_to_text_fragment` from WebLoadParams is added to
// the new navigation item if it is present.
TEST_P(NavigationManagerTest,
       LoadURLWithParamsWithInternalScrollToTextFragment) {
  NavigationManager::WebLoadParams params(GURL("http://www.url.com/0"));
  params.internal_scroll_to_text_fragment = "start,end";

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());
  EXPECT_CALL(navigation_manager_delegate(),
              LoadCurrentItem(NavigationInitiationType::BROWSER_INITIATED));

  navigation_manager()->LoadURLWithParams(params);

  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  ASSERT_TRUE(pending_item);
  EXPECT_EQ("http://www.url.com/0", pending_item->GetURL().spec());
  ASSERT_TRUE(pending_item->GetInternalScrollToTextFragment().has_value());
  EXPECT_EQ("start,end",
            pending_item->GetInternalScrollToTextFragment().value());
}

// Tests that `internal_scroll_to_text_fragment` is preserved during reload.
TEST_P(NavigationManagerTest, ReloadWithInternalScrollToTextFragment) {
  GURL url = GURL("http://www.url.com/0");
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  navigation_manager()->GetPendingItem()->SetInternalScrollToTextFragment(
      "start,end");

  [mock_wk_list_ setCurrentURL:base::SysUTF8ToNSString(url.spec())];
  navigation_manager()->CommitPendingItem();

  EXPECT_CALL(navigation_manager_delegate(), Reload());
  navigation_manager()->Reload(web::ReloadType::NORMAL,
                               false /* check_for_repost */);

  // Reload on the mock delegate doesn't trigger the actual navigation flow,
  // so manually call `AddPendingItem` to test its behavior during a reload.
  navigation_manager()->AddPendingItem(
      url, Referrer(), ui::PAGE_TRANSITION_RELOAD,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  ASSERT_TRUE(navigation_manager()
                  ->GetPendingItem()
                  ->GetInternalScrollToTextFragment()
                  .has_value());
  EXPECT_EQ("start,end", navigation_manager()
                             ->GetPendingItem()
                             ->GetInternalScrollToTextFragment()
                             .value());
}

// Tests that LoadURLWithParams() calls RecordPageStateInNavigationItem() on the
// navigation manager deleget before navigating to the new URL.
TEST_P(NavigationManagerTest, LoadURLWithParamsSavesStateOnCurrentItem) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  NavigationManager::WebLoadParams params(GURL("http://www.url.com/1"));
  params.transition_type = ui::PAGE_TRANSITION_TYPED;

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());
  EXPECT_CALL(navigation_manager_delegate(),
              LoadCurrentItem(NavigationInitiationType::BROWSER_INITIATED));

  navigation_manager()->LoadURLWithParams(params);

  NavigationItem* last_committed_item =
      navigation_manager()->GetLastCommittedItem();
  ASSERT_TRUE(last_committed_item);
  EXPECT_EQ("http://www.url.com/0", last_committed_item->GetURL().spec());
  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  ASSERT_TRUE(pending_item);
  EXPECT_EQ("http://www.url.com/1", pending_item->GetURL().spec());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(pending_item->GetTransitionType(),
                                           ui::PAGE_TRANSITION_TYPED));
  EXPECT_FALSE(pending_item->HasPostData());
  EXPECT_EQ(web::HttpsUpgradeType::kNone, pending_item->GetHttpsUpgradeType());
}

TEST_P(NavigationManagerTest, UpdatePendingItemWithoutPendingItem) {
  navigation_manager()->UpdatePendingItemUrl(GURL("http://another.url.com"));
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
}

TEST_P(NavigationManagerTest, UpdatePendingItemWithPendingItem) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  navigation_manager()->UpdatePendingItemUrl(GURL("http://another.url.com"));

  ASSERT_TRUE(navigation_manager()->GetPendingItem());
  EXPECT_EQ("http://another.url.com/",
            navigation_manager()->GetPendingItem()->GetURL().spec());
}

TEST_P(NavigationManagerTest,
       UpdatePendingItemWithPendingItemAlreadyCommitted) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com"];
  navigation_manager()->CommitPendingItem();
  navigation_manager()->UpdatePendingItemUrl(GURL("http://another.url.com"));

  ASSERT_EQ(1, navigation_manager()->GetItemCount());
  EXPECT_EQ("http://www.url.com/",
            navigation_manager()->GetItemAtIndex(0)->GetURL().spec());
}

// Tests that LoadCurrentItem() is exercised when going to a different page.
TEST_P(NavigationManagerTest, GoToIndexDifferentDocument) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
  EXPECT_FALSE(navigation_manager()->GetItemAtIndex(0)->GetTransitionType() &
               ui::PAGE_TRANSITION_FORWARD_BACK);

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());

  navigation_manager()->GoToIndex(0);
  EXPECT_TRUE(navigation_manager()->GetItemAtIndex(0)->GetTransitionType() &
              ui::PAGE_TRANSITION_FORWARD_BACK);
}

// Tests that LoadCurrentItem() is not exercised for same-document navigation.
TEST_P(NavigationManagerTest, GoToIndexSameDocument) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0#hash"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  static_cast<NavigationItemImpl*>(navigation_manager()->GetPendingItem())
      ->SetIsCreatedFromHashChange(true);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0#hash"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem();

  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_EQ(-1, navigation_manager()->GetPendingItemIndex());
  EXPECT_FALSE(navigation_manager()->GetItemAtIndex(0)->GetTransitionType() &
               ui::PAGE_TRANSITION_FORWARD_BACK);

  EXPECT_CALL(navigation_manager_delegate(), RecordPageStateInNavigationItem());
  EXPECT_CALL(navigation_manager_delegate(), ClearDialogs());

  navigation_manager()->GoToIndex(0);
  EXPECT_TRUE(navigation_manager()->GetItemAtIndex(0)->GetTransitionType() &
              ui::PAGE_TRANSITION_FORWARD_BACK);
}

// Tests that NavigationManagerImpl::CommitPendingItem() is no-op when called
// with null.
TEST_P(NavigationManagerTest, CommitNilPendingItem) {
  ASSERT_EQ(0, navigation_manager()->GetItemCount());
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:nil
               forwardListURLs:nil];
  navigation_manager()->CommitPendingItem(nullptr);

  EXPECT_EQ(1, navigation_manager()->GetItemCount());
  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ("http://www.url.com/0",
            navigation_manager()->GetLastCommittedItem()->GetURL());
}

// Tests that NavigationManagerImpl::CommitPendingItem() for an invalid URL
// doesn't crash.
TEST_P(NavigationManagerTest, CommitEmptyPendingItem) {
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:nil
               forwardListURLs:nil];

  // Call CommitPendingItem() with a valid pending item.
  auto item = std::make_unique<web::NavigationItemImpl>();
  item->SetURL(GURL());
  navigation_manager()->CommitPendingItem(std::move(item));
}

// Tests NavigationManagerImpl::CommitPendingItem() with a valid pending item.
TEST_P(NavigationManagerTest, CommitNonNilPendingItem) {
  // Create navigation manager with a single forward item and no back items.
  [mock_wk_list_ setCurrentURL:@"http://www.url.test"
                  backListURLs:@[
                    @"www.url.test/0",
                  ]
               forwardListURLs:nil];
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.test/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  navigation_manager()->CommitPendingItem();
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.test/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  navigation_manager()->CommitPendingItem();
  SimulateGoToIndex(0);
  mock_wk_list_.backList = @[ mock_wk_list_.currentItem ];
  mock_wk_list_.currentItem =
      [CRWFakeBackForwardList itemWithURLString:@"http://www.url.com/new"];
  mock_wk_list_.forwardList = nil;
  ASSERT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  ASSERT_EQ(2, navigation_manager()->GetItemCount());

  // Emulate 2 simultanious navigations to verify that pending item index does
  // not prevent passed item commit.
  navigation_manager()->SetPendingItemIndex(0);

  // Call CommitPendingItem() with a valid pending item.
  auto item = std::make_unique<web::NavigationItemImpl>();
  item->SetURL(GURL("http://www.url.com/new"));
  item->SetNavigationInitiationType(
      web::NavigationInitiationType::BROWSER_INITIATED);
  navigation_manager()->CommitPendingItem(std::move(item));

  // Verify navigation manager and navigation item states.
  EXPECT_EQ(1, navigation_manager()->GetLastCommittedItemIndex());
  EXPECT_EQ(0, navigation_manager()->GetPendingItemIndex());
  ASSERT_TRUE(navigation_manager()->GetLastCommittedItem());
  EXPECT_FALSE(
      navigation_manager()->GetLastCommittedItem()->GetTimestamp().is_null());
  EXPECT_EQ(web::NavigationInitiationType::NONE,
            navigation_manager()
                ->GetLastCommittedItemImpl()
                ->NavigationInitiationType());
  ASSERT_EQ(2, navigation_manager()->GetItemCount());
  EXPECT_EQ(navigation_manager()->GetLastCommittedItem(),
            navigation_manager()->GetItemAtIndex(1));
}

TEST_P(NavigationManagerTest, LoadIfNecessary) {
  EXPECT_CALL(navigation_manager_delegate(), LoadIfNecessary()).Times(1);
  navigation_manager()->LoadIfNecessary();
}

// Tests that GetCurrentItemImpl() returns the pending item or last committed
// item in that precedence order.
TEST_P(NavigationManagerTest, GetCurrentItemImpl) {
  ASSERT_EQ(nullptr, navigation_manager()->GetCurrentItemImpl());

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();
  NavigationItem* last_committed_item =
      navigation_manager()->GetLastCommittedItem();
  ASSERT_NE(last_committed_item, nullptr);
  EXPECT_EQ(last_committed_item, navigation_manager()->GetCurrentItemImpl());

  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  NavigationItem* pending_item = navigation_manager()->GetPendingItem();
  ASSERT_NE(pending_item, nullptr);
  EXPECT_EQ(pending_item, navigation_manager()->GetCurrentItemImpl());
}

// Tests that the SSLStatus is updated when creating a new NavigationItem
// lazily when getting it by index and the item is the current one.
TEST_P(
    NavigationManagerTest,
    GetNavigationItemImplAtIndex_UpdateSSLStatusForLazilyCreatedItem_Enabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kUpdateSSLStatusOnNavigationItemLazyCreation);
  // Add a committed item to the back-forward list. This does not
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  ON_CALL(navigation_manager_delegate(), GetCurrentURL())
      .WillByDefault(testing::Return(GURL("http://www.url.com/0")));

  // Expect the delegate method to be called the first time
  // GetNavigationItemImplAtIndex is called, which triggers the lazy creation.
  EXPECT_CALL(navigation_manager_delegate(),
              UpdateSSLStatusForCurrentNavigationItem())
      .Times(1);

  NavigationItem* item = navigation_manager()->GetNavigationItemImplAtIndex(0);
  ASSERT_TRUE(item);
  EXPECT_EQ(GURL("http://www.url.com/0"), item->GetURL());

  // Verify the mock call and reset expectations.
  testing::Mock::VerifyAndClearExpectations(&navigation_manager_delegate());

  EXPECT_CALL(navigation_manager_delegate(),
              UpdateSSLStatusForCurrentNavigationItem())
      .Times(0);

  item = navigation_manager()->GetNavigationItemImplAtIndex(0);
  ASSERT_TRUE(item);
}

// Tests that the SSLStatus is not updated when creating a new NavigationItem
// lazily for a non-current item.
TEST_P(NavigationManagerTest,
       GetNavigationItemImplAtIndex_NoUpdateSSLStatusForNonCurrentItem) {
  // Add two committed items to the back-forward list.
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];

  EXPECT_CALL(navigation_manager_delegate(),
              UpdateSSLStatusForCurrentNavigationItem())
      .Times(0);

  NavigationItem* item = navigation_manager()->GetNavigationItemImplAtIndex(0);
  ASSERT_TRUE(item);
  EXPECT_EQ(GURL("http://www.url.com/0"), item->GetURL());
}

// Tests that when the dedicated kill switch is activated, the SSLStatus is not
// updated when creating a new NavigationItem lazily when getting it by index.
TEST_P(
    NavigationManagerTest,
    GetNavigationItemImplAtIndex_UpdateSSLStatusForLazilyCreatedItem_Disabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kUpdateSSLStatusOnNavigationItemLazyCreation);
  // Add a committed item to the back-forward list. This does not
  // associate a NavigationItemImpl with the WKBackForwardListItem yet.
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];

  // Expect the delegate method NOT to be called the first time
  // GetNavigationItemImplAtIndex is called, which triggers the lazy creation.
  EXPECT_CALL(navigation_manager_delegate(),
              UpdateSSLStatusForCurrentNavigationItem())
      .Times(0);

  NavigationItem* item = navigation_manager()->GetNavigationItemImplAtIndex(0);
  ASSERT_TRUE(item);
  EXPECT_EQ(GURL("http://www.url.com/0"), item->GetURL());
}

TEST_P(NavigationManagerTest, UpdateCurrentItemForReplaceState) {
  navigation_manager()->AddPendingItem(
      GURL("http://www.url.com/0"),
      Referrer(GURL("http://referrer.com"), ReferrerPolicyDefault),
      ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  // Tests that pending item can be replaced.
  GURL replace_page_url("http://www.url.com/replace");
  NSString* state_object = @"{'foo': 1}";

  // Replace current item and check history size and fields of the modified
  // item.
  navigation_manager()->UpdateCurrentItemForReplaceState(replace_page_url,
                                                         state_object);

  EXPECT_EQ(0, navigation_manager()->GetItemCount());
  auto* pending_item =
      static_cast<NavigationItemImpl*>(navigation_manager()->GetPendingItem());
  EXPECT_EQ(replace_page_url, pending_item->GetURL());
  EXPECT_NSEQ(state_object, pending_item->GetSerializedStateObject());
  EXPECT_EQ(GURL("http://referrer.com"), pending_item->GetReferrer().url);

  // Commit pending item and tests that replace updates the committed item.
  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  navigation_manager()->CommitPendingItem();

  // Replace current item again and check history size and fields.
  GURL replace_page_url2("http://www.url.com/replace2");
  navigation_manager()->UpdateCurrentItemForReplaceState(replace_page_url2,
                                                         nil);

  EXPECT_EQ(1, navigation_manager()->GetItemCount());
  auto* last_committed_item = static_cast<NavigationItemImpl*>(
      navigation_manager()->GetLastCommittedItem());
  EXPECT_EQ(replace_page_url2, last_committed_item->GetURL());
  EXPECT_NSEQ(nil, last_committed_item->GetSerializedStateObject());
  EXPECT_EQ(GURL("http://referrer.com"),
            last_committed_item->GetReferrer().url);
}

// Tests SetPendingItem() and ReleasePendingItem() methods.
TEST_P(NavigationManagerTest, TransferPendingItem) {
  auto item = std::make_unique<web::NavigationItemImpl>();
  web::NavigationItemImpl* item_ptr = item.get();

  navigation_manager()->SetPendingItem(std::move(item));
  EXPECT_EQ(item_ptr, navigation_manager()->GetPendingItem());

  auto extracted_item = navigation_manager()->ReleasePendingItem();
  EXPECT_FALSE(navigation_manager()->GetPendingItem());
  EXPECT_EQ(item_ptr, extracted_item.get());
}

// Tests that GetItemAtIndex() on an empty manager will sync navigation items to
// WKBackForwardList using default properties.
TEST_P(NavigationManagerTest, SyncAfterItemAtIndex) {
  EXPECT_EQ(0, manager_->GetItemCount());
  EXPECT_EQ(nullptr, manager_->GetItemAtIndex(0));

  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
  EXPECT_EQ(1, manager_->GetItemCount());
  EXPECT_EQ(0, manager_->GetLastCommittedItemIndex());

  NavigationItem* item = manager_->GetItemAtIndex(0);
  ASSERT_NE(item, nullptr);
  EXPECT_EQ(GURL("http://www.0.com"), item->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_LINK,
                                           item->GetTransitionType()));
  EXPECT_EQ(UserAgentType::NONE, item->GetUserAgentType());
  EXPECT_FALSE(item->GetTimestamp().is_null());
  EXPECT_EQ(web::HttpsUpgradeType::kNone, item->GetHttpsUpgradeType());
}

// Tests that Referrer is inferred from the previous WKBackForwardListItem.
TEST_P(NavigationManagerTest, SyncAfterItemAtIndexWithPreviousItem) {
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                  backListURLs:@[ @"http://www.0.com" ]
               forwardListURLs:@[ @"http://www.2.com" ]];
  EXPECT_EQ(3, manager_->GetItemCount());
  EXPECT_EQ(1, manager_->GetLastCommittedItemIndex());

  // The out-of-order access is intentionall to test that syncing doesn't rely
  // on the previous WKBackForwardListItem having an associated NavigationItem.
  NavigationItem* item2 = manager_->GetItemAtIndex(2);
  ASSERT_NE(item2, nullptr);
  EXPECT_EQ(GURL("http://www.2.com"), item2->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_LINK,
                                           item2->GetTransitionType()));
  EXPECT_EQ(UserAgentType::NONE, item2->GetUserAgentType());
  EXPECT_EQ(GURL("http://www.1.com"), item2->GetReferrer().url);
  EXPECT_FALSE(item2->GetTimestamp().is_null());

  NavigationItem* item1 = manager_->GetItemAtIndex(1);
  ASSERT_NE(item1, nullptr);
  EXPECT_EQ(GURL("http://www.1.com"), item1->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_LINK,
                                           item1->GetTransitionType()));
  EXPECT_EQ(UserAgentType::NONE, item1->GetUserAgentType());
  EXPECT_EQ(GURL("http://www.0.com"), item1->GetReferrer().url);
  EXPECT_FALSE(item1->GetTimestamp().is_null());

  NavigationItem* item0 = manager_->GetItemAtIndex(0);
  ASSERT_NE(item0, nullptr);
  EXPECT_EQ(GURL("http://www.0.com"), item0->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_LINK,
                                           item0->GetTransitionType()));
  EXPECT_EQ(UserAgentType::NONE, item0->GetUserAgentType());
  EXPECT_FALSE(item0->GetTimestamp().is_null());
}

// Tests that GetLastCommittedItem() creates a default NavigationItem when the
// last committed item in WKWebView does not have a linked entry.
TEST_P(NavigationManagerTest, SyncInGetLastCommittedItem) {
  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
  EXPECT_EQ(1, manager_->GetItemCount());

  NavigationItem* item = manager_->GetLastCommittedItem();
  ASSERT_NE(item, nullptr);
  EXPECT_EQ("http://www.0.com/", item->GetURL().spec());
  EXPECT_FALSE(item->GetTimestamp().is_null());
}

// Tests that GetLastCommittedItem() creates a default NavigationItem when the
// last committed item in WKWebView is an app-specific URL.
TEST_P(NavigationManagerTest, SyncInGetLastCommittedItemForAppSpecificURL) {
  GURL url(url::SchemeHostPort(kSchemeToRewrite, "test", 0).Serialize());

  // Verifies that the test URL is rewritten into an app-specific URL.
  manager_->AddPendingItem(url, Referrer(), ui::PAGE_TRANSITION_TYPED,
                           web::NavigationInitiationType::BROWSER_INITIATED,
                           /*is_post_navigation=*/false,
                           /*is_error_navigation=*/false,
                           web::HttpsUpgradeType::kNone);
  NavigationItem* pending_item = manager_->GetPendingItem();
  ASSERT_TRUE(pending_item);
  ASSERT_TRUE(web::GetWebClient()->IsAppSpecificURL(pending_item->GetURL()));

  [mock_wk_list_ setCurrentURL:base::SysUTF8ToNSString(url.spec())];
  NavigationItem* item = manager_->GetLastCommittedItem();

  ASSERT_NE(item, nullptr);
  EXPECT_EQ(url, item->GetURL());
  EXPECT_EQ(1, manager_->GetItemCount());
}

// Tests that CommitPendingItem() will sync navigation items to
// WKBackForwardList and the pending item NavigationItemImpl will be used.
TEST_P(NavigationManagerTest, GetItemAtIndexAfterCommitPending) {
  // Simulate a main frame navigation.
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  NavigationItem* pending_item0 = manager_->GetPendingItem();

  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
  manager_->CommitPendingItem();

  EXPECT_EQ(1, manager_->GetItemCount());
  NavigationItem* item = manager_->GetLastCommittedItem();
  EXPECT_EQ(pending_item0, item);
  EXPECT_EQ(GURL("http://www.0.com"), item->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_TYPED,
                                           item->GetTransitionType()));

  // Simulate a second main frame navigation.
  manager_->AddPendingItem(
      GURL("http://www.2.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  NavigationItem* pending_item2 = manager_->GetPendingItem();

  // Simulate an iframe navigation between the two main frame navigations.
  [mock_wk_list_ setCurrentURL:@"http://www.2.com"
                  backListURLs:@[ @"http://www.0.com", @"http://www.1.com" ]
               forwardListURLs:nil];
  manager_->CommitPendingItem();

  EXPECT_EQ(3, manager_->GetItemCount());
  EXPECT_EQ(2, manager_->GetLastCommittedItemIndex());

  // This item is created by syncing.
  NavigationItem* item1 = manager_->GetItemAtIndex(1);
  EXPECT_EQ(GURL("http://www.1.com"), item1->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_LINK,
                                           item1->GetTransitionType()));
  EXPECT_EQ(GURL("http://www.0.com"), item1->GetReferrer().url);

  // This item is created by CommitPendingItem.
  NavigationItem* item2 = manager_->GetItemAtIndex(2);
  EXPECT_EQ(pending_item2, item2);
  EXPECT_EQ(GURL("http://www.2.com"), item2->GetURL());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_TYPED,
                                           item2->GetTransitionType()));
  EXPECT_EQ(GURL(""), item2->GetReferrer().url);
}

// Tests that AddPendingItem does not create a new NavigationItem if the new
// pending item is a back forward navigation or when reloading a redirect page.
TEST_P(NavigationManagerTest, ReusePendingItemForHistoryNavigation) {
  // Simulate two regular navigations.
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                  backListURLs:@[ @"http://www.0.com" ]
               forwardListURLs:nil];

  // Force sync NavigationItems.
  NavigationItem* original_item0 = manager_->GetItemAtIndex(0);
  manager_->GetItemAtIndex(1);

  // Simulate a back-forward navigation. Manually shuffle the objects in
  // mock_wk_list_ to avoid creating new WKBackForwardListItem mocks and
  // preserve existing NavigationItem associations.
  WKBackForwardListItem* wk_item0 = [mock_wk_list_ itemAtIndex:-1];
  WKBackForwardListItem* wk_item1 = [mock_wk_list_ itemAtIndex:0];
  mock_wk_list_.currentItem = wk_item0;
  mock_wk_list_.backList = nil;
  mock_wk_list_.forwardList = @[ wk_item1 ];
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.0.com"]);
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_EQ(original_item0, manager_->GetPendingItem());
}

// Tests that transient URL rewriters are only applied to a new pending item.
TEST_P(NavigationManagerTest, TransientURLRewritersOnlyUsedForPendingItem) {
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  // Install transient URL rewriters.
  manager_->AddTransientURLRewriter(&AppendingUrlRewriter);
  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];

  // Transient URL rewriters do not apply to lazily synced items.
  NavigationItem* item0 = manager_->GetItemAtIndex(0);
  EXPECT_EQ(GURL("http://www.0.com"), item0->GetURL());

  // Transient URL rewriters are applied to a new pending item.
  manager_->AddPendingItem(
      GURL("http://www.2.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_EQ(kRewrittenQueryParam,
            manager_->GetPendingItem()->GetURL().GetQuery());
}

// Tests DiscardNonCommittedItems discards pending items.
TEST_P(NavigationManagerTest, DiscardNonCommittedItems) {
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_NE(nullptr, manager_->GetPendingItem());

  manager_->DiscardNonCommittedItems();
  EXPECT_EQ(nullptr, manager_->GetPendingItem());
}

// Tests that going back is delegated to the underlying WKWebView.
TEST_P(NavigationManagerTest, GoBack) {
  ASSERT_FALSE(manager_->CanGoBack());

  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];

  manager_->AddPendingItem(
      GURL("http://www.1.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                  backListURLs:@[ @"http://www.0.com" ]
               forwardListURLs:nil];

  ASSERT_TRUE(manager_->CanGoBack());

  const BackForwardNavigationType expected_navigation_type =
      SkipAutomaticNavigationInBackForwardList()
          ? BackForwardNavigationType::kToEntry
          : BackForwardNavigationType::kBackward;

  EXPECT_CALL(
      delegate_,
      GoToBackForwardListItem(
          mock_wk_list_.backList[0], manager_->GetItemAtIndex(0),
          expected_navigation_type, NavigationInitiationType::BROWSER_INITIATED,
          /*has_user_gesture=*/true));
  manager_->GoBack();
  [mock_web_view_ verify];
}

// Tests that going forward is always delegated to the underlying WKWebView
// without any sanity checks such as whether any forward history exists.
TEST_P(NavigationManagerTest, GoForward) {
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
  manager_->CommitPendingItem();

  manager_->AddPendingItem(
      GURL("http://www.1.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                  backListURLs:@[ @"http://www.0.com" ]
               forwardListURLs:nil];

  [mock_wk_list_ moveCurrentToIndex:0];
  ASSERT_TRUE(manager_->CanGoForward());

  const BackForwardNavigationType expected_navigation_type =
      SkipAutomaticNavigationInBackForwardList()
          ? BackForwardNavigationType::kToEntry
          : BackForwardNavigationType::kForward;

  EXPECT_CALL(
      delegate_,
      GoToBackForwardListItem(
          mock_wk_list_.forwardList[0], manager_->GetItemAtIndex(1),
          expected_navigation_type, NavigationInitiationType::BROWSER_INITIATED,
          /*has_user_gesture=*/true));
  manager_->GoForward();
  [mock_web_view_ verify];
}

// Tests that going forward clears uncommitted items.
TEST_P(NavigationManagerTest, GoForwardShouldDiscardsUncommittedItems) {
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.0.com"];
  manager_->CommitPendingItem();

  manager_->AddPendingItem(
      GURL("http://www.1.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                  backListURLs:@[ @"http://www.0.com" ]
               forwardListURLs:nil];

  [mock_wk_list_ moveCurrentToIndex:0];
  ASSERT_TRUE(manager_->CanGoForward());

  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_NE(nullptr, manager_->GetPendingItem());

  const BackForwardNavigationType expected_navigation_type =
      SkipAutomaticNavigationInBackForwardList()
          ? BackForwardNavigationType::kToEntry
          : BackForwardNavigationType::kForward;

  EXPECT_CALL(
      delegate_,
      GoToBackForwardListItem(
          mock_wk_list_.forwardList[0], manager_->GetItemAtIndex(1),
          expected_navigation_type, NavigationInitiationType::BROWSER_INITIATED,
          /*has_user_gesture=*/true));
  manager_->GoForward();
  [mock_web_view_ verify];

  EXPECT_EQ(nullptr, manager_->GetPendingItem());
}

// Tests CanGoToOffset API for positive, negative and zero delta.
TEST_P(NavigationManagerTest, CanGoToOffset) {
  manager_->AddPendingItem(
      GURL("http://www.url.com/0"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/0"];
  manager_->CommitPendingItem();

  manager_->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
                  backListURLs:@[ @"http://www.url.com/0" ]
               forwardListURLs:nil];
  manager_->CommitPendingItem();

  manager_->AddPendingItem(
      GURL("http://www.url.com/2"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  [mock_wk_list_
        setCurrentURL:@"http://www.url.com/2"
         backListURLs:@[ @"http://www.url.com/0", @"http://www.url.com/1" ]
      forwardListURLs:nil];
  manager_->CommitPendingItem();

  ASSERT_EQ(3, manager_->GetItemCount());
  ASSERT_EQ(2, manager_->GetLastCommittedItemIndex());

  // Go to entry at index 1 and test API from that state.
  [mock_wk_list_ moveCurrentToIndex:1];
  ASSERT_EQ(1, manager_->GetLastCommittedItemIndex());
  ASSERT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_TRUE(manager_->CanGoToOffset(-1));
  EXPECT_EQ(0, manager_->GetIndexForOffset(-1));
  EXPECT_FALSE(manager_->CanGoToOffset(-2));
  EXPECT_TRUE(manager_->CanGoToOffset(1));
  EXPECT_EQ(2, manager_->GetIndexForOffset(1));
  EXPECT_FALSE(manager_->CanGoToOffset(2));
  // Test with large values
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MAX));
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MIN));

  // Go to entry at index 0 and test API from that state.
  [mock_wk_list_ moveCurrentToIndex:0];
  ASSERT_EQ(0, manager_->GetLastCommittedItemIndex());
  ASSERT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_FALSE(manager_->CanGoToOffset(-1));
  EXPECT_TRUE(manager_->CanGoToOffset(1));
  EXPECT_EQ(1, manager_->GetIndexForOffset(1));
  EXPECT_TRUE(manager_->CanGoToOffset(2));
  EXPECT_EQ(2, manager_->GetIndexForOffset(2));
  EXPECT_FALSE(manager_->CanGoToOffset(3));
  // Test with large values
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MAX));
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MIN));

  // Go to entry at index 2 and test API from that state.
  [mock_wk_list_ moveCurrentToIndex:2];
  ASSERT_EQ(2, manager_->GetLastCommittedItemIndex());
  ASSERT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_TRUE(manager_->CanGoToOffset(-1));
  EXPECT_EQ(1, manager_->GetIndexForOffset(-1));
  EXPECT_TRUE(manager_->CanGoToOffset(-2));
  EXPECT_EQ(0, manager_->GetIndexForOffset(-2));
  EXPECT_FALSE(manager_->CanGoToOffset(1));
  // Test with large values
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MAX));
  EXPECT_FALSE(manager_->CanGoToOffset(INT_MIN));

  // Simulate a history navigation pending item.
  [mock_wk_list_ moveCurrentToIndex:1];
  OCMStub([mock_web_view_ URL])
      .andReturn([[NSURL alloc] initWithString:@"http://www.url.com/1"]);
  manager_->AddPendingItem(
      GURL("http://www.url.com/1"), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  EXPECT_EQ(3, manager_->GetItemCount());
  EXPECT_EQ(2, manager_->GetLastCommittedItemIndex());
  EXPECT_EQ(1, manager_->GetPendingItemIndex());
  EXPECT_TRUE(manager_->CanGoToOffset(-1));
  EXPECT_EQ(0, manager_->GetIndexForOffset(-1));
  EXPECT_FALSE(manager_->CanGoToOffset(-2));
  EXPECT_TRUE(manager_->CanGoToOffset(1));
  EXPECT_EQ(2, manager_->GetIndexForOffset(1));
  EXPECT_FALSE(manager_->CanGoToOffset(2));
}

// Tests that Restore() accepts empty session history and performs no-op.
TEST_P(NavigationManagerTest, RestoreSessionWithEmptyHistory) {
  manager_->Restore(-1 /* last_committed_item_index */,
                    std::vector<std::unique_ptr<NavigationItem>>());

  ASSERT_EQ(nullptr, manager_->GetPendingItem());
}

// Tests that all NavigationManager APIs return reasonable values in the Empty
// Window Open Navigation edge case. See comments in header file for details.
TEST_P(NavigationManagerTest, EmptyWindowOpenNavigation) {
  // Set up the precondition for an empty window open item.
  // Use OCMStub for `URL` instead of OCMExpect because it will only be called
  // when DCHECKS are enabled.
  OCMStub([mock_web_view_ URL])
      .andReturn(net::NSURLWithGURL(GURL(url::kAboutBlankURL)));
  mock_wk_list_.currentItem = nil;

  manager_->AddPendingItem(
      GURL(url::kAboutBlankURL), Referrer(), ui::PAGE_TRANSITION_LINK,
      web::NavigationInitiationType::RENDERER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  const NavigationItem* pending_item = manager_->GetPendingItem();
  ASSERT_TRUE(pending_item);
  EXPECT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_EQ(url::kAboutBlankURL, pending_item->GetURL().spec());

  manager_->CommitPendingItem();

  const NavigationItem* last_committed_item = manager_->GetLastCommittedItem();
  ASSERT_EQ(pending_item, last_committed_item);
  EXPECT_EQ(last_committed_item, manager_->GetVisibleItem());

  EXPECT_EQ(0, manager_->GetIndexForOffset(0));
  EXPECT_EQ(1, manager_->GetIndexForOffset(1));
  EXPECT_EQ(-1, manager_->GetIndexForOffset(-1));

  EXPECT_EQ(1, manager_->GetItemCount());
  EXPECT_EQ(last_committed_item, manager_->GetItemAtIndex(0));
  EXPECT_FALSE(manager_->GetItemAtIndex(1));

  EXPECT_EQ(0, manager_->GetIndexOfItem(last_committed_item));
  EXPECT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_EQ(0, manager_->GetLastCommittedItemIndex());

  EXPECT_FALSE(manager_->CanGoBack());
  EXPECT_FALSE(manager_->CanGoForward());
  EXPECT_TRUE(manager_->CanGoToOffset(0));
  EXPECT_FALSE(manager_->CanGoToOffset(-1));
  EXPECT_FALSE(manager_->CanGoToOffset(1));

  // This is allowed on an empty window open item.
  manager_->GoToIndex(0);

  // Add another navigation and verify that it replaces the empty window open
  // item.
  manager_->AddPendingItem(
      GURL("http://www.2.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);

  const NavigationItem* pending_item_2 = manager_->GetPendingItem();
  ASSERT_TRUE(pending_item_2);
  EXPECT_EQ("http://www.2.com/", pending_item_2->GetURL().spec());

  [mock_wk_list_ setCurrentURL:@"http://www.2.com"];
  manager_->CommitPendingItem();

  const NavigationItem* last_committed_item_2 =
      manager_->GetLastCommittedItem();
  ASSERT_EQ(pending_item_2, last_committed_item_2);
  EXPECT_EQ(last_committed_item_2, manager_->GetVisibleItem());

  EXPECT_EQ(0, manager_->GetIndexForOffset(0));
  EXPECT_EQ(1, manager_->GetIndexForOffset(1));
  EXPECT_EQ(-1, manager_->GetIndexForOffset(-1));

  EXPECT_EQ(1, manager_->GetItemCount());
  EXPECT_EQ(last_committed_item_2, manager_->GetItemAtIndex(0));
  EXPECT_FALSE(manager_->GetItemAtIndex(1));

  EXPECT_EQ(-1, manager_->GetIndexOfItem(last_committed_item));
  EXPECT_EQ(0, manager_->GetIndexOfItem(last_committed_item_2));
  EXPECT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_EQ(0, manager_->GetLastCommittedItemIndex());

  EXPECT_FALSE(manager_->CanGoBack());
  EXPECT_FALSE(manager_->CanGoForward());
  EXPECT_TRUE(manager_->CanGoToOffset(0));
  EXPECT_FALSE(manager_->CanGoToOffset(-1));
  EXPECT_FALSE(manager_->CanGoToOffset(1));

  // This is still allowed on a length-1 navigation history.
  manager_->GoToIndex(0);
}

// Tests that GetVisibleWebViewURL() returns a cached GURL.
TEST_P(NavigationManagerTest, TestGetVisibleWebViewOriginURLCache) {
  NavigationManagerImpl manager(&browser_state_, &delegate_);
  NavigationManagerImpl::WKWebViewCache& cache = manager.web_view_cache_;

  GURL gurl("http://www.existing.com");
  __block NSURL* nsurl = [NSURL URLWithString:@"http://www.existing.com"];
  OCMStub([mock_web_view_ URL]).andDo(^(NSInvocation* invocation) {
    [invocation setReturnValue:&nsurl];
  });
  EXPECT_EQ(gurl, cache.GetVisibleWebViewOriginURL());

  // Change mock_web_view_'s URL.
  nsurl = [NSURL URLWithString:@"http://www.anotherexisting.com"];
  EXPECT_NE(gurl, cache.GetVisibleWebViewOriginURL());
  EXPECT_EQ(GURL("http://www.anotherexisting.com"),
            cache.GetVisibleWebViewOriginURL());
}

// Test fixture for detach from web view mode for NavigationManagerImpl.
class NavigationManagerDetachedModeTest : public NavigationManagerTest {
 protected:
  void SetUp() override {
    // Sets up each test case with a session history of 3 items. The middle item
    // is the current item.
    url0_ = GURL("http://www.0.com");
    url1_ = GURL("http://www.1.com");
    url2_ = GURL("http://www.2.com");

    [mock_wk_list_ setCurrentURL:@"http://www.1.com"
                    backListURLs:@[ @"http://www.0.com" ]
                 forwardListURLs:@[ @"http://www.2.com" ]];

    ASSERT_EQ(url0_, manager_->GetItemAtIndex(0)->GetURL());
    ASSERT_EQ(url1_, manager_->GetItemAtIndex(1)->GetURL());
    ASSERT_EQ(url2_, manager_->GetItemAtIndex(2)->GetURL());
  }

  GURL url0_;
  GURL url1_;
  GURL url2_;
};

INSTANTIATE_TEST_SUITE_P(, NavigationManagerDetachedModeTest, testing::Bool());

// Tests that all getters return the expected value in detached mode.
TEST_P(NavigationManagerDetachedModeTest, CachedSessionHistory) {
  manager_->DetachFromWebView();
  delegate_.RemoveWebView();

  EXPECT_EQ(url1_, manager_->GetVisibleItem()->GetURL());
  EXPECT_EQ(3, manager_->GetItemCount());

  EXPECT_EQ(url0_, manager_->GetItemAtIndex(0)->GetURL());
  EXPECT_EQ(url1_, manager_->GetItemAtIndex(1)->GetURL());
  EXPECT_EQ(url2_, manager_->GetItemAtIndex(2)->GetURL());

  EXPECT_EQ(0, manager_->GetIndexOfItem(manager_->GetItemAtIndex(0)));
  EXPECT_EQ(1, manager_->GetIndexOfItem(manager_->GetItemAtIndex(1)));
  EXPECT_EQ(2, manager_->GetIndexOfItem(manager_->GetItemAtIndex(2)));

  EXPECT_EQ(-1, manager_->GetPendingItemIndex());
  EXPECT_EQ(nullptr, manager_->GetPendingItem());

  EXPECT_EQ(1, manager_->GetLastCommittedItemIndex());
  EXPECT_EQ(url1_, manager_->GetLastCommittedItem()->GetURL());

  EXPECT_TRUE(manager_->CanGoBack());
  EXPECT_TRUE(manager_->CanGoForward());
  EXPECT_TRUE(manager_->CanGoToOffset(0));
  EXPECT_TRUE(manager_->CanGoToOffset(-1));
  EXPECT_TRUE(manager_->CanGoToOffset(1));

  EXPECT_EQ(0, manager_->GetIndexForOffset(-1));
  EXPECT_EQ(1, manager_->GetIndexForOffset(0));
  EXPECT_EQ(2, manager_->GetIndexForOffset(1));

  std::vector<NavigationItem*> backward_items = manager_->GetBackwardItems();
  EXPECT_EQ(1UL, backward_items.size());
  EXPECT_EQ(url0_, backward_items[0]->GetURL());

  std::vector<NavigationItem*> forward_items = manager_->GetForwardItems();
  EXPECT_EQ(1UL, forward_items.size());
  EXPECT_EQ(url2_, forward_items[0]->GetURL());
}

// Tests that detaching from an empty WKWebView works.
TEST_P(NavigationManagerDetachedModeTest, NothingToCache) {
  delegate_.RemoveWebView();
  manager_->DetachFromWebView();

  EXPECT_EQ(0, manager_->GetItemCount());
  EXPECT_EQ(nullptr, manager_->GetVisibleItem());
  EXPECT_EQ(nullptr, manager_->GetItemAtIndex(0));
  EXPECT_EQ(nullptr, manager_->GetPendingItem());
  EXPECT_EQ(-1, manager_->GetLastCommittedItemIndex());

  manager_->Reload(web::ReloadType::NORMAL, false /* check_for_repost */);
  EXPECT_EQ(nullptr, manager_->GetPendingItem());
}

// Tests that pending item is set to serializable when appropriate.
TEST_P(NavigationManagerDetachedModeTest, NotSerializable) {
  manager_->AddPendingItem(
      GURL("http://www.0.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_FALSE(manager_->GetPendingItemImpl()->ShouldSkipSerialization());

  manager_->SetWKWebViewNextPendingUrlNotSerializable(GURL("http://www.1.com"));
  manager_->AddPendingItem(
      GURL("http://www.1.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_TRUE(manager_->GetPendingItemImpl()->ShouldSkipSerialization());

  manager_->AddPendingItem(
      GURL("http://www.1.com"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      web::NavigationInitiationType::BROWSER_INITIATED,
      /*is_post_navigation=*/false, /*is_error_navigation=*/false,
      web::HttpsUpgradeType::kNone);
  EXPECT_FALSE(manager_->GetPendingItemImpl()->ShouldSkipSerialization());
}

class NavigationManagerSerialisationTest : public PlatformTest {
 public:
  NavigationManagerSerialisationTest() {
    web_state_ = WebStateImpl::CreateWithFakeWebViewNavigationProxyForTesting(
        WebState::CreateParams(&browser_state_),
        [[CRWFakeWebViewNavigationProxy alloc] init]);
  }

  ~NavigationManagerSerialisationTest() override {}

  CRWFakeWebViewNavigationProxy* fake_web_view() {
    return base::apple::ObjCCastStrict<CRWFakeWebViewNavigationProxy>(
        web_state_->GetWebViewNavigationProxy());
  }

  WebStateImpl* web_state() { return web_state_.get(); }

  BrowserState* browser_state() { return &browser_state_; }

 private:
  WebTaskEnvironment task_environment_;
  FakeBrowserState browser_state_;
  std::unique_ptr<WebStateImpl> web_state_;
};

// Tests serializing NavigationManagerImpl state for a session that is
// longer than kMaxSessionSize with the last committed item at the end
// of the session.
TEST_F(NavigationManagerSerialisationTest, LargeSession) {
  // Populate the WebState with more than kMaxSessionSize navigation items.
  NSMutableArray<NSString*>* back_urls = [NSMutableArray array];
  for (int i = 0; i < 3 * wk_navigation_util::kMaxSessionSize / 2; ++i) {
    [back_urls addObject:[NSString stringWithFormat:@"http://%d.test", i]];
  }
  [fake_web_view() setCurrentURL:@"http://current.test"
                    backListURLs:back_urls
                 forwardListURLs:nil];
  const int original_item_count = web_state()->GetNavigationItemCount();
  EXPECT_GT(original_item_count, wk_navigation_util::kMaxSessionSize);

  // Verify that the serialised state only contains kMaxSessionSize items.
  proto::NavigationStorage storage;
  web_state()->GetNavigationManagerImpl().SerializeToProto(storage);
  const int storage_item_count = storage.items_size();
  ASSERT_EQ(storage_item_count, wk_navigation_util::kMaxSessionSize);
  const int offset = original_item_count - storage_item_count;

  // Verify that the serialised items URLs match the URLs in original storage.
  NavigationManager* navigation_manager = web_state()->GetNavigationManager();
  for (int i = 0; i < wk_navigation_util::kMaxSessionSize; ++i) {
    NavigationItem* item = navigation_manager->GetItemAtIndex(i + offset);
    const proto::NavigationItemStorage& item_storage = storage.items(i);
    EXPECT_EQ(item->GetURL(), GURL(item_storage.url()));
  }
}

// Tests serializing NavigationManagerImpl state for a session that contain
// items with ShouldSkipSerialization flag.
TEST_F(NavigationManagerSerialisationTest, ShouldSkipSerializationItems) {
  // Number of items to skip.
  const int kCountOfItemsToSkip = 9;

  // Number of items to insert in the session so that there is more than
  // kMaxSessionSize items after dropping the skipped items.
  const int kCountOfItemsToInsert =
      wk_navigation_util::kMaxSessionSize + kCountOfItemsToSkip;

  // Populate the WebState with more than kMaxSessionSize navigation items.
  NSMutableArray<NSString*>* back_urls = [NSMutableArray array];
  for (int i = 0; i < kCountOfItemsToInsert; ++i) {
    [back_urls addObject:[NSString stringWithFormat:@"http://%d.test", i]];
  }
  [fake_web_view() setCurrentURL:@"http://current.test"
                    backListURLs:back_urls
                 forwardListURLs:nil];
  const int original_item_count = web_state()->GetNavigationItemCount();

  web::NavigationManagerImpl& navigation_manager =
      web_state()->GetNavigationManagerImpl();

  // Skip the items just before the last committed item.
  const int skipped_item_begin =
      navigation_manager.GetLastCommittedItemIndex() - 1 - kCountOfItemsToSkip;
  const int skipped_item_end = skipped_item_begin + kCountOfItemsToSkip;
  for (int index = skipped_item_begin; index < skipped_item_end; ++index) {
    navigation_manager.GetNavigationItemImplAtIndex(index)
        ->SetShouldSkipSerialization(true);
  }

  // Verify that the serialised state only contains kMaxSessionSize items.
  proto::NavigationStorage storage;
  navigation_manager.SerializeToProto(storage);
  const int storage_item_count = storage.items_size();
  ASSERT_EQ(storage_item_count, wk_navigation_util::kMaxSessionSize);
  EXPECT_LT(storage.last_committed_item_index(), storage_item_count);
  const int offset = original_item_count - storage_item_count;

  // Verify that URLs in the storage match original URLs without skipped item.
  for (int i = 0; i < wk_navigation_util::kMaxSessionSize; ++i) {
    int item_index = i + offset;
    if (item_index < skipped_item_end) {
      item_index -= kCountOfItemsToSkip;
    }

    NavigationItem* item = navigation_manager.GetItemAtIndex(item_index);
    const proto::NavigationItemStorage& item_storage = storage.items(i);
    EXPECT_EQ(item->GetURL(), GURL(item_storage.url()));
  }
}

// Tests serializing NavigationManagerImpl state for a session that contain
// items with extra long URL.
TEST_F(NavigationManagerSerialisationTest, ExtraLongURL) {
  // Create extra long URL.
  NSString* long_url =
      [@"http://" stringByPaddingToLength:(url::kMaxURLChars + 1)
                               withString:@"a"
                          startingAtIndex:0];
  [fake_web_view() setCurrentURL:@"http://current.test"
                    backListURLs:@[ long_url ]
                 forwardListURLs:nil];
  const int original_item_count = web_state()->GetNavigationItemCount();
  EXPECT_EQ(original_item_count, 2);

  NavigationItem* item = web_state()->GetNavigationManager()->GetItemAtIndex(1);
  ASSERT_EQ(item->GetReferrer().url, GURL(base::SysNSStringToUTF8(long_url)));

  // Verify that the serialised state only contains one item, and that the
  // item does not have a referrer.
  proto::NavigationStorage storage;
  web_state()->GetNavigationManagerImpl().SerializeToProto(storage);
  const int storage_item_count = storage.items_size();
  ASSERT_EQ(storage_item_count, 1);
  EXPECT_EQ(GURL(storage.items(0).referrer().url()), GURL());
}

// Tests serializing NavigationManagerImpl state for a session that contain
// items with extra long URL as the last committed item.
TEST_F(NavigationManagerSerialisationTest, ExtraLongURLLastCommittedItem) {
  // Create extra long URL.
  NSString* long_url =
      [@"http://" stringByPaddingToLength:(url::kMaxURLChars + 1)
                               withString:@"a"
                          startingAtIndex:0];
  [fake_web_view() setCurrentURL:long_url
                    backListURLs:@[ @"http://current.test" ]
                 forwardListURLs:nil];
  const int original_item_count = web_state()->GetNavigationItemCount();
  EXPECT_EQ(original_item_count, 2);

  // Verify that the serialised state last committed item is correct.
  proto::NavigationStorage storage;
  web_state()->GetNavigationManagerImpl().SerializeToProto(storage);
  const int storage_item_count = storage.items_size();
  ASSERT_EQ(storage_item_count, 1);
  ASSERT_EQ(storage.last_committed_item_index(), 0);

  NavigationItem* item = web_state()->GetNavigationManager()->GetItemAtIndex(0);
  EXPECT_EQ(item->GetURL(), GURL(storage.items(0).url()));
}

// Tests that restoring a session works correctly.
TEST_F(NavigationManagerSerialisationTest, RestoreFromProto) {
  proto::NavigationStorage storage;
  storage.set_last_committed_item_index(0);
  for (std::string_view url : kTestURLs) {
    storage.add_items()->set_url(url);
  }
  storage.set_last_committed_item_index(storage.items_size() - 1);

  // Create a WebState with a real navigation proxy as this is required to
  // perform a session restore and access the view to force instantiation
  // of the WKWebView.
  std::unique_ptr<web::WebStateImpl> web_state =
      std::make_unique<web::WebStateImpl>(
          web::WebState::CreateParams(browser_state()));
  std::ignore = web_state->GetView();

  NavigationManagerImpl& navigation_manager =
      web_state->GetNavigationManagerImpl();

  navigation_manager.RestoreFromProto(storage);

  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
    return navigation_manager.GetItemCount() == kTestURLs.size();
  }));

  EXPECT_EQ(navigation_manager.GetLastCommittedItemIndex(),
            static_cast<int>(kTestURLs.size()) - 1);

  for (size_t index = 0; index < kTestURLs.size(); ++index) {
    EXPECT_EQ(navigation_manager.GetItemAtIndex(index)->GetURL(),
              GURL(kTestURLs[index]));
  }
}

// Tests that restoring a empty session works correctly.
TEST_F(NavigationManagerSerialisationTest, RestoreFromProto_Empty) {
  proto::NavigationStorage storage;
  storage.set_last_committed_item_index(storage.items_size() - 1);

  // Create a WebState with a real navigation proxy as this is required to
  // perform a session restore and access the view to force instantiation
  // of the WKWebView.
  std::unique_ptr<web::WebStateImpl> web_state =
      std::make_unique<web::WebStateImpl>(
          web::WebState::CreateParams(browser_state()));
  std::ignore = web_state->GetView();

  NavigationManagerImpl& navigation_manager =
      web_state->GetNavigationManagerImpl();

  navigation_manager.RestoreFromProto(storage);

  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
    return navigation_manager.GetItemCount() == 0;
  }));
  EXPECT_EQ(navigation_manager.GetLastCommittedItemIndex(), -1);
}

// Tests that restoring a session works correctly and respect the index
// of the last committed item.
TEST_F(NavigationManagerSerialisationTest, RestoreFromProto_LastItemIndex) {
  proto::NavigationStorage storage;
  storage.set_last_committed_item_index(0);
  for (std::string_view url : kTestURLs) {
    storage.add_items()->set_url(url);
  }
  storage.set_last_committed_item_index(0);

  // Create a WebState with a real navigation proxy as this is required to
  // perform a session restore and access the view to force instantiation
  // of the WKWebView.
  std::unique_ptr<web::WebStateImpl> web_state =
      std::make_unique<web::WebStateImpl>(
          web::WebState::CreateParams(browser_state()));
  std::ignore = web_state->GetView();

  NavigationManagerImpl& navigation_manager =
      web_state->GetNavigationManagerImpl();

  navigation_manager.RestoreFromProto(storage);

  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
    return navigation_manager.GetItemCount() == kTestURLs.size();
  }));
  EXPECT_EQ(navigation_manager.GetLastCommittedItemIndex(), 0);

  for (size_t index = 0; index < kTestURLs.size(); ++index) {
    EXPECT_EQ(navigation_manager.GetItemAtIndex(index)->GetURL(),
              GURL(kTestURLs[index]));
  }
}

// Tests that restoring a session with a specific virtual url which is different
// with url works correctly.
TEST_F(NavigationManagerSerialisationTest, RestoreVirtualURLFromProto) {
  struct UrlAndVirtualUrl {
    std::string_view url;
    std::string_view virtual_url;
  };

  // Tests both HTTP url and non HTTP url.
  static constexpr auto kUrlsAndVirtualURls = std::to_array<UrlAndVirtualUrl>({
      {"http://url.test", "http://virtual.test"},
      {"file:///path/to/file.pdf", "http://virtual.test"},
  });

  proto::NavigationStorage storage;
  storage.set_last_committed_item_index(0);

  for (const auto& url_and_virtual_url : kUrlsAndVirtualURls) {
    proto::NavigationItemStorage* item = storage.add_items();
    item->set_url(url_and_virtual_url.url);
    item->set_virtual_url(url_and_virtual_url.virtual_url);
  }

  // Create a WebState with a real navigation proxy as this is required to
  // perform a session restore and access the view to force instantiation
  // of the WKWebView.
  std::unique_ptr<web::WebStateImpl> web_state =
      std::make_unique<web::WebStateImpl>(
          web::WebState::CreateParams(browser_state()));
  std::ignore = web_state->GetView();

  NavigationManagerImpl& navigation_manager =
      web_state->GetNavigationManagerImpl();

  navigation_manager.RestoreFromProto(storage);

  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
    return navigation_manager.GetItemCount() == kUrlsAndVirtualURls.size();
  }));

  EXPECT_EQ(0, navigation_manager.GetLastCommittedItemIndex());

  for (size_t index = 0; index < kUrlsAndVirtualURls.size(); ++index) {
    NavigationItem* item = navigation_manager.GetItemAtIndex(index);
    GURL url = GURL(kUrlsAndVirtualURls[index].url);
    GURL virtual_url = GURL(kUrlsAndVirtualURls[index].virtual_url);

    EXPECT_EQ(item->GetURL(), url);
    EXPECT_EQ(item->GetVirtualURL(), virtual_url);
  }
}

}  // namespace web
