1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome_frame/test/chrome_frame_test_utils.h"
6
7#include <atlapp.h>
8#include <atlmisc.h>
9#include <iepmapi.h>
10#include <sddl.h>
11#include <shlobj.h>
12#include <TlHelp32.h>
13#include <winsock2.h>
14
15#include "base/command_line.h"
16#include "base/file_util.h"
17#include "base/file_version_info.h"
18#include "base/files/file_path.h"
19#include "base/memory/scoped_ptr.h"
20#include "base/path_service.h"
21#include "base/process/kill.h"
22#include "base/process/launch.h"
23#include "base/process/process.h"
24#include "base/process/process_iterator.h"
25#include "base/strings/string_number_conversions.h"
26#include "base/strings/string_piece.h"
27#include "base/strings/string_util.h"
28#include "base/strings/stringprintf.h"
29#include "base/strings/utf_string_conversions.h"
30#include "base/win/registry.h"
31#include "base/win/scoped_handle.h"
32#include "base/win/windows_version.h"
33#include "chrome/common/chrome_paths.h"
34#include "chrome/common/chrome_paths_internal.h"
35#include "chrome/common/chrome_switches.h"
36#include "chrome/test/base/ui_test_utils.h"
37#include "chrome_frame/utils.h"
38#include "net/base/net_util.h"
39#include "testing/gtest/include/gtest/gtest.h"
40#include "ui/base/clipboard/clipboard.h"
41#include "ui/base/clipboard/scoped_clipboard_writer.h"
42
43namespace chrome_frame_test {
44
45const wchar_t kCrashServicePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
46
47const DWORD kCrashServicePipeDesiredAccess = FILE_READ_DATA |
48                                             FILE_WRITE_DATA |
49                                             FILE_WRITE_ATTRIBUTES;
50
51const DWORD kCrashServicePipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
52                                                  SECURITY_SQOS_PRESENT;
53const int kCrashServiceDetectTimeoutMs = 500;
54const int kCrashServiceStartupTimeoutMs = 1000;
55
56const wchar_t kIEImageName[] = L"iexplore.exe";
57const wchar_t kIEBrokerImageName[] = L"ieuser.exe";
58const char kChromeImageName[] = "chrome.exe";
59const wchar_t kIEProfileName[] = L"iexplore";
60const wchar_t kChromeLauncher[] = L"chrome_launcher.exe";
61
62#ifndef NDEBUG
63const base::TimeDelta kChromeFrameLongNavigationTimeout =
64    base::TimeDelta::FromSeconds(30);
65const base::TimeDelta kChromeFrameVeryLongNavigationTimeout =
66    base::TimeDelta::FromSeconds(90);
67#else
68const base::TimeDelta kChromeFrameLongNavigationTimeout =
69    base::TimeDelta::FromSeconds(10);
70const base::TimeDelta kChromeFrameVeryLongNavigationTimeout =
71    base::TimeDelta::FromSeconds(30);
72#endif
73
74// Callback function for EnumThreadWindows.
75BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) {
76  int& count = *reinterpret_cast<int*>(param);
77  if (IsWindowVisible(hwnd)) {
78    if (IsWindowEnabled(hwnd)) {
79      DWORD results = 0;
80      if (!::SendMessageTimeout(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0, SMTO_BLOCK,
81                                10000, &results)) {
82        LOG(WARNING) << "Window hung: " << base::StringPrintf(L"%08X", hwnd);
83      }
84      count++;
85    } else {
86      LOG(WARNING) << "Skipping disabled window: "
87                   << base::StringPrintf(L"%08X", hwnd);
88    }
89  }
90  return TRUE;  // continue enumeration
91}
92
93// Attempts to close all non-child, visible windows on the given thread.
94// The return value is the number of visible windows a close request was
95// sent to.
96int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id) {
97  int window_close_attempts = 0;
98  EnumThreadWindows(thread_id, CloseWindowsThreadCallback,
99                    reinterpret_cast<LPARAM>(&window_close_attempts));
100  return window_close_attempts;
101}
102
103// Enumerates the threads of a process and attempts to close visible non-child
104// windows on all threads of the process.
105// The return value is the number of visible windows a close request was
106// sent to.
107int CloseVisibleWindowsOnAllThreads(HANDLE process) {
108  DWORD process_id = ::GetProcessId(process);
109  if (process_id == 0) {
110    NOTREACHED();
111    return 0;
112  }
113
114  base::win::ScopedHandle snapshot(
115      CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0));
116  if (!snapshot.IsValid()) {
117    NOTREACHED();
118    return 0;
119  }
120
121  int window_close_attempts = 0;
122  THREADENTRY32 te = { sizeof(THREADENTRY32) };
123  if (Thread32First(snapshot, &te)) {
124    do {
125      if (RTL_CONTAINS_FIELD(&te, te.dwSize, th32OwnerProcessID) &&
126          te.th32OwnerProcessID == process_id) {
127        window_close_attempts +=
128            CloseVisibleTopLevelWindowsOnThread(te.th32ThreadID);
129      }
130      te.dwSize = sizeof(te);
131    } while (Thread32Next(snapshot, &te));
132  }
133
134  return window_close_attempts;
135}
136
137std::wstring GetExecutableAppPath(const std::wstring& file) {
138  std::wstring kAppPathsKey =
139      L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\";
140
141  std::wstring app_path;
142  base::win::RegKey key(HKEY_LOCAL_MACHINE, (kAppPathsKey + file).c_str(),
143                        KEY_READ);
144  if (key.Handle()) {
145    key.ReadValue(NULL, &app_path);
146  }
147
148  return app_path;
149}
150
151std::wstring FormatCommandForApp(const std::wstring& exe_name,
152                                 const std::wstring& argument) {
153  std::wstring reg_path(
154      base::StringPrintf(L"Applications\\%ls\\shell\\open\\command",
155                         exe_name.c_str()));
156  base::win::RegKey key(HKEY_CLASSES_ROOT, reg_path.c_str(), KEY_READ);
157
158  std::wstring command;
159  if (key.Handle()) {
160    key.ReadValue(NULL, &command);
161    int found = command.find(L"%1");
162    if (found >= 0) {
163      command.replace(found, 2, argument);
164    }
165  }
166  return command;
167}
168
169base::ProcessHandle LaunchExecutable(const std::wstring& executable,
170                                     const std::wstring& argument) {
171  base::ProcessHandle process = NULL;
172  std::wstring path = GetExecutableAppPath(executable);
173  if (path.empty()) {
174    path = FormatCommandForApp(executable, argument);
175    if (path.empty()) {
176      LOG(ERROR) << "Failed to find executable: " << executable;
177    } else {
178      CommandLine cmdline = CommandLine::FromString(path);
179      if (!base::LaunchProcess(cmdline, base::LaunchOptions(), &process)) {
180        LOG(ERROR) << "LaunchProcess failed: " << ::GetLastError();
181      }
182    }
183  } else {
184    CommandLine cmdline((base::FilePath(path)));
185    cmdline.AppendArgNative(argument);
186    if (!base::LaunchProcess(cmdline, base::LaunchOptions(), &process)) {
187      LOG(ERROR) << "LaunchProcess failed: " << ::GetLastError();
188    }
189  }
190  return process;
191}
192
193base::ProcessHandle LaunchChrome(const std::wstring& url,
194                                 const base::FilePath& user_data_dir) {
195  base::FilePath path;
196  PathService::Get(base::DIR_MODULE, &path);
197  path = path.AppendASCII(kChromeImageName);
198
199  CommandLine cmd(path);
200  cmd.AppendSwitch(switches::kNoFirstRun);
201  if (!user_data_dir.empty())
202    cmd.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
203  cmd.AppendArgNative(url);
204
205  base::ProcessHandle process = NULL;
206  base::LaunchProcess(cmd, base::LaunchOptions(), &process);
207  return process;
208}
209
210base::ProcessHandle LaunchIEOnVista(const std::wstring& url) {
211  typedef HRESULT (WINAPI* IELaunchURLPtr)(const wchar_t* url,
212                                           PROCESS_INFORMATION* pi,
213                                           VOID* info);
214
215  IELaunchURLPtr launch;
216  PROCESS_INFORMATION pi = {0};
217  IELAUNCHURLINFO  info = {sizeof info, 0};
218  HMODULE h = LoadLibrary(L"ieframe.dll");
219  if (!h) {
220    LOG(ERROR) << "Failed to load ieframe.dll: " << ::GetLastError();
221    return NULL;
222  }
223  launch = reinterpret_cast<IELaunchURLPtr>(GetProcAddress(h, "IELaunchURL"));
224  CHECK(launch);
225  HRESULT hr = launch(url.c_str(), &pi, &info);
226  FreeLibrary(h);
227  if (SUCCEEDED(hr)) {
228    CloseHandle(pi.hThread);
229  } else {
230    LOG(ERROR) << base::StringPrintf("IELaunchURL failed: 0x%08X", hr);
231  }
232  return pi.hProcess;
233}
234
235base::ProcessHandle LaunchIE(const std::wstring& url) {
236  if (GetInstalledIEVersion() >= IE_8) {
237    chrome_frame_test::ClearIESessionHistory();
238  }
239
240  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
241    return LaunchIEOnVista(url);
242  }
243  return LaunchExecutable(kIEImageName, url);
244}
245
246bool TakeSnapshotAndLog() {
247  testing::UnitTest* unit_test = testing::UnitTest::GetInstance();
248  const testing::TestInfo* test_info = unit_test->current_test_info();
249  std::string name;
250  if (test_info != NULL) {
251    name.append(test_info->test_case_name())
252        .append(1, '.')
253        .append(test_info->name());
254  } else {
255    name = "unknown test";
256  }
257
258  base::FilePath snapshot;
259  if (!ui_test_utils::SaveScreenSnapshotToDesktop(&snapshot)) {
260    LOG(ERROR) << "Failed saving screen snapshot for " << name;
261    return false;
262  }
263
264  LOG(ERROR) << "Saved screen snapshot for " << name << " to "
265             << snapshot.value();
266  return true;
267}
268
269int CloseAllIEWindows() {
270  int ret = 0;
271
272  base::win::ScopedComPtr<IShellWindows> windows;
273  HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL,
274      IID_IShellWindows, reinterpret_cast<void**>(windows.Receive()));
275  DCHECK(SUCCEEDED(hr));
276
277  if (SUCCEEDED(hr)) {
278    long count = 0;  // NOLINT
279    windows->get_Count(&count);
280    VARIANT i = { VT_I4 };
281    for (i.lVal = 0; i.lVal < count; ++i.lVal) {
282      base::win::ScopedComPtr<IDispatch> folder;
283      windows->Item(i, folder.Receive());
284      if (folder != NULL) {
285        base::win::ScopedComPtr<IWebBrowser2> browser;
286        if (SUCCEEDED(browser.QueryFrom(folder))) {
287          bool is_ie = true;
288          HWND window = NULL;
289          // Check the class of the browser window to make sure we only close
290          // IE windows.
291          if (browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&window))) {
292            wchar_t class_name[MAX_PATH];
293            if (::GetClassName(window, class_name, arraysize(class_name))) {
294              is_ie = _wcsicmp(class_name, L"IEFrame") == 0;
295            }
296          }
297          if (is_ie) {
298            browser->Quit();
299            ++ret;
300          }
301        }
302      }
303    }
304  }
305
306  return ret;
307}
308
309
310LowIntegrityToken::LowIntegrityToken() : impersonated_(false) {
311}
312
313LowIntegrityToken::~LowIntegrityToken() {
314  RevertToSelf();
315}
316
317BOOL LowIntegrityToken::RevertToSelf() {
318  BOOL ok = TRUE;
319  if (impersonated_) {
320    DCHECK(IsImpersonated());
321    ok = ::RevertToSelf();
322    if (ok)
323      impersonated_ = false;
324  }
325
326  return ok;
327}
328
329BOOL LowIntegrityToken::Impersonate() {
330  DCHECK(!impersonated_);
331  DCHECK(!IsImpersonated());
332  HANDLE process_token_handle = NULL;
333  BOOL ok = ::OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE,
334                               &process_token_handle);
335  if (!ok) {
336    LOG(ERROR) << "::OpenProcessToken failed: " << GetLastError();
337    return ok;
338  }
339
340  base::win::ScopedHandle process_token(process_token_handle);
341  // Create impersonation low integrity token.
342  HANDLE impersonation_token_handle = NULL;
343  ok = ::DuplicateTokenEx(process_token,
344      TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_ADJUST_DEFAULT, NULL,
345      SecurityImpersonation, TokenImpersonation, &impersonation_token_handle);
346  if (!ok) {
347    LOG(ERROR) << "::DuplicateTokenEx failed: " << GetLastError();
348    return ok;
349  }
350
351  // TODO(stoyan): sandbox/win/src/restricted_token_utils.cc has
352  // SetTokenIntegrityLevel function already.
353  base::win::ScopedHandle impersonation_token(impersonation_token_handle);
354  PSID integrity_sid = NULL;
355  TOKEN_MANDATORY_LABEL tml = {0};
356  ok = ::ConvertStringSidToSid(SDDL_ML_LOW, &integrity_sid);
357  if (!ok) {
358    LOG(ERROR) << "::ConvertStringSidToSid failed: " << GetLastError();
359    return ok;
360  }
361
362  tml.Label.Attributes = SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED;
363  tml.Label.Sid = integrity_sid;
364  ok = ::SetTokenInformation(impersonation_token, TokenIntegrityLevel,
365      &tml, sizeof(tml) + ::GetLengthSid(integrity_sid));
366  ::LocalFree(integrity_sid);
367  if (!ok) {
368    LOG(ERROR) << "::SetTokenInformation failed: " << GetLastError();
369    return ok;
370  }
371
372  // Switch current thread to low integrity.
373  ok = ::ImpersonateLoggedOnUser(impersonation_token);
374  if (ok) {
375    impersonated_ = true;
376  } else {
377    LOG(ERROR) << "::ImpersonateLoggedOnUser failed: " << GetLastError();
378  }
379
380  return ok;
381}
382
383bool LowIntegrityToken::IsImpersonated() {
384  HANDLE token = NULL;
385  if (!::OpenThreadToken(::GetCurrentThread(), 0, false, &token) &&
386      ::GetLastError() != ERROR_NO_TOKEN) {
387    return true;
388  }
389
390  if (token)
391    ::CloseHandle(token);
392
393  return false;
394}
395
396HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser) {
397  if (!web_browser)
398    return E_INVALIDARG;
399
400  if (GetInstalledIEVersion() >= IE_8) {
401    chrome_frame_test::ClearIESessionHistory();
402  }
403
404  AllowSetForegroundWindow(ASFW_ANY);
405
406  HRESULT hr = S_OK;
407  DWORD cocreate_flags = CLSCTX_LOCAL_SERVER;
408  chrome_frame_test::LowIntegrityToken token;
409  base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN;
410  // Vista has a bug which manifests itself when a medium integrity process
411  // launches a COM server like IE which runs in protected mode due to UAC.
412  // This causes the IWebBrowser2 interface which is returned to be useless,
413  // i.e it does not receive any events, etc. Our workaround for this is
414  // to impersonate a low integrity token and then launch IE.  Skip this if the
415  // tests are running at high integrity, since the workaround results in the
416  // medium-integrity broker exiting, and the low-integrity IE is therefore
417  // unable to get chrome_launcher running at medium integrity.
418  if (base::win::GetVersion() == base::win::VERSION_VISTA &&
419      GetInstalledIEVersion() == IE_7 &&
420      base::GetProcessIntegrityLevel(base::Process::Current().handle(),
421                                     &integrity_level) &&
422      integrity_level != base::HIGH_INTEGRITY) {
423    // Create medium integrity browser that will launch IE broker.
424    base::win::ScopedComPtr<IWebBrowser2> medium_integrity_browser;
425    hr = medium_integrity_browser.CreateInstance(CLSID_InternetExplorer, NULL,
426                                                 CLSCTX_LOCAL_SERVER);
427    if (FAILED(hr))
428      return hr;
429    medium_integrity_browser->Quit();
430    // Broker remains alive.
431    if (!token.Impersonate()) {
432      hr = HRESULT_FROM_WIN32(GetLastError());
433      return hr;
434    }
435
436    cocreate_flags |= CLSCTX_ENABLE_CLOAKING;
437  }
438
439  hr = ::CoCreateInstance(CLSID_InternetExplorer, NULL,
440                          cocreate_flags, IID_IWebBrowser2,
441                          reinterpret_cast<void**>(web_browser));
442  // ~LowIntegrityToken() will switch integrity back to medium.
443  return hr;
444}
445
446std::wstring GetExeVersion(const std::wstring& exe_path) {
447  scoped_ptr<FileVersionInfo> ie_version_info(
448      FileVersionInfo::CreateFileVersionInfo(base::FilePath(exe_path)));
449  return ie_version_info->product_version();
450}
451
452IEVersion GetInstalledIEVersion() {
453  std::wstring path(chrome_frame_test::GetExecutableAppPath(kIEImageName));
454  std::wstring version(GetExeVersion(path));
455  size_t first_dot = version.find(L'.');
456  int major_version = 0;
457  if (!base::StringToInt(base::StringPiece16(
458          version.data(),
459          first_dot == std::wstring::npos ? version.size() : first_dot),
460                         &major_version)) {
461    return IE_UNSUPPORTED;
462  }
463
464  switch (major_version) {
465    case 6:
466      return IE_6;
467    case 7:
468      return IE_7;
469    case 8:
470      return IE_8;
471    case 9:
472      return IE_9;
473    case 10:
474      return IE_10;
475    default:
476      break;
477  }
478
479  return IE_UNSUPPORTED;
480}
481
482base::FilePath GetProfilePathForIE() {
483  base::FilePath profile_path;
484  // Browsers without IDeleteBrowsingHistory in non-priv mode
485  // have their profiles moved into "Temporary Internet Files".
486  // The code below basically retrieves the version of IE and computes
487  // the profile directory accordingly.
488  if (GetInstalledIEVersion() <= IE_7) {
489    profile_path = GetIETemporaryFilesFolder();
490    profile_path = profile_path.Append(L"Google Chrome Frame");
491  } else {
492    GetChromeFrameProfilePath(kIEProfileName, &profile_path);
493  }
494  return profile_path;
495}
496
497base::FilePath GetTestDataFolder() {
498  base::FilePath test_dir;
499  PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
500  test_dir = test_dir.Append(FILE_PATH_LITERAL("chrome_frame"))
501      .Append(FILE_PATH_LITERAL("test"))
502      .Append(FILE_PATH_LITERAL("data"));
503  return test_dir;
504}
505
506base::FilePath GetSeleniumTestFolder() {
507  base::FilePath test_dir;
508  PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
509  test_dir = test_dir.Append(FILE_PATH_LITERAL("data"))
510      .Append(FILE_PATH_LITERAL("selenium_core"));
511  return test_dir;
512}
513
514std::wstring GetPathFromUrl(const std::wstring& url) {
515  string16 url16 = WideToUTF16(url);
516  GURL gurl = GURL(url16);
517  if (gurl.has_query()) {
518    GURL::Replacements replacements;
519    replacements.ClearQuery();
520    gurl = gurl.ReplaceComponents(replacements);
521  }
522  return UTF8ToWide(gurl.PathForRequest());
523}
524
525std::wstring GetPathAndQueryFromUrl(const std::wstring& url) {
526  string16 url16 = WideToUTF16(url);
527  GURL gurl = GURL(url16);
528  return UTF8ToWide(gurl.PathForRequest());
529}
530
531std::wstring GetClipboardText() {
532  string16 text16;
533  ui::Clipboard::GetForCurrentThread()->ReadText(
534      ui::CLIPBOARD_TYPE_COPY_PASTE, &text16);
535  return UTF16ToWide(text16);
536}
537
538void DestroyClipboard() {
539  ui::Clipboard::DestroyClipboardForCurrentThread();
540}
541
542void SetClipboardText(const std::wstring& text) {
543  ui::ScopedClipboardWriter clipboard_writer(
544      ui::Clipboard::GetForCurrentThread(),
545      ui::CLIPBOARD_TYPE_COPY_PASTE);
546  clipboard_writer.WriteText(WideToUTF16(text));
547}
548
549bool AddCFMetaTag(std::string* html_data) {
550  if (!html_data) {
551    NOTREACHED();
552    return false;
553  }
554  std::string lower = StringToLowerASCII(*html_data);
555  size_t head = lower.find("<head>");
556  if (head == std::string::npos) {
557    // Add missing head section.
558    size_t html = lower.find("<html>");
559    if (html != std::string::npos) {
560      head = html + strlen("<html>");
561      html_data->insert(head, "<head></head>");
562    } else {
563      LOG(ERROR) << "Meta tag will not be injected "
564                 << "because the html tag could not be found";
565    }
566  }
567  if (head != std::string::npos) {
568    html_data->insert(
569        head + strlen("<head>"),
570        "<meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\" />");
571  }
572  return head != std::string::npos;
573}
574
575CloseIeAtEndOfScope::~CloseIeAtEndOfScope() {
576  int closed = CloseAllIEWindows();
577  LOG_IF(ERROR, closed != 0) << "Closed " << closed << " windows forcefully";
578}
579
580// Attempt to connect to a running crash_service instance. Success occurs if we
581// can actually connect to the service's pipe or we receive ERROR_PIPE_BUSY.
582// Waits up to |timeout_ms| for success. |timeout_ms| may be 0, meaning only try
583// once, or negative, meaning wait forever.
584bool DetectRunningCrashService(int timeout_ms) {
585  // Wait for the crash_service.exe to be ready for clients.
586  base::Time start = base::Time::Now();
587  base::win::ScopedHandle new_pipe;
588
589  while (true) {
590    new_pipe.Set(::CreateFile(kCrashServicePipeName,
591                              kCrashServicePipeDesiredAccess,
592                              0,  // dwShareMode
593                              NULL,  // lpSecurityAttributes
594                              OPEN_EXISTING,
595                              kCrashServicePipeFlagsAndAttributes,
596                              NULL));  // hTemplateFile
597
598    if (new_pipe.IsValid()) {
599      return true;
600    }
601
602    switch (::GetLastError()) {
603      case ERROR_PIPE_BUSY:
604        // OK, it exists, let's assume that clients will eventually be able to
605        // connect to it.
606        return true;
607      case ERROR_FILE_NOT_FOUND:
608        // Wait a bit longer
609        break;
610      default:
611        DPLOG(WARNING) << "Unexpected error while checking crash_service.exe's "
612                       << "pipe.";
613        // Go ahead and wait in case it clears up.
614        break;
615    }
616
617    if (timeout_ms == 0) {
618      return false;
619    } else if (timeout_ms > 0) {
620      base::TimeDelta duration = base::Time::Now() - start;
621      if (duration.InMilliseconds() > timeout_ms) {
622        return false;
623      }
624    }
625
626    Sleep(10);
627  }
628}
629
630base::ProcessHandle StartCrashService() {
631  if (DetectRunningCrashService(kCrashServiceDetectTimeoutMs)) {
632    VLOG(1) << "crash_service.exe is already running. We will use the "
633               "existing process and leave it running after tests complete.";
634    return NULL;
635  }
636
637  base::FilePath exe_dir;
638  if (!PathService::Get(base::DIR_EXE, &exe_dir)) {
639    DCHECK(false);
640    return NULL;
641  }
642
643  base::win::ScopedHandle crash_service;
644
645  VLOG(1) << "Starting crash_service.exe so you know if a test crashes!";
646
647  base::FilePath crash_service_path = exe_dir.AppendASCII("crash_service.exe");
648  if (!base::LaunchProcess(crash_service_path.value(), base::LaunchOptions(),
649                           &crash_service)) {
650    LOG(ERROR) << "Couldn't start crash_service.exe";
651    return NULL;
652  }
653
654  base::Time start = base::Time::Now();
655
656  if (DetectRunningCrashService(kCrashServiceStartupTimeoutMs)) {
657    VLOG(1) << "crash_service.exe is ready for clients in "
658            << (base::Time::Now() - start).InMilliseconds() << " ms.";
659    return crash_service.Take();
660  } else {
661    LOG(ERROR) << "crash_service.exe failed to accept client connections "
662                  "within " << kCrashServiceStartupTimeoutMs << " ms. "
663                  "Terminating it now.";
664
665    // First check to see if it's even still running just to minimize the
666    // likelihood of spurious error messages from KillProcess.
667    if (WAIT_OBJECT_0 != ::WaitForSingleObject(crash_service.Get(), 0)) {
668      base::KillProcess(crash_service.Get(), 0, false);
669    }
670    return NULL;
671  }
672}
673
674ScopedVirtualizeHklmAndHkcu::ScopedVirtualizeHklmAndHkcu() {
675  override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE, L"hklm_fake");
676  override_manager_.OverrideRegistry(HKEY_CURRENT_USER, L"hkcu_fake");
677}
678
679ScopedVirtualizeHklmAndHkcu::~ScopedVirtualizeHklmAndHkcu() {
680}
681
682bool KillProcesses(const std::wstring& executable_name, int exit_code,
683                   bool wait) {
684  bool result = true;
685  base::NamedProcessIterator iter(executable_name, NULL);
686  while (const base::ProcessEntry* entry = iter.NextProcessEntry()) {
687    result &= base::KillProcessById(entry->pid(), exit_code, wait);
688  }
689  return result;
690}
691
692ScopedChromeFrameRegistrar::RegistrationType GetTestBedType() {
693  if (GetConfigBool(false, L"PerUserTestBed")) {
694    return ScopedChromeFrameRegistrar::PER_USER;
695  } else {
696    return ScopedChromeFrameRegistrar::SYSTEM_LEVEL;
697  }
698}
699
700void ClearIESessionHistory() {
701  base::FilePath session_history_path;
702  if (!PathService::Get(base::DIR_LOCAL_APP_DATA, &session_history_path))
703    return;
704
705  session_history_path = session_history_path.AppendASCII("Microsoft");
706  session_history_path = session_history_path.AppendASCII("Internet Explorer");
707  session_history_path = session_history_path.AppendASCII("Recovery");
708  base::DeleteFile(session_history_path, true);
709}
710
711std::string GetLocalIPv4Address() {
712  std::string address;
713  net::NetworkInterfaceList nic_list;
714
715  if (!net::GetNetworkList(&nic_list)) {
716    LOG(ERROR) << "GetNetworkList failed to look up non-loopback adapters. "
717               << "Tests will be run over the loopback adapter, which may "
718               << "result in hangs.";
719  } else {
720    // GetNetworkList only returns 'Up' non-loopback adapters. Select the first
721    // IPv4 address found - we should be able to bind/connect over it.
722    for (size_t i = 0; i < nic_list.size(); ++i) {
723      if (nic_list[i].address.size() != net::kIPv4AddressSize)
724        continue;
725      char* address_string =
726          inet_ntoa(*reinterpret_cast<in_addr*>(&nic_list[i].address[0]));
727      DCHECK(address_string != NULL);
728      if (address_string != NULL) {
729        LOG(INFO) << "HTTP tests will run over " << address_string << ".";
730        address.assign(address_string);
731        break;
732      }
733    }
734  }
735
736  if (address.empty()) {
737    LOG(ERROR) << "Failed to find a non-loopback IP_V4 address. Tests will be "
738               << "run over the loopback adapter, which may result in hangs.";
739    address.assign("127.0.0.1");
740  }
741
742  return address;
743}
744
745}  // namespace chrome_frame_test
746