// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome_frame/test/test_with_web_server.h"

#include "base/base_paths.h"
#include "base/file_version_info.h"
#include "base/path_service.h"
#include "base/stringprintf.h"
#include "base/test/test_timeouts.h"
#include "base/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/installer/util/product.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/helper.h"
#include "chrome_frame/html_utils.h"
#include "chrome_frame/utils.h"
#include "chrome_frame/test/chrome_frame_test_utils.h"
#include "chrome_frame/test/mock_ie_event_sink_actions.h"
#include "chrome_frame/test/mock_ie_event_sink_test.h"
#include "chrome_frame/test/test_scrubber.h"
#include "net/base/mime_util.h"
#include "net/base/stream_listen_socket.h"
#include "net/http/http_util.h"

using chrome_frame_test::kChromeFrameLongNavigationTimeout;
using chrome_frame_test::kChromeFrameVeryLongNavigationTimeout;

using testing::_;
using testing::StrCaseEq;

const wchar_t kDocRoot[] = L"chrome_frame\\test\\data";

namespace {

// Helper method for creating the appropriate HTTP response headers.
std::string CreateHttpHeaders(CFInvocation invocation,
                              bool add_no_cache_header,
                              const std::string& content_type) {
  std::ostringstream ss;
  ss << "HTTP/1.1 200 OK\r\n"
     << "Connection: close\r\n"
     << "Content-Type: " << content_type << "\r\n";
  if (invocation.type() == CFInvocation::HTTP_HEADER)
    ss << "X-UA-Compatible: chrome=1\r\n";
  if (add_no_cache_header) {
    ss << "Cache-Control: no-cache\r\n";
    ss << "Expires: Tue, 15 Nov 1994 08:12:31 GMT\r\n";
  }
  return ss.str();
}

std::string GetMockHttpHeaders(const FilePath& mock_http_headers_path) {
  std::string headers;
  file_util::ReadFileToString(mock_http_headers_path, &headers);
  return headers;
}

class ChromeFrameTestEnvironment: public testing::Environment {
 public:
  virtual ~ChromeFrameTestEnvironment() {}
  virtual void SetUp() OVERRIDE {
    ScopedChromeFrameRegistrar::RegisterDefaults();
  }
};

::testing::Environment* const chrome_frame_env =
    ::testing::AddGlobalTestEnvironment(new ChromeFrameTestEnvironment);

}  // namespace

FilePath ChromeFrameTestWithWebServer::test_file_path_;
FilePath ChromeFrameTestWithWebServer::results_dir_;
FilePath ChromeFrameTestWithWebServer::CFInstall_path_;
FilePath ChromeFrameTestWithWebServer::CFInstance_path_;
ScopedTempDir ChromeFrameTestWithWebServer::temp_dir_;
FilePath ChromeFrameTestWithWebServer::chrome_user_data_dir_;
chrome_frame_test::TimedMsgLoop* ChromeFrameTestWithWebServer::loop_;
std::string ChromeFrameTestWithWebServer::local_address_;
testing::StrictMock<MockWebServerListener>*
    ChromeFrameTestWithWebServer::listener_mock_;
testing::StrictMock<MockWebServer>* ChromeFrameTestWithWebServer::server_mock_;

ChromeFrameTestWithWebServer::ChromeFrameTestWithWebServer() {
}

// static
void ChromeFrameTestWithWebServer::SetUpTestCase() {
  FilePath chrome_frame_source_path;
  PathService::Get(base::DIR_SOURCE_ROOT, &chrome_frame_source_path);
  chrome_frame_source_path = chrome_frame_source_path.Append(
      FILE_PATH_LITERAL("chrome_frame"));

  test_file_path_ = chrome_frame_source_path
      .Append(FILE_PATH_LITERAL("test"))
      .Append(FILE_PATH_LITERAL("data"));

  results_dir_ = chrome_frame_test::GetTestDataFolder().AppendASCII("dump");

  // Copy the CFInstance.js and CFInstall.js files from src\chrome_frame to
  // src\chrome_frame\test\data.
  FilePath CFInstance_src_path;
  FilePath CFInstall_src_path;

  CFInstance_src_path = chrome_frame_source_path.AppendASCII("CFInstance.js");
  CFInstance_path_ = test_file_path_.AppendASCII("CFInstance.js");

  ASSERT_TRUE(file_util::CopyFile(CFInstance_src_path, CFInstance_path_));

  CFInstall_src_path = chrome_frame_source_path.AppendASCII("CFInstall.js");
  CFInstall_path_ = test_file_path_.AppendASCII("CFInstall.js");

  ASSERT_TRUE(file_util::CopyFile(CFInstall_src_path, CFInstall_path_));

  loop_ = new chrome_frame_test::TimedMsgLoop();
  loop_->set_snapshot_on_timeout(true);
  local_address_ = chrome_frame_test::GetLocalIPv4Address();
  listener_mock_ = new testing::StrictMock<MockWebServerListener>();
  server_mock_ = new testing::StrictMock<MockWebServer>(
      1337, ASCIIToWide(local_address_),
      chrome_frame_test::GetTestDataFolder());
  server_mock_->set_listener(listener_mock_);
}

// static
void ChromeFrameTestWithWebServer::TearDownTestCase() {
  delete server_mock_;
  server_mock_ = NULL;
  delete listener_mock_;
  listener_mock_ = NULL;
  local_address_.clear();
  delete loop_;
  loop_ = NULL;
  file_util::Delete(CFInstall_path_, false);
  file_util::Delete(CFInstance_path_, false);
  if (temp_dir_.IsValid())
    EXPECT_TRUE(temp_dir_.Delete());
}

// static
const FilePath& ChromeFrameTestWithWebServer::GetChromeUserDataDirectory() {
  if (!temp_dir_.IsValid()) {
    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
    chrome_user_data_dir_ = temp_dir_.path().AppendASCII("User Data");
  }
  return chrome_user_data_dir_;
}

void ChromeFrameTestWithWebServer::SetUp() {
  // Make sure that we are not accidentally enabling gcf protocol.
  SetConfigBool(kAllowUnsafeURLs, false);

  server_mock().ClearResults();
  server_mock().ExpectAndServeAnyRequests(CFInvocation(CFInvocation::NONE));
  server_mock().set_expected_result("OK");
}

void ChromeFrameTestWithWebServer::TearDown() {
  CloseBrowser();
  loop().RunAllPending();
  testing::Mock::VerifyAndClear(listener_mock_);
  testing::Mock::VerifyAndClear(server_mock_);
}

bool ChromeFrameTestWithWebServer::LaunchBrowser(BrowserKind browser,
                                                 const wchar_t* page) {
  std::wstring url = page;

  // We should resolve the URL only if it is a relative url.
  GURL parsed_url(WideToUTF8(page));
  if (!parsed_url.has_scheme()) {
    url = server_mock().Resolve(page);
  }

  browser_ = browser;
  if (browser == IE) {
    browser_handle_.Set(chrome_frame_test::LaunchIE(url));
  } else if (browser == CHROME) {
    const FilePath& user_data_dir = GetChromeUserDataDirectory();
    chrome_frame_test::OverrideDataDirectoryForThisTest(user_data_dir.value());
    browser_handle_.Set(chrome_frame_test::LaunchChrome(url, user_data_dir));
  } else {
    NOTREACHED();
  }

  return browser_handle_.IsValid();
}

void ChromeFrameTestWithWebServer::CloseBrowser() {
  if (!browser_handle_.IsValid())
    return;

  int attempts = 0;
  if (browser_ == IE) {
    attempts = chrome_frame_test::CloseAllIEWindows();
  } else {
    attempts = chrome_frame_test::CloseVisibleWindowsOnAllThreads(
                                                               browser_handle_);
  }

  if (attempts > 0) {
    DWORD wait = ::WaitForSingleObject(browser_handle_, 20000);
    if (wait == WAIT_OBJECT_0) {
      browser_handle_.Close();
    } else {
      LOG(ERROR) << "WaitForSingleObject returned " << wait;
    }
  } else {
    LOG(ERROR) << "No attempts to close browser windows";
  }

  if (browser_handle_.IsValid()) {
    DWORD exit_code = 0;
    if (!::GetExitCodeProcess(browser_handle_, &exit_code) ||
        exit_code == STILL_ACTIVE) {
      LOG(ERROR) << L"Forcefully killing browser process. Exit:" << exit_code;
      base::KillProcess(browser_handle_, 0, true);
    }
    browser_handle_.Close();
  }
}

bool ChromeFrameTestWithWebServer::BringBrowserToTop() {
  return simulate_input::EnsureProcessInForeground(
      GetProcessId(browser_handle_));
}

bool ChromeFrameTestWithWebServer::WaitForTestToComplete(
    base::TimeDelta duration) {
  loop().RunFor(duration);
  return !loop().WasTimedOut();
}

bool ChromeFrameTestWithWebServer::WaitForOnLoad(int milliseconds) {
  return false;
}

const wchar_t kPostedResultSubstring[] = L"/writefile/";

void ChromeFrameTestWithWebServer::SimpleBrowserTestExpectedResult(
    BrowserKind browser, const wchar_t* page, const char* result) {
  int tries = 0;
  ExpectAndHandlePostedResult();
  // Retry tests that timeout once; see http://crbug.com/96449.
  do {
    // NOTE: Failed ASSERTs cause this function to exit immediately.
    // Don't take a snapshot on the first try.
    loop().set_snapshot_on_timeout(tries != 0);
    ASSERT_TRUE(LaunchBrowser(browser, page));
    if (WaitForTestToComplete(TestTimeouts::action_max_timeout())) {
      // The test exited without timing out.  Confirm that the expected response
      // was posted and return.
      ASSERT_EQ(result, server_mock().posted_result());
      break;
    }
    ASSERT_EQ(std::string(), server_mock().posted_result())
        << "Test timed out yet provided a result.";
    ASSERT_EQ(0, tries++) << "Failing test due to two timeouts.";
    // Close the browser and try a second time.
    CloseBrowser();
    LOG(ERROR) << "Retrying test once since it timed out.";
  } while (true);
  loop().set_snapshot_on_timeout(true);
}

void ChromeFrameTestWithWebServer::SimpleBrowserTest(BrowserKind browser,
    const wchar_t* page) {
  SimpleBrowserTestExpectedResult(browser, page, "OK");
}

void ChromeFrameTestWithWebServer::ExpectAndHandlePostedResult() {
  EXPECT_CALL(listener_mock(), OnExpectedResponse())
      .WillRepeatedly(QUIT_LOOP_SOON(loop(),
                                     base::TimeDelta::FromMilliseconds(100)));
  server_mock().ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE),
                                            kPostedResultSubstring);
}

void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser,
    const wchar_t* page) {
  FilePath plugin_path;
  PathService::Get(base::DIR_MODULE, &plugin_path);
  plugin_path = plugin_path.Append(kChromeFrameDllName);

  static FileVersionInfo* version_info =
      FileVersionInfo::CreateFileVersionInfo(plugin_path);

  std::wstring version;
  if (version_info)
    version = version_info->product_version();

  // If we can't find the Chrome Frame DLL in the src tree, we turn to
  // the directory where chrome is installed.
  if (!version_info) {
    BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    Version ver_system;
    InstallUtil::GetChromeVersion(dist, true, &ver_system);
    Version ver_user;
    InstallUtil::GetChromeVersion(dist, false, &ver_system);
    ASSERT_TRUE(ver_system.IsValid() || ver_user.IsValid());

    bool system_install = ver_system.IsValid();
    FilePath cf_dll_path(installer::GetChromeInstallPath(system_install, dist));
    cf_dll_path = cf_dll_path.Append(UTF8ToWide(
        ver_system.IsValid() ? ver_system.GetString() : ver_user.GetString()));
    cf_dll_path = cf_dll_path.Append(kChromeFrameDllName);
    version_info = FileVersionInfo::CreateFileVersionInfo(cf_dll_path);
    if (version_info)
      version = version_info->product_version();
  }

  server_mock().set_expected_result(WideToUTF8(version));

  EXPECT_TRUE(version_info);
  EXPECT_FALSE(version.empty());

  SimpleBrowserTestExpectedResult(browser, page, WideToASCII(version).c_str());
}

// MockWebServer methods
void MockWebServer::ExpectAndServeRequest(CFInvocation invocation,
                                          const std::wstring& url) {
  ExpectAndServeRequestWithCardinality(invocation, url, testing::Exactly(1));
}

void MockWebServer::ExpectAndServeRequestWithCardinality(
    CFInvocation invocation, const std::wstring& url,
    testing::Cardinality cardinality) {
  EXPECT_CALL(*this, Get(_, chrome_frame_test::UrlPathEq(url), _))
      .Times(cardinality)
      .WillRepeatedly(SendResponse(this, invocation));
}

void MockWebServer::ExpectAndServeRequestAllowCache(CFInvocation invocation,
                                                    const std::wstring &url) {
  EXPECT_CALL(*this, Get(_, chrome_frame_test::UrlPathEq(url), _))
      .WillOnce(SendResponse(this, invocation));
}

void MockWebServer::ExpectAndServeRequestAnyNumberTimes(
    CFInvocation invocation, const std::wstring& path_prefix) {
  EXPECT_CALL(*this, Get(_, testing::StartsWith(path_prefix), _))
      .WillRepeatedly(SendResponse(this, invocation));
}

void MockWebServer::ExpectAndHandlePostedResult(
    CFInvocation invocation, const std::wstring& post_suffix) {
  EXPECT_CALL(*this, Post(_, testing::HasSubstr(post_suffix), _))
      .WillRepeatedly(HandlePostedResponseHelper(this, invocation));
}

void MockWebServer::HandlePostedResponse(
    test_server::ConfigurableConnection* connection,
    const test_server::Request& request) {
  posted_result_ = request.content();
  if (listener_ && posted_result_ == expected_result_)
    listener_->OnExpectedResponse();
  connection->Send("HTTP/1.1 200 OK\r\n", "");
}

void MockWebServer::SendResponseHelper(
    test_server::ConfigurableConnection* connection,
    const std::wstring& request_uri,
    const test_server::Request& request,
    CFInvocation invocation,
    bool add_no_cache_header) {
  static const wchar_t kEchoHeader[] = L"/echoheader?";
  if (request_uri.find(kEchoHeader) != std::wstring::npos) {
    std::wstring header = request_uri.substr(
        wcslen(kEchoHeader),
        request_uri.length() - wcslen(kEchoHeader));

    std::string header_value = http_utils::GetHttpHeaderFromHeaderList(
        WideToUTF8(header), request.headers());
    connection->Send(header_value, "");
    return;
  }
  // Convert |request_uri| to a path.
  std::wstring path = request_uri;
  size_t query_index = request_uri.find(L"?");
  if (query_index != std::string::npos) {
    path = path.erase(query_index);
  }
  FilePath file_path = root_dir_;
  if (path.size())
    file_path = file_path.Append(path.substr(1));  // remove first '/'

  std::string headers, body;
  std::string content_type;
  if (file_util::PathExists(file_path) &&
      !file_util::DirectoryExists(file_path)) {
    FilePath mock_http_headers(file_path.value() + L".mock-http-headers");
    if (file_util::PathExists(mock_http_headers)) {
      headers = GetMockHttpHeaders(mock_http_headers);
      content_type = http_utils::GetHttpHeaderFromHeaderList("Content-type",
                                                             headers);
    } else {
      EXPECT_TRUE(net::GetMimeTypeFromFile(file_path, &content_type));
      VLOG(1) << "Going to send file (" << WideToUTF8(file_path.value())
              << ") with content type (" << content_type << ")";
      headers = CreateHttpHeaders(invocation, add_no_cache_header,
                                  content_type);
    }

    EXPECT_FALSE(headers.empty());

    EXPECT_TRUE(file_util::ReadFileToString(file_path, &body))
        << "Could not read file (" << WideToUTF8(file_path.value()) << ")";
    if (invocation.type() == CFInvocation::META_TAG &&
        StartsWithASCII(content_type, "text/html", false)) {
      EXPECT_TRUE(chrome_frame_test::AddCFMetaTag(&body)) << "Could not add "
          << "meta tag to HTML file.";
    }
  } else {
    VLOG(1) << "Going to send 404 for non-existent file ("
            << WideToUTF8(file_path.value()) << ")";
    headers = "HTTP/1.1 404 Not Found";
    body = "";
  }
  connection->Send(headers, body);
}

const wchar_t kPostMessageBasicPage[] = L"postmessage_basic_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PostMessageBasic) {
  SimpleBrowserTest(IE, kPostMessageBasicPage);
}

TEST_F(ChromeFrameTestWithWebServer, FullTabIE_MIMEFilterBasic) {
  const wchar_t kMIMEFilterBasicPage[] =
      L"chrome_frame_mime_filter_test.html";

  // If this test fails for IE8 then it is possible that prebinding is enabled.
  // A known workaround is to disable it until CF properly handles it.
  //
  // HKCU\Software\Microsoft\Internet Explorer\Main
  // Value name: EnablePreBinding (REG_DWORD)
  // Value: 0
  SimpleBrowserTest(IE, kMIMEFilterBasicPage);
}

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Resize) {
  SimpleBrowserTest(IE, L"chrome_frame_resize.html");
}

const wchar_t kNavigateURLAbsolutePage[] =
    L"navigateurl_absolute_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLAbsolute) {
  SimpleBrowserTest(IE, kNavigateURLAbsolutePage);
}

const wchar_t kNavigateURLRelativePage[] =
    L"navigateurl_relative_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLRelative) {
  SimpleBrowserTest(IE, kNavigateURLRelativePage);
}

const wchar_t kNavigateSimpleObjectFocus[] = L"simple_object_focus.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_ObjectFocus) {
  SimpleBrowserTest(IE, kNavigateSimpleObjectFocus);
}

const wchar_t kiframeBasicPage[] = L"iframe_basic_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_iframeBasic) {
  SimpleBrowserTest(IE, kiframeBasicPage);
}

const wchar_t kSrcPropertyTestPage[] = L"src_property_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_SrcProperty) {
  SimpleBrowserTest(IE, kSrcPropertyTestPage);
}

const wchar_t kCFInstanceBasicTestPage[] = L"CFInstance_basic_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceBasic) {
  SimpleBrowserTest(IE, kCFInstanceBasicTestPage);
}

const wchar_t kCFISingletonPage[] = L"CFInstance_singleton_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceSingleton) {
  SimpleBrowserTest(IE, kCFISingletonPage);
}

const wchar_t kCFIDelayPage[] = L"CFInstance_delay_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceDelay) {
  SimpleBrowserTest(IE, kCFIDelayPage);
}

const wchar_t kCFIFallbackPage[] = L"CFInstance_fallback_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceFallback) {
  SimpleBrowserTest(IE, kCFIFallbackPage);
}

const wchar_t kCFINoSrcPage[] = L"CFInstance_no_src_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceNoSrc) {
  SimpleBrowserTest(IE, kCFINoSrcPage);
}

const wchar_t kCFIIfrOnLoadPage[] = L"CFInstance_iframe_onload_host.html";

// disabled since it's unlikely that we care about this case
TEST_F(ChromeFrameTestWithWebServer,
       DISABLED_WidgetModeIE_CFInstanceIfrOnLoad) {
  SimpleBrowserTest(IE, kCFIIfrOnLoadPage);
}

const wchar_t kCFIZeroSizePage[] = L"CFInstance_zero_size_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceZeroSize) {
  SimpleBrowserTest(IE, kCFIZeroSizePage);
}

const wchar_t kCFIIfrPostPage[] = L"CFInstance_iframe_post_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceIfrPost) {
  SimpleBrowserTest(IE, kCFIIfrPostPage);
}

TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceIfrPost) {
  SimpleBrowserTest(CHROME, kCFIIfrPostPage);
}

const wchar_t kCFIPostPage[] = L"CFInstance_post_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstancePost) {
  if (chrome_frame_test::GetInstalledIEVersion() == IE_9) {
    LOG(INFO) << "Not running test on Vista/Windows 7 with IE9";
    return;
  }
  SimpleBrowserTest(IE, kCFIPostPage);
}

// This test randomly fails on the ChromeFrame builder.
TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstancePost) {
  SimpleBrowserTest(CHROME, kCFIPostPage);
}

const wchar_t kCFIRPCPage[] = L"CFInstance_rpc_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPC) {
  if (chrome_frame_test::GetInstalledIEVersion() == IE_9) {
    LOG(INFO) << "Not running test on Vista/Windows 7 with IE9";
    return;
  }
  SimpleBrowserTest(IE, kCFIRPCPage);
}

TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPC) {
  SimpleBrowserTest(CHROME, kCFIRPCPage);
}

const wchar_t kCFIRPCInternalPage[] =
    L"CFInstance_rpc_internal_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPCInternal) {
  if (chrome_frame_test::GetInstalledIEVersion() == IE_9) {
    LOG(INFO) << "Not running test on Vista/Windows 7 with IE9";
    return;
  }
  SimpleBrowserTest(IE, kCFIRPCInternalPage);
}

TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPCInternal) {
  SimpleBrowserTest(CHROME, kCFIRPCInternalPage);
}

const wchar_t kCFIDefaultCtorPage[] =
    L"CFInstance_default_ctor_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceDefaultCtor) {
  SimpleBrowserTest(IE, kCFIDefaultCtorPage);
}

const wchar_t kCFInstallBasicTestPage[] = L"CFInstall_basic.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallBasic) {
  SimpleBrowserTest(IE, kCFInstallBasicTestPage);
}

const wchar_t kCFInstallPlaceTestPage[] = L"CFInstall_place.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallPlace) {
  SimpleBrowserTest(IE, kCFInstallPlaceTestPage);
}

const wchar_t kCFInstallOverlayTestPage[] = L"CFInstall_overlay.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallOverlay) {
  SimpleBrowserTest(IE, kCFInstallOverlayTestPage);
}

const wchar_t kCFInstallDismissTestPage[] = L"CFInstall_dismiss.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabIE_CFInstallDismiss) {
  SimpleBrowserTest(IE, kCFInstallDismissTestPage);
}

const wchar_t kInitializeHiddenPage[] = L"initialize_hidden.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_InitializeHidden) {
  SimpleBrowserTest(IE, kInitializeHiddenPage);
}

const wchar_t kFullTabHttpHeaderPage[] = L"chrome_frame_http_header.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFHttpHeaderBasic) {
  SimpleBrowserTest(IE, kFullTabHttpHeaderPage);
}

const wchar_t kFullTabHttpHeaderPageIFrame[] =
    L"chrome_frame_http_header_host.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFHttpHeaderIFrame) {
  SimpleBrowserTest(IE, kFullTabHttpHeaderPageIFrame);
}

const wchar_t kFullTabHttpHeaderPageFrameset[] =
    L"chrome_frame_http_header_frameset.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFHttpHeaderFrameSet) {
  SimpleBrowserTest(IE, kFullTabHttpHeaderPageFrameset);
}

const wchar_t kVersionPage[] = L"version.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Version) {
  VersionTest(IE, kVersionPage);
}

const wchar_t kEventListenerPage[] = L"event_listener.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_EventListener) {
  SimpleBrowserTest(IE, kEventListenerPage);
}

const wchar_t kPrivilegedApisPage[] = L"privileged_apis_host.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PrivilegedApis) {
  SimpleBrowserTest(IE, kPrivilegedApisPage);
}

const wchar_t kMetaTagPage[] = L"meta_tag.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_MetaTag) {
  SimpleBrowserTest(IE, kMetaTagPage);
}

const wchar_t kCFProtocolPage[] = L"cf_protocol.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFProtocol) {
  // Temporarily enable  gcf: protocol for this test.
  SetConfigBool(kAllowUnsafeURLs, true);
  SimpleBrowserTest(IE, kCFProtocolPage);
  SetConfigBool(kAllowUnsafeURLs, false);
}

const wchar_t kPersistentCookieTest[] =
    L"persistent_cookie_test_page.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_PersistentCookieTest) {
  SimpleBrowserTest(IE, kPersistentCookieTest);
}

const wchar_t kNavigateOutPage[] = L"navigate_out.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_NavigateOut) {
  SimpleBrowserTest(IE, kNavigateOutPage);
}

const wchar_t kReferrerMainTest[] = L"referrer_main.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_ReferrerTest) {
  SimpleBrowserTest(IE, kReferrerMainTest);
}

const wchar_t kSubFrameTestPage[] = L"full_tab_sub_frame_main.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_SubFrame) {
  SimpleBrowserTest(IE, kSubFrameTestPage);
}

const wchar_t kSubIFrameTestPage[] = L"full_tab_sub_iframe_main.html";
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_SubIFrame) {
  SimpleBrowserTest(IE, kSubIFrameTestPage);
}

const wchar_t kXMLHttpRequestTestUrl[] =
    L"xmlhttprequest_test.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_XHRTest) {
  SimpleBrowserTest(IE, kXMLHttpRequestTestUrl);
}

const wchar_t kInstallFlowTestUrl[] =
    L"install_flow_test.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_InstallFlowTest) {
  if (base::win::GetVersion() < base::win::VERSION_VISTA) {
    ScopedChromeFrameRegistrar::UnregisterAtPath(
        GetChromeFrameBuildPath().value(),
        chrome_frame_test::GetTestBedType());

    ASSERT_TRUE(LaunchBrowser(IE, kInstallFlowTestUrl));

    loop().RunFor(kChromeFrameLongNavigationTimeout);

    ScopedChromeFrameRegistrar::RegisterAtPath(
        GetChromeFrameBuildPath().value(),
        chrome_frame_test::GetTestBedType());

    ExpectAndHandlePostedResult();
    loop().RunFor(kChromeFrameLongNavigationTimeout);

    chrome_frame_test::CloseAllIEWindows();
    ASSERT_EQ("OK", server_mock().posted_result());
  }
}

const wchar_t kMultipleCFInstancesTestUrl[] =
    L"multiple_cf_instances_main.html";

TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_MultipleCFInstances) {
  SimpleBrowserTest(IE, kMultipleCFInstancesTestUrl);
}

const wchar_t kXHRHeaderTestUrl[] =
    L"xmlhttprequest_header_test.html";

// Marking as flaky since it occasionally times out. crbug.com/127395.
TEST_F(ChromeFrameTestWithWebServer, FLAKY_FullTabModeIE_XHRHeaderTest) {
  SimpleBrowserTest(IE, kXHRHeaderTestUrl);
}

const wchar_t kDeleteCookieTest[] =
    L"fulltab_delete_cookie_test.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_DeleteCookieTest) {
  SimpleBrowserTest(IE, kDeleteCookieTest);
}

const wchar_t kAnchorUrlNavigate[] =
    L"fulltab_anchor_url_navigate.html#chrome_frame";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_AnchorUrlNavigateTest) {
  SimpleBrowserTest(IE, kAnchorUrlNavigate);
}

// Test whether POST-ing a form from an mshtml page to a CF page will cause
// the request to get reissued.  It should not.
// https://code.google.com/p/chromium/issues/detail?id=143699
TEST_F(ChromeFrameTestWithWebServer, FLAKY_FullTabModeIE_TestPostReissue) {
  // The order of pages in this array is assumed to be mshtml, cf, script.
  const wchar_t* kPages[] = {
    L"full_tab_post_mshtml.html",
    L"full_tab_post_target_cf.html",
    L"chrome_frame_tester_helpers.js",
  };

  SimpleWebServerTest server(local_address_, 46664);
  server.PopulateStaticFileListT<test_server::FileResponse>(kPages,
      arraysize(kPages), GetCFTestFilePath());

  ASSERT_TRUE(LaunchBrowser(IE, server.FormatHttpPath(kPages[0]).c_str()));

  loop().RunFor(kChromeFrameLongNavigationTimeout);

  const test_server::Request* request = NULL;
  server.FindRequest("/quit?OK", &request);
  ASSERT_TRUE(request != NULL);
  EXPECT_EQ("OK", request->arguments());

  if (request->arguments().compare("OK") == 0) {
    // Check how many requests we got for the cf page.  Also expect it to be
    // a POST.
    int requests = server.GetRequestCountForPage(kPages[1], "POST");
    EXPECT_EQ(1, requests);
  }
}

// Test whether following a link from an mshtml page to a CF page will cause
// multiple network requests.  It should not.
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_TestMultipleGet) {
  // The order of pages in this array is assumed to be mshtml, cf, script.
  const wchar_t* kPages[] = {
    L"full_tab_get_mshtml.html",
    L"full_tab_get_target_cf.html",
    L"chrome_frame_tester_helpers.js",
  };

  SimpleWebServerTest server(local_address_, 46664);

  server.PopulateStaticFileListT<test_server::FileResponse>(kPages,
      arraysize(kPages), GetCFTestFilePath());

  ASSERT_TRUE(LaunchBrowser(IE, server.FormatHttpPath(kPages[0]).c_str()));

  loop().RunFor(kChromeFrameVeryLongNavigationTimeout);

  const test_server::Request* request = NULL;
  server.FindRequest("/quit?OK", &request);
  ASSERT_TRUE(request != NULL);
  EXPECT_EQ("OK", request->arguments());

  if (request->arguments().compare("OK") == 0) {
    // Check how many requests we got for the cf page and check that it was
    // a GET.
    int requests = server.GetRequestCountForPage(kPages[1], "GET");
    EXPECT_EQ(1, requests);
  }
}

const wchar_t kSetCookieTest[] =
    L"fulltab_set_cookie_test.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_SetCookieTest) {
  SimpleBrowserTest(IE, kSetCookieTest);
}

const wchar_t kXHRConditionalHeaderTestUrl[] =
    L"xmlhttprequest_conditional_header_test.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_XHRConditionalHeaderTest) {
  SimpleBrowserTest(IE, kXHRConditionalHeaderTestUrl);
}

const wchar_t kWindowCloseTestUrl[] =
    L"window_close.html";

TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_WindowClose) {
  SimpleBrowserTest(IE, kWindowCloseTestUrl);
}

std::string GetHeaderValue(const std::string& headers,
                           const char* header_name) {
  net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(),
                                    "\r\n");
  while (it.GetNext()) {
    if (lstrcmpiA(it.name().c_str(), header_name) == 0) {
      return it.values();
    }
  }
  return "";
}

// Specialized implementation of test_server::FileResponse that supports
// adding the request's User-Agent header to the returned document.
// The class also supports $request_id$ which will be replaced with an
// id that's incremented each time the response is sent over a socket.
class UaTemplateFileResponse : public test_server::FileResponse {
 public:
  typedef test_server::FileResponse SuperClass;

  UaTemplateFileResponse(const char* request_path, const FilePath& file_path)
      : test_server::FileResponse(request_path, file_path), request_id_(0) {
  }

  virtual bool Matches(const test_server::Request& r) const {
    bool ret = SuperClass::Matches(r);
    if (ret)
      ua_ = GetHeaderValue(r.headers(), "User-Agent");
    return ret;
  }

  virtual size_t ContentLength() const {
    const char kRequestIdTemplate[] = "$request_id$";
    const char kUserAgentTemplate[] = "$UA$";

    size_t length = SuperClass::ContentLength();
    DCHECK(length);
    content_.assign(reinterpret_cast<const char*>(file_->data()),
                    file_->length());
    size_t i = content_.find(kUserAgentTemplate);
    if (i != std::string::npos)
      content_.replace(i, arraysize(kUserAgentTemplate) - 1, ua_);
    i = content_.find(kRequestIdTemplate);
    if (i != std::string::npos) {
      content_.replace(i, arraysize(kRequestIdTemplate) - 1,
                       base::StringPrintf("%i", request_id_));
    }
    return content_.length();
  }

  virtual void WriteContents(net::StreamListenSocket* socket) const {
    DCHECK(content_.length());
    socket->Send(content_.c_str(), content_.length(), false);
    request_id_++;
  }

 protected:
  mutable std::string ua_;
  mutable std::string content_;
  mutable int request_id_;
};

// This test simulates a URL that on first request returns a document
// that should be rendered in mshtml, then pops up a sign-in page that
// after signing in, refreshes the original page that should then return
// a page that needs to be rendered in GCF.
//
// This test currently fails because GCF does not add the chromeframe header
// to requests that mshtml initiates via IInternetSession::CreateBinding.
TEST_F(ChromeFrameTestWithWebServer, FAILS_FullTabModeIE_RefreshMshtmlTest) {
  const wchar_t* kPages[] = {
    L"mshtml_refresh_test.html",
    L"mshtml_refresh_test_popup.html",
  };

  SimpleWebServerTest server(local_address_, 46664);
  server.PopulateStaticFileListT<UaTemplateFileResponse>(kPages,
      arraysize(kPages), GetCFTestFilePath());

  ASSERT_TRUE(LaunchBrowser(IE, server.FormatHttpPath(kPages[0]).c_str()));

  loop().RunFor(kChromeFrameLongNavigationTimeout);

  test_server::SimpleWebServer* ws = server.web_server();
  const test_server::ConnectionList& connections = ws->connections();
  test_server::ConnectionList::const_iterator it = connections.begin();
  int requests_for_first_page = 0;
  for (; it != connections.end(); ++it) {
    test_server::Connection* c = (*it);
    const test_server::Request& r = c->request();
    if (!r.path().empty() &&
        ASCIIToWide(r.path().substr(1)).compare(kPages[0]) == 0) {
      requests_for_first_page++;
      std::string ua(GetHeaderValue(r.headers(), "User-Agent"));
      EXPECT_NE(std::string::npos, ua.find("chromeframe"));
    }
  }
  EXPECT_GT(requests_for_first_page, 1);
}

// See bug 36694 for details.  http://crbug.com/36694
TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_TestDownloadFromForm) {
  chrome_frame_test::MockWindowObserver win_observer_mock;
  win_observer_mock.WatchWindow("File Download", "");
  win_observer_mock.WatchWindow("View Downloads*", "");

  // The content of our HTML test page.  This will be returned whenever
  // we reply to a GET request.
  static const char kHtml[] =
      "<html><head>\n"
      "<title>ChromeFrame Form Download Test</title>\n"
      // To see how this test runs with only IE (no CF in the picture), comment
      // out this meta tag.  The outcome of the test should be identical.
      "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />\n"
      "</head>\n"
      "<script language=\"javascript\">\n"
      "function SubmitForm() {\n"
      "  var form = document.forms['myform'];\n"
      "  form.action = document.location;\n"
      "  form.submit();\n"
      "  return true;\n"
      "}\n"
      "</script>\n"
      "<body onload=\"SubmitForm();\">\n"
      "<form method=\"post\" action=\"foo.html\" id=\"myform\">\n"
      "  <input type=\"hidden\" name=\"Field1\" value=\"myvalue\" />\n"
      "  <input type=\"button\" name=\"btn\" value=\"Test Download\" "
          "onclick=\"return SubmitForm();\" id=\"Button1\"/>\n"
      "</form></body></html>\n";

  // The content of our HTML test page.  This will be returned whenever
  // we reply to a POST request.
  static const char kText[] =
      "This is a text file (in case you were wondering).";

  // This http response class will return an HTML document that contains
  // a form whenever it receives a GET request.  Whenever it gets a POST
  // request, it will respond with a text file that needs to be downloaded
  // (content-disposition is "attachment").
  class CustomResponse : public test_server::ResponseForPath {
   public:
    explicit CustomResponse(const char* path)
      : test_server::ResponseForPath(path), is_post_(false),
        post_requests_(0), get_requests_(0) {
    }

    virtual bool GetContentType(std::string* content_type) const {
      DCHECK(!is_post_);
      return false;
    }

    virtual size_t ContentLength() const {
      DCHECK(!is_post_);
      return sizeof(kHtml) - 1;
    }

    virtual bool GetCustomHeaders(std::string* headers) const {
      if (!is_post_)
        return false;
      *headers = StringPrintf(
          "HTTP/1.1 200 OK\r\n"
          "Content-Disposition: attachment;filename=\"test.txt\"\r\n"
          "Content-Type: application/text\r\n"
          "Connection: close\r\n"
          "Content-Length: %i\r\n\r\n", sizeof(kText) - 1);
      return true;
    }

    virtual bool Matches(const test_server::Request& r) const {
      bool match = __super::Matches(r);
      if (match) {
        is_post_ = LowerCaseEqualsASCII(r.method().c_str(), "post");
      }
      return match;
    }

    virtual void WriteContents(net::StreamListenSocket* socket) const {
      if (is_post_) {
        socket->Send(kText, sizeof(kText) - 1, false);
      } else {
        socket->Send(kHtml, sizeof(kHtml) - 1, false);
      }
    }

    virtual void IncrementAccessCounter() {
      __super::IncrementAccessCounter();
      if (is_post_) {
        post_requests_++;
      } else {
        get_requests_++;
      }
    }

    size_t get_request_count() const {
      return get_requests_;
    }

    size_t post_request_count() const {
      return get_requests_;
    }

   protected:
    mutable bool is_post_;
    size_t post_requests_;
    size_t get_requests_;
  };

  EXPECT_CALL(win_observer_mock, OnWindowOpen(_))
      .Times(testing::AtMost(1))
      .WillOnce(chrome_frame_test::DoCloseWindow());

  EXPECT_CALL(win_observer_mock, OnWindowClose(_))
      .Times(testing::AtMost(1))
      .WillOnce(QUIT_LOOP(loop()));

  SimpleWebServerTest server(local_address_, 46664);
  CustomResponse* response = new CustomResponse("/form.html");
  server.web_server()->AddResponse(response);

  std::wstring url(server.FormatHttpPath(L"form.html"));

  ASSERT_TRUE(LaunchBrowser(IE, url.c_str()));
  loop().RunFor(kChromeFrameLongNavigationTimeout);

  EXPECT_EQ(1, response->get_request_count());
  EXPECT_EQ(1, response->post_request_count());
}