text: // 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/net/fake_external_tab.h"
  
  #include <atlbase.h>
  #include <atlcom.h>
  #include <exdisp.h>
  #include <Winsock2.h>
  
  #include "base/bind.h"
  #include "base/command_line.h"
  #include "base/debug/debugger.h"
  #include "base/file_util.h"
  #include "base/file_version_info.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/i18n/icu_util.h"
  #include "base/lazy_instance.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/path_service.h"
  #include "base/prefs/json_pref_store.h"
  #include "base/prefs/pref_registry_simple.h"
  #include "base/prefs/pref_service.h"
  #include "base/strings/string_piece.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/system_monitor/system_monitor.h"
  #include "base/test/test_timeouts.h"
  #include "base/threading/platform_thread.h"
  #include "base/threading/thread.h"
  #include "base/win/scoped_comptr.h"
  #include "base/win/scoped_handle.h"
  #include "chrome/app/chrome_main_delegate.h"
  #include "chrome/browser/automation/automation_provider_list.h"
  #include "chrome/browser/chrome_content_browser_client.h"
  #include "chrome/browser/prefs/browser_prefs.h"
  #include "chrome/browser/prefs/proxy_config_dictionary.h"
  #include "chrome/browser/process_singleton.h"
  #include "chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.h"
  #include "chrome/browser/profiles/profile_manager.h"
  #include "chrome/browser/profiles/profiles_state.h"
  #include "chrome/browser/renderer_host/web_cache_manager.h"
  #include "chrome/common/chrome_constants.h"
  #include "chrome/common/chrome_content_client.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/chrome_paths_internal.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/renderer/chrome_content_renderer_client.h"
  #include "chrome/test/base/ui_test_utils.h"
  #include "chrome/test/logging/win/file_logger.h"
  #include "chrome/test/logging/win/log_file_printer.h"
  #include "chrome/test/logging/win/test_log_collector.h"
  #include "chrome_frame/crash_server_init.h"
  #include "chrome_frame/test/chrome_frame_test_utils.h"
  #include "chrome_frame/test/ie_configurator.h"
  #include "chrome_frame/test/net/test_automation_resource_message_filter.h"
  #include "chrome_frame/test/simulate_input.h"
  #include "chrome_frame/test/win_event_receiver.h"
  #include "chrome_frame/utils.h"
  #include "content/public/app/content_main.h"
  #include "content/public/app/startup_helper_win.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/notification_service.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/common/content_client.h"
  #include "content/public/common/content_paths.h"
  #include "net/base/net_util.h"
  #include "net/url_request/url_request_test_util.h"
  #include "sandbox/win/src/sandbox_types.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "ui/base/resource/resource_bundle.h"
  #include "ui/base/resource/resource_bundle_win.h"
  #include "ui/base/ui_base_paths.h"
  
  #if defined(USE_AURA)
  #include "ui/aura/env.h"
  #include "ui/gfx/screen.h"
  #include "ui/views/widget/desktop_aura/desktop_screen.h"
  #endif
  
  using content::BrowserThread;
  
  namespace {
  
  // We must store this globally so that our main delegate can set it.
  static CFUrlRequestUnittestRunner* g_test_suite = NULL;
  
  // Copied here for access by CreateBrowserMainParts and InitGoogleTest.
  static int g_argc = 0;
  static char** g_argv = NULL;
  
  // A special command line switch to allow developers to manually launch the
  // browser and debug CF inside the browser.
  const char kManualBrowserLaunch[] = "manual-browser";
  
  // Pops up a message box after the test environment has been set up
  // and before tearing it down.  Useful for when debugging tests and not
  // the test environment that's been set up.
  const char kPromptAfterSetup[] = "prompt-after-setup";
  
  const int kTestServerPort = 4666;
  // The test HTML we use to initialize Chrome Frame.
  // Note that there's a little trick in there to avoid an extra URL request
  // that the browser will otherwise make for the site's favicon.
  // If we don't do this the browser will create a new URL request after
  // the CF page has been initialized and that URL request will confuse the
  // global URL instance counter in the unit tests and subsequently trip
  // some DCHECKs.
  const char kChromeFrameHtml[] = "<html><head>"
      "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />"
      "<link rel=\"shortcut icon\" href=\"file://c:\\favicon.ico\"/>"
      "</head><body>Chrome Frame should now be loaded</body></html>";
  
  // Uses the IAccessible interface for the window to set the focus.
  // This can be useful when you don't have control over the thread that
  // owns the window.
  // NOTE: this depends on oleacc.lib which the net tests already depend on
  // but other unit tests don't depend on oleacc so we can't just add the method
  // directly into chrome_frame_test_utils.cc (without adding a
  // #pragma comment(lib, "oleacc.lib")).
  bool SetFocusToAccessibleWindow(HWND hwnd) {
    bool ret = false;
    base::win::ScopedComPtr<IAccessible> acc;
    AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible,
        reinterpret_cast<void**>(acc.Receive()));
    if (acc) {
      VARIANT self = { VT_I4 };
      self.lVal = CHILDID_SELF;
      ret = SUCCEEDED(acc->accSelect(SELFLAG_TAKEFOCUS, self));
    }
    return ret;
  }
  
  class FakeContentBrowserClient : public chrome::ChromeContentBrowserClient {
   public:
    virtual ~FakeContentBrowserClient() {}
  
    virtual content::BrowserMainParts* CreateBrowserMainParts(
        const content::MainFunctionParams& parameters) OVERRIDE;
  };
  
  base::LazyInstance<ChromeContentClient>
      g_chrome_content_client = LAZY_INSTANCE_INITIALIZER;
  
  // Override the default ContentBrowserClient to let Chrome participate in
  // content logic.  Must be done before any tabs are created.
  base::LazyInstance<FakeContentBrowserClient>
      g_browser_client = LAZY_INSTANCE_INITIALIZER;
  
  base::LazyInstance<ChromeContentRendererClient>
      g_renderer_client = LAZY_INSTANCE_INITIALIZER;
  
  class FakeMainDelegate : public content::ContentMainDelegate {
   public:
    virtual ~FakeMainDelegate() {}
  
    virtual bool BasicStartupComplete(int* exit_code) OVERRIDE {
      logging_win::InstallTestLogCollector(
          testing::UnitTest::GetInstance());
  
      content::SetContentClient(&g_chrome_content_client.Get());
      content::SetRendererClientForTesting(&g_renderer_client.Get());
      return false;
    }
  
    // Override the default ContentBrowserClient to let Chrome participate in
    // content logic.  We use a subclass of Chrome's implementation,
    // FakeContentBrowserClient, to override CreateBrowserMainParts.  Must
    // be done before any tabs are created.
    virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE {
      return &g_browser_client.Get();
    };
  };
  
  void FilterDisabledTests() {
    if (::testing::FLAGS_gtest_filter.length() &&
        ::testing::FLAGS_gtest_filter != "*") {
      // Don't override user specified filters.
      return;
    }
  
    const char* disabled_tests[] = {
      // Tests disabled since they're testing the same functionality used
      // by the TestAutomationProvider.
      "URLRequestTest.Intercept",
      "URLRequestTest.InterceptNetworkError",
      "URLRequestTest.InterceptRestartRequired",
      "URLRequestTest.InterceptRespectsCancelMain",
      "URLRequestTest.InterceptRespectsCancelRedirect",
      "URLRequestTest.InterceptRespectsCancelFinal",
      "URLRequestTest.InterceptRespectsCancelInRestart",
      "URLRequestTest.InterceptRedirect",
      "URLRequestTest.InterceptServerError",
      "URLRequestTestFTP.*",
  
      // Tests that are currently not working:
  
      // Temporarily disabled because they needs user input (login dialog).
      "URLRequestTestHTTP.BasicAuth",
      "URLRequestTestHTTP.BasicAuthWithCookies",
  
      // ChromeFrame does not support load timing.
      "URLRequestTestHTTP.BasicAuthLoadTiming",
      "URLRequestTestHTTP.GetTestLoadTiming",
      "URLRequestTestHTTP.RedirectLoadTiming",
  
      // HTTPS tests temporarily disabled due to the certificate error dialog.
      // TODO(tommi): The tests currently fail though, so need to fix.
      "HTTPSRequestTest.HTTPSMismatchedTest",
      "HTTPSRequestTest.HTTPSExpiredTest",
      "HTTPSRequestTest.ClientAuthTest",
  
      // More HTTPS tests failing due to certificate dialogs.
      // http://crbug.com/102991
      "URLRequestTestHTTP.HTTPSToHTTPRedirectNoRefererTest",
      "HTTPSRequestTest.HTTPSGetTest",
  
      // Tests chrome's network stack's cache (might not apply to CF).
      "URLRequestTestHTTP.VaryHeader",
      "URLRequestTestHTTP.GetZippedTest",
  
      // Tests that requests can be blocked asynchronously in states
      // OnBeforeURLRequest, OnBeforeSendHeaders and OnHeadersReceived. At least
      // the second state is not supported by CF.
      "URLRequestTestHTTP.NetworkDelegateBlockAsynchronously",
  
      // Tests for cancelling requests in states OnBeforeSendHeaders and
      // OnHeadersReceived, which do not seem supported by CF.
      "URLRequestTestHTTP.NetworkDelegateCancelRequestSynchronously2",
      "URLRequestTestHTTP.NetworkDelegateCancelRequestSynchronously3",
      "URLRequestTestHTTP.NetworkDelegateCancelRequestAsynchronously2",
      "URLRequestTestHTTP.NetworkDelegateCancelRequestAsynchronously3",
  
      // Tests that requests can be cancelled while blocking in
      // OnBeforeSendHeaders state. But this state is not supported by CF.
      "URLRequestTestHTTP.NetworkDelegateCancelWhileWaiting2",
  
      // Tests that requests can be cancelled while blocking in
      // OnHeadersRecevied state. At first glance, this state does not appear to
      // be supported by CF.
      "URLRequestTestHTTP.NetworkDelegateCancelWhileWaiting3",
  
      // Tests that requests can be cancelled while blocking in OnAuthRequired
      // state. At first glance, this state does not appear to be supported by CF.
      // IE displays a credentials prompt during this test - I (erikwright)
      // believe that, from Chrome's point of view this is not a state change. In
      // any case, I also believe that we do not have support for handling the
      // credentials dialog during tests.
      "URLRequestTestHTTP.NetworkDelegateCancelWhileWaiting4",
  
      // I suspect we can only get this one to work (if at all) on IE8 and
      // later by using the new INTERNET_OPTION_SUPPRESS_BEHAVIOR flags
      // See http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx
      "URLRequestTest.DoNotSaveCookies",
      "URLRequestTest.DelayedCookieCallback",
  
      // TODO(ananta): This test has been consistently failing. Disabling it for
      // now.
      "URLRequestTestHTTP.GetTest_NoCache",
  
      // These tests use HTTPS, and IE's trust store does not have the test
      // certs. So these tests time out waiting for user input. The
      // functionality they test (HTTP Strict Transport Security and
      // HTTP-based Public Key Pinning) does not work in Chrome Frame anyway.
      "URLRequestTestHTTP.ProcessPKP",
      "URLRequestTestHTTP.ProcessSTS",
      "URLRequestTestHTTP.ProcessSTSOnce",
      "URLRequestTestHTTP.ProcessSTSAndPKP",
      "URLRequestTestHTTP.ProcessSTSAndPKP2",
  
      // These tests have been disabled as the Chrome cookie policies don't make
      // sense or have not been implemented for the host network stack.
      "URLRequestTest.DoNotSaveCookies_ViaPolicy",
      "URLRequestTest.DoNotSendCookies_ViaPolicy",
      "URLRequestTest.DoNotSaveCookies_ViaPolicy_Async",
      "URLRequestTest.CookiePolicy_ForceSession",
      "URLRequestTest.DoNotSendCookies",
      "URLRequestTest.DoNotSendCookies_ViaPolicy_Async",
      "URLRequestTest.CancelTest_During_OnGetCookies",
      "URLRequestTest.CancelTest_During_OnSetCookie",
  
      // These tests are disabled as they rely on functionality provided by
      // Chrome's HTTP stack like the ability to set the proxy for a URL, etc.
      "URLRequestTestHTTP.ProxyTunnelRedirectTest",
      "URLRequestTestHTTP.NetworkDelegateTunnelConnectionFailed",
      "URLRequestTestHTTP.UnexpectedServerAuthTest",
  
      // These tests are disabled as they expect an empty UA to be echoed back
      // from the server which is not the case in ChromeFrame.
      "URLRequestTestHTTP.DefaultUserAgent",
      "URLRequestTestHTTP.EmptyHttpUserAgentSettings",
      // This test modifies the UploadData object after it has been marshaled to
      // ChromeFrame. We don't support this.
      "URLRequestTestHTTP.TestPostChunkedDataAfterStart",
  
      // Do not work in CF, it may well be that IE is unconditionally
      // adding Accept-Encoding header by default to outgoing requests.
      "URLRequestTestHTTP.DefaultAcceptEncoding",
      "URLRequestTestHTTP.OverrideAcceptEncoding",
  
      // Not supported in ChromeFrame as we use IE's network stack.
      "URLRequestTest.NetworkDelegateProxyError",
      "URLRequestTest.AcceptClockSkewCookieWithWrongDateTimezone",
  
      // URLRequestAutomationJob needs to support NeedsAuth.
      // http://crbug.com/98446
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredSyncNoAction",
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredSyncSetAuth",
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredSyncCancel",
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredAsyncNoAction",
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredAsyncSetAuth",
      "URLRequestTestHTTP.NetworkDelegateOnAuthRequiredAsyncCancel",
  
      // Flaky on the tryservers, http://crbug.com/103097
      "URLRequestTestHTTP.MultipleRedirectTest",
      "URLRequestTestHTTP.NetworkDelegateRedirectRequest",
  
      // These tests are unsupported in CF.
      "HTTPSRequestTest.HTTPSPreloadedHSTSTest",
      "HTTPSRequestTest.HTTPSErrorsNoClobberTSSTest",
      "HTTPSRequestTest.HSTSPreservesPosts",
      "HTTPSRequestTest.ResumeTest",
      "HTTPSRequestTest.SSLSessionCacheShardTest",
      "HTTPSRequestTest.SSLSessionCacheShardTest",
      "HTTPSRequestTest.SSLv3Fallback",
      "HTTPSRequestTest.TLSv1Fallback",
      "HTTPSHardFailTest.*",
      "HTTPSOCSPTest.*",
      "HTTPSEVCRLSetTest.*",
      "HTTPSCRLSetTest.*",
  
      // Chrome Frame doesn't support GetFullRequestHeaders.
      "URLRequestTest*.*_GetFullRequestHeaders",
  
      // IE redirects to data: URLs differently.
      "URLRequestTestHTTP.RestrictDataRedirects",
  
      // Chrome frame doesn't use URLRequestHttpJob, so doesn't call into
      // NetworkDelegates in OnStartCompleted, unlike Chrome.
      "URLRequestTestHTTP.NetworkDelegateInfo",
      "URLRequestTestHTTP.NetworkDelegateInfoAuth",
      "URLRequestTestHTTP.NetworkDelegateInfoRedirect",
    };
  
    const char* ie9_disabled_tests[] = {
      // These always hang on Joi's box with IE9, http://crbug.com/105435.
      // Several other tests, e.g. URLRequestTestHTTP.CancelTest2, 3 and
      // 5, often hang but not always.
      "URLRequestTestHTTP.NetworkDelegateRedirectRequestPost",
      "URLRequestTestHTTP.GetTest",
      "HTTPSRequestTest.HTTPSPreloadedHSTSTest",
      // This always hangs on erikwright's box with IE9.
      "URLRequestTestHTTP.Redirect302Tests"
    };
  
    std::string filter("-");  // All following filters will be negative.
    for (int i = 0; i < arraysize(disabled_tests); ++i) {
      if (i > 0)
        filter += ":";
  
      // If the rule has the form TestSuite.TestCase, also filter out
      // TestSuite.FLAKY_TestCase . This way the exclusion rules above
      // don't need to be updated when a test is marked flaky.
      base::StringPiece test_name(disabled_tests[i]);
      size_t dot_index = test_name.find('.');
      if (dot_index != base::StringPiece::npos &&
          dot_index + 1 < test_name.size()) {
        test_name.substr(0, dot_index).AppendToString(&filter);
        filter += ".FLAKY_";
        test_name.substr(dot_index + 1).AppendToString(&filter);
        filter += ":";
      }
      filter += disabled_tests[i];
    }
  
    if (chrome_frame_test::GetInstalledIEVersion() >= IE_9) {
      for (int i = 0; i < arraysize(ie9_disabled_tests); ++i) {
        filter += ":";
        filter += ie9_disabled_tests[i];
      }
    }
  
    ::testing::FLAGS_gtest_filter = filter;
  }
  
  }  // namespace
  
  // Same as BrowserProcessImpl, but uses custom profile manager.
  class FakeBrowserProcessImpl : public BrowserProcessImpl {
   public:
    FakeBrowserProcessImpl(base::SequencedTaskRunner* local_state_task_runner,
                           const CommandLine& command_line)
        : BrowserProcessImpl(local_state_task_runner, command_line) {
      profiles_dir_.CreateUniqueTempDir();
    }
  
    virtual ~FakeBrowserProcessImpl() {}
  
    virtual ProfileManager* profile_manager() OVERRIDE {
      if (!profile_manager_.get()) {
        profile_manager_.reset(
            new ProfileManagerWithoutInit(profiles_dir_.path()));
      }
      return profile_manager_.get();
    }
  
    virtual MetricsService* metrics_service() OVERRIDE {
      return NULL;
    }
  
    void DestroyProfileManager() {
      profile_manager_.reset();
    }
  
   private:
    base::ScopedTempDir profiles_dir_;
    scoped_ptr<ProfileManager> profile_manager_;
  };
  
  class SupplyProxyCredentials : public WindowObserver {
   public:
    SupplyProxyCredentials(const char* username, const char* password);
  
   protected:
    struct DialogProps {
      HWND username_;
      HWND password_;
    };
  
    virtual void OnWindowOpen(HWND hwnd);
    virtual void OnWindowClose(HWND hwnd);
    static BOOL CALLBACK EnumChildren(HWND hwnd, LPARAM param);
  
   protected:
    std::string username_;
    std::string password_;
  };
  
  
  SupplyProxyCredentials::SupplyProxyCredentials(const char* username,
                                                 const char* password)
      : username_(username), password_(password) {
  }
  
  void SupplyProxyCredentials::OnWindowClose(HWND hwnd) { }
  
  void SupplyProxyCredentials::OnWindowOpen(HWND hwnd) {
    DialogProps props = {0};
    ::EnumChildWindows(hwnd, EnumChildren, reinterpret_cast<LPARAM>(&props));
    DCHECK(::IsWindow(props.username_));
    DCHECK(::IsWindow(props.password_));
  
    // We can't use SetWindowText to set the username/password, so simulate
    // keyboard input instead.
    simulate_input::ForceSetForegroundWindow(hwnd);
    CHECK(SetFocusToAccessibleWindow(props.username_));
    simulate_input::SendStringA(username_.c_str());
    Sleep(100);
  
    simulate_input::SendCharA(VK_TAB, simulate_input::NONE);
    Sleep(100);
    simulate_input::SendStringA(password_.c_str());
  
    Sleep(100);
    simulate_input::SendCharA(VK_RETURN, simulate_input::NONE);
  }
  
  // static
  BOOL SupplyProxyCredentials::EnumChildren(HWND hwnd, LPARAM param) {
    if (!::IsWindowVisible(hwnd))
      return TRUE;  // Ignore but continue to enumerate.
  
    DialogProps* props = reinterpret_cast<DialogProps*>(param);
  
    char class_name[MAX_PATH] = {0};
    ::GetClassNameA(hwnd, class_name, arraysize(class_name));
    if (lstrcmpiA(class_name, "Edit") == 0) {
      if (props->username_ == NULL || props->username_ == hwnd) {
        props->username_ = hwnd;
      } else if (props->password_ == NULL) {
        props->password_ = hwnd;
      }
    } else {
      EnumChildWindows(hwnd, EnumChildren, param);
    }
  
    return TRUE;
  }
  
  FakeExternalTab::FakeExternalTab() {
    user_data_dir_ = chrome_frame_test::GetProfilePathForIE();
  
    if (base::PathExists(user_data_dir_)) {
      VLOG(1) << __FUNCTION__ << " deleting IE Profile user data directory "
              << user_data_dir_.value();
      bool deleted = base::DeleteFile(user_data_dir_, true);
      LOG_IF(ERROR, !deleted) << "Failed to delete user data directory directory "
                              << user_data_dir_.value();
    }
  
    PathService::Get(chrome::DIR_USER_DATA, &overridden_user_dir_);
    PathService::Override(chrome::DIR_USER_DATA, user_data_dir_);
  }
  
  FakeExternalTab::~FakeExternalTab() {
    if (!overridden_user_dir_.empty()) {
      PathService::Override(chrome::DIR_USER_DATA, overridden_user_dir_);
    }
  }
  
  void FakeExternalTab::Initialize() {
    DCHECK(g_browser_process == NULL);
  
    TestTimeouts::Initialize();
  
    // Load Chrome.dll as our resource dll.
    base::FilePath dll;
    PathService::Get(base::DIR_MODULE, &dll);
    dll = dll.Append(chrome::kBrowserResourcesDll);
    HMODULE res_mod = ::LoadLibraryExW(dll.value().c_str(),
        NULL, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
    DCHECK(res_mod);
    _AtlBaseModule.SetResourceInstance(res_mod);
  
    // Point the ResourceBundle at chrome.dll.
    ui::SetResourcesDataDLL(_AtlBaseModule.GetResourceInstance());
  
    ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL);
  
    CommandLine* cmd = CommandLine::ForCurrentProcess();
    // Disable Device Discovery with switch because this test does not respect
    // BrowserContextKeyedBaseFactory::ServiceIsNULLWhileTesting.
    cmd->AppendSwitch(switches::kDisableDeviceDiscoveryNotifications);
    cmd->AppendSwitch(switches::kDisableWebResources);
    cmd->AppendSwitch(switches::kSingleProcess);
  
    base::FilePath local_state_path;
    CHECK(PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path));
    scoped_refptr<base::SequencedTaskRunner> local_state_task_runner =
        JsonPrefStore::GetTaskRunnerForFile(local_state_path,
                                            BrowserThread::GetBlockingPool());
    browser_process_.reset(new FakeBrowserProcessImpl(local_state_task_runner,
                                                      *cmd));
    // BrowserProcessImpl's constructor should set g_browser_process.
    DCHECK(g_browser_process);
    g_browser_process->SetApplicationLocale("en-US");
  
    content::RenderProcessHost::SetRunRendererInProcess(true);
  
    // TODO(joi): Registration should be done up front via browser_prefs.cc
    scoped_refptr<PrefRegistrySimple> registry = static_cast<PrefRegistrySimple*>(
        browser_process_->local_state()->DeprecatedGetPrefRegistry());
    if (!browser_process_->local_state()->FindPreference(
            prefs::kMetricsReportingEnabled)) {
      registry->RegisterBooleanPref(prefs::kMetricsReportingEnabled, false);
    }
  }
  
  void FakeExternalTab::InitializePostThreadsCreated() {
  #if defined(USE_AURA)
    aura::Env::CreateInstance();
    gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE,
                                   views::CreateDesktopScreen());
  #endif
    base::FilePath profile_path(
        profiles::GetDefaultProfileDir(user_data()));
    Profile* profile =
        g_browser_process->profile_manager()->GetProfile(profile_path);
  }
  
  void FakeExternalTab::Shutdown() {
    browser_process_.reset();
    g_browser_process = NULL;
  
    ResourceBundle::CleanupSharedInstance();
  }
  
  FakeBrowserProcessImpl* FakeExternalTab::browser_process() const {
    return browser_process_.get();
  }
  
  CFUrlRequestUnittestRunner::CFUrlRequestUnittestRunner(int argc, char** argv)
      : NetTestSuite(argc, argv, false),
        chrome_frame_html_("/chrome_frame", kChromeFrameHtml),
        registrar_(chrome_frame_test::GetTestBedType()),
        test_result_(0),
        launch_browser_(
            !CommandLine::ForCurrentProcess()->HasSwitch(kManualBrowserLaunch)),
        prompt_after_setup_(
            CommandLine::ForCurrentProcess()->HasSwitch(kPromptAfterSetup)),
        tests_ran_(false) {
  }
  
  CFUrlRequestUnittestRunner::~CFUrlRequestUnittestRunner() {
  }
  
  void CFUrlRequestUnittestRunner::StartChromeFrameInHostBrowser() {
    if (!launch_browser_)
      return;
  
    chrome_frame_test::CloseAllIEWindows();
  
    // Tweak IE settings to make it amenable to testing before launching it.
    ie_configurator_.reset(chrome_frame_test::CreateConfigurator());
    if (ie_configurator_.get() != NULL) {
      ie_configurator_->Initialize();
      ie_configurator_->ApplySettings();
    }
  
    test_http_server_.reset(new test_server::SimpleWebServer("127.0.0.1",
                                                             kTestServerPort));
    test_http_server_->AddResponse(&chrome_frame_html_);
    std::wstring url(base::StringPrintf(L"http://localhost:%i/chrome_frame",
                                        kTestServerPort));
  
    // Launch IE.  This launches IE correctly on Vista too.
    base::win::ScopedHandle ie_process(chrome_frame_test::LaunchIE(url));
    EXPECT_TRUE(ie_process.IsValid());
  
    // NOTE: If you're running IE8 and CF is not being loaded, you need to
    // disable IE8's prebinding until CF properly handles that situation.
    //
    // HKCU\Software\Microsoft\Internet Explorer\Main
    // Value name: EnablePreBinding (REG_DWORD)
    // Value: 0
  }
  
  void CFUrlRequestUnittestRunner::ShutDownHostBrowser() {
    if (launch_browser_)
      chrome_frame_test::CloseAllIEWindows();
  }
  
  void CFUrlRequestUnittestRunner::OnIEShutdownFailure() {
    LOG(ERROR) << "Failed to shutdown IE and npchrome_frame cleanly after test "
                  "execution.";
  
    if (ie_configurator_.get() != NULL)
      ie_configurator_->RevertSettings();
  
    StopFileLogger(true);
    ::ExitProcess(0);
  }
  
  // Override virtual void Initialize to not call icu initialize.
  void CFUrlRequestUnittestRunner::Initialize() {
    DCHECK(::GetCurrentThreadId() == test_thread_id_);
  
    // Start by replicating some of the steps that would otherwise be
    // done by TestSuite::Initialize.  We can't call the base class
    // directly because it will attempt to initialize some things such as
    // ICU that have already been initialized for this process.
    CFUrlRequestUnittestRunner::InitializeLogging();
  
    SuppressErrorDialogs();
    base::debug::SetSuppressDebugUI(true);
    logging::SetLogAssertHandler(UnitTestAssertHandler);
  
    // Next, do some initialization for NetTestSuite.
    NetTestSuite::InitializeTestThreadNoNetworkChangeNotifier();
  
    // Finally, override the host used by the HTTP tests. See
    // http://crbug.com/114369 .
    OverrideHttpHost();
  }
  
  void CFUrlRequestUnittestRunner::Shutdown() {
    DCHECK(::GetCurrentThreadId() == test_thread_id_);
    NetTestSuite::Shutdown();
    OleUninitialize();
  }
  
  void CFUrlRequestUnittestRunner::OnInitialTabLoaded() {
    test_http_server_.reset();
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&CFUrlRequestUnittestRunner::CancelInitializationTimeout,
                   base::Unretained(this)));
    StartTests();
  }
  
  void CFUrlRequestUnittestRunner::OnProviderDestroyed() {
    if (tests_ran_) {
      StopFileLogger(false);
  
      if (ie_configurator_.get() != NULL)
        ie_configurator_->RevertSettings();
  
      if (crash_service_)
        base::KillProcess(crash_service_, 0, false);
  
      ::ExitProcess(test_result());
    } else {
      DLOG(ERROR) << "Automation Provider shutting down before test execution "
                     "has completed.";
    }
  }
  
  void CFUrlRequestUnittestRunner::StartTests() {
    if (prompt_after_setup_)
      MessageBoxA(NULL, "click ok to run", "", MB_OK);
  
    DCHECK_EQ(test_thread_.IsValid(), false);
    StopFileLogger(false);
    test_thread_.Set(::CreateThread(NULL, 0, RunAllUnittests, this, 0,
                                    &test_thread_id_));
    DCHECK(test_thread_.IsValid());
  }
  
  // static
  DWORD CFUrlRequestUnittestRunner::RunAllUnittests(void* param) {
    base::PlatformThread::SetName("CFUrlRequestUnittestRunner");
    CFUrlRequestUnittestRunner* me =
        reinterpret_cast<CFUrlRequestUnittestRunner*>(param);
    me->test_result_ = me->Run();
    me->tests_ran_ = true;
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&CFUrlRequestUnittestRunner::TakeDownBrowser,
                   base::Unretained(me)));
    return 0;
  }
  
  void CFUrlRequestUnittestRunner::TakeDownBrowser() {
    if (prompt_after_setup_)
      MessageBoxA(NULL, "click ok to exit", "", MB_OK);
  
    // Start capturing logs from npchrome_frame and the in-process Chrome to help
    // diagnose failures in IE shutdown. This will be Stopped in either
    // OnIEShutdownFailure or OnProviderDestroyed.
    StartFileLogger();
  
    // AddRef to ensure that IE going away does not trigger the Chrome shutdown
    // process.
    // IE shutting down will, however, trigger the automation channel to shut
    // down, at which time we will exit the process (see OnProviderDestroyed).
    g_browser_process->AddRefModule();
    ShutDownHostBrowser();
  
    // In case IE is somehow hung, make sure we don't sit around until a try-bot
    // kills us. OnIEShutdownFailure will log and exit with an error.
    BrowserThread::PostDelayedTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&CFUrlRequestUnittestRunner::OnIEShutdownFailure,
                   base::Unretained(this)),
        TestTimeouts::action_max_timeout());
  }
  
  void CFUrlRequestUnittestRunner::InitializeLogging() {
    base::FilePath exe;
    PathService::Get(base::FILE_EXE, &exe);
    base::FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log"));
    logging::LoggingSettings settings;
    settings.logging_dest = logging::LOG_TO_ALL;
    settings.log_file = log_filename.value().c_str();
    settings.delete_old = logging::DELETE_OLD_LOG_FILE;
    logging::InitLogging(settings);
    // We want process and thread IDs because we may have multiple processes.
    // Note: temporarily enabled timestamps in an effort to catch bug 6361.
    logging::SetLogItems(true, true, true, true);
  }
  
  void CFUrlRequestUnittestRunner::CancelInitializationTimeout() {
    timeout_closure_.Cancel();
  }
  
  void CFUrlRequestUnittestRunner::StartInitializationTimeout() {
    timeout_closure_.Reset(
        base::Bind(&CFUrlRequestUnittestRunner::OnInitializationTimeout,
                   base::Unretained(this)));
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        timeout_closure_.callback(),
        TestTimeouts::action_max_timeout());
  }
  
  void CFUrlRequestUnittestRunner::OnInitializationTimeout() {
    LOG(ERROR) << "Failed to start Chrome Frame in the host browser.";
  
    base::FilePath snapshot;
    if (ui_test_utils::SaveScreenSnapshotToDesktop(&snapshot))
      LOG(ERROR) << "Screen snapshot saved to " << snapshot.value();
  
    StopFileLogger(true);
  
    if (launch_browser_)
      chrome_frame_test::CloseAllIEWindows();
  
    if (ie_configurator_.get() != NULL)
      ie_configurator_->RevertSettings();
  
    if (crash_service_)
      base::KillProcess(crash_service_, 0, false);
  
    ::ExitProcess(1);
  }
  
  void CFUrlRequestUnittestRunner::OverrideHttpHost() {
    override_http_host_.reset(
        new net::ScopedCustomUrlRequestTestHttpHost(
            chrome_frame_test::GetLocalIPv4Address()));
  }
  
  void CFUrlRequestUnittestRunner::PreEarlyInitialization() {
    testing::InitGoogleTest(&g_argc, g_argv);
    FilterDisabledTests();
    StartFileLogger();
  }
  
  int CFUrlRequestUnittestRunner::PreCreateThreads() {
    fake_chrome_.reset(new FakeExternalTab());
    fake_chrome_->Initialize();
    fake_chrome_->browser_process()->PreCreateThreads();
    ProcessSingleton::NotificationCallback callback(
        base::Bind(
            &CFUrlRequestUnittestRunner::ProcessSingletonNotificationCallback,
            base::Unretained(this)));
    process_singleton_.reset(new ProcessSingleton(fake_chrome_->user_data(),
                                                  callback));
    return 0;
  }
  
  bool CFUrlRequestUnittestRunner::ProcessSingletonNotificationCallback(
      const CommandLine& command_line, const base::FilePath& current_directory) {
    std::string channel_id = command_line.GetSwitchValueASCII(
        switches::kAutomationClientChannelID);
    EXPECT_FALSE(channel_id.empty());
  
    Profile* profile = g_browser_process->profile_manager()->GetLastUsedProfile(
        fake_chrome_->user_data());
  
    AutomationProviderList* list = g_browser_process->GetAutomationProviderList();
    DCHECK(list);
    list->AddProvider(
        TestAutomationProvider::NewAutomationProvider(profile, channel_id, this));
    return true;
  }
  
  void CFUrlRequestUnittestRunner::PreMainMessageLoopRun() {
    fake_chrome_->InitializePostThreadsCreated();
    // Call Create directly instead of NotifyOtherProcessOrCreate as failure is
    // prefered to notifying another process here.
    if (!process_singleton_->Create()) {
      LOG(FATAL) << "Failed to start up ProcessSingleton. Is another test "
                 << "executable or Chrome Frame running?";
      if (crash_service_)
        base::KillProcess(crash_service_, 0, false);
      ::ExitProcess(1);
    }
  }
  
  bool CFUrlRequestUnittestRunner::MainMessageLoopRun(int* result_code) {
    DCHECK(base::MessageLoop::current());
    DCHECK(base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI);
  
    // We need to allow IO on the main thread for these tests.
    base::ThreadRestrictions::SetIOAllowed(true);
    StartChromeFrameInHostBrowser();
    StartInitializationTimeout();
    return false;
  }
  
  void CFUrlRequestUnittestRunner::PostMainMessageLoopRun() {
    process_singleton_->Cleanup();
    fake_chrome_->browser_process()->StartTearDown();
  
    // Must do this separately as the mock profile_manager_ is not the
    // same member as BrowserProcessImpl::StartTearDown resets.
    fake_chrome_->browser_process()->DestroyProfileManager();
  
    if (crash_service_)
      base::KillProcess(crash_service_, 0, false);
  
    base::KillProcesses(chrome_frame_test::kIEImageName, 0, NULL);
    base::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0, NULL);
  }
  
  void CFUrlRequestUnittestRunner::PostDestroyThreads() {
    process_singleton_.reset();
    fake_chrome_->browser_process()->PostDestroyThreads();
    fake_chrome_->Shutdown();
    fake_chrome_.reset();
  
  #ifndef NDEBUG
    // Avoid CRT cleanup in debug test runs to ensure that webkit ASSERTs which
    // check if globals are created and destroyed on the same thread don't fire.
    // Webkit global objects are created on the inproc renderer thread.
    ::ExitProcess(test_result());
  #endif
  }
  
  void CFUrlRequestUnittestRunner::StartFileLogger() {
    if (file_util::CreateTemporaryFile(&log_file_)) {
      file_logger_.reset(new logging_win::FileLogger());
      file_logger_->Initialize();
      file_logger_->StartLogging(log_file_);
    } else {
      LOG(ERROR) << "Failed to create an ETW log file";
    }
  }
  
  void CFUrlRequestUnittestRunner::StopFileLogger(bool print) {
    if (file_logger_.get() != NULL && file_logger_->is_logging()) {
      file_logger_->StopLogging();
  
      if (print) {
        // Flushing stdout should prevent unrelated output from being interleaved
        // with the log file output.
        std::cout.flush();
        // Dump the log to stderr.
        logging_win::PrintLogFile(log_file_, &std::cerr);
        std::cerr.flush();
      }
    }
  
    if (!log_file_.empty() && !base::DeleteFile(log_file_, false))
      LOG(ERROR) << "Failed to delete log file " << log_file_.value();
  
    log_file_.clear();
    file_logger_.reset();
  }
  
  const char* IEVersionToString(IEVersion version) {
    switch (version) {
      case IE_6:
        return "IE6";
      case IE_7:
        return "IE7";
      case IE_8:
        return "IE8";
      case IE_9:
        return "IE9";
      case IE_10:
        return "IE10";
      case IE_UNSUPPORTED:
        return "Unknown IE Version";
      case NON_IE:
        return "Could not find IE";
      default:
        return "Error.";
    }
  }
  
  content::BrowserMainParts* FakeContentBrowserClient::CreateBrowserMainParts(
      const content::MainFunctionParams& parameters) {
    // Normally this would happen during browser startup, but for tests
    // we need to trigger creation of Profile-related services.
    ChromeBrowserMainExtraPartsProfiles::
        EnsureBrowserContextKeyedServiceFactoriesBuilt();
  
    // We never delete this, as the content module takes ownership.
    //
    // We must not construct this earlier, or we will have out-of-order
    // AtExitManager creation/destruction.
    g_test_suite = new CFUrlRequestUnittestRunner(g_argc, g_argv);
    g_test_suite->set_crash_service(chrome_frame_test::StartCrashService());
    return g_test_suite;
  }
  
  // We must provide a few functions content/ expects to link with.  They
  // are never called for this executable.
  int PluginMain(const content::MainFunctionParams& parameters) {
    NOTREACHED();
    return 0;
  }
  
  int PpapiBrokerMain(const content::MainFunctionParams& parameters) {
    return PluginMain(parameters);
  }
  
  int PpapiPluginMain(const content::MainFunctionParams& parameters) {
    return PluginMain(parameters);
  }
  
  int WorkerMain(const content::MainFunctionParams& parameters) {
    return PluginMain(parameters);
  }
  
  int main(int argc, char** argv) {
    ScopedChromeFrameRegistrar::RegisterAndExitProcessIfDirected();
    g_argc = argc;
    g_argv = argv;
  
    google_breakpad::scoped_ptr<google_breakpad::ExceptionHandler> breakpad(
        InitializeCrashReporting(HEADLESS));
  
    // Display the IE version we run with. This must be done after
    // CFUrlRequestUnittestRunner is constructed since that initializes logging.
    IEVersion ie_version = chrome_frame_test::GetInstalledIEVersion();
    LOG(INFO) << "Running CF net tests with IE version: "
              << IEVersionToString(ie_version);
  
    // See url_request_unittest.cc for these credentials.
    SupplyProxyCredentials credentials("user", "secret");
    WindowWatchdog watchdog;
    watchdog.AddObserver(&credentials, "Windows Security", "");
  
    sandbox::SandboxInterfaceInfo sandbox_info = {0};
    content::InitializeSandboxInfo(&sandbox_info);
    FakeMainDelegate delegate;
    content::ContentMain(
        reinterpret_cast<HINSTANCE>(GetModuleHandle(NULL)),
        &sandbox_info,
        &delegate);
  
    // Note:  In debug builds, we ExitProcess during PostDestroyThreads.
    return g_test_suite->test_result();
  }
  