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.
5 #include "chrome_frame/test/chrome_frame_test_utils.h"
14 #include "base/command_line.h"
15 #include "base/file_path.h"
16 #include "base/file_util.h"
17 #include "base/file_version_info.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/path_service.h"
20 #include "base/process.h"
21 #include "base/process_util.h"
22 #include "base/string_util.h"
23 #include "base/stringprintf.h"
24 #include "base/utf_string_conversions.h"
25 #include "base/win/registry.h"
26 #include "base/win/scoped_handle.h"
27 #include "base/win/windows_version.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/chrome_paths_internal.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome_frame/utils.h"
32 #include "net/base/net_util.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "ui/base/clipboard/clipboard.h"
35 #include "ui/base/clipboard/scoped_clipboard_writer.h"
37 namespace chrome_frame_test
{
39 const wchar_t kCrashServicePipeName
[] = L
"\\\\.\\pipe\\ChromeCrashServices";
41 const DWORD kCrashServicePipeDesiredAccess
= FILE_READ_DATA
|
43 FILE_WRITE_ATTRIBUTES
;
45 const DWORD kCrashServicePipeFlagsAndAttributes
= SECURITY_IDENTIFICATION
|
46 SECURITY_SQOS_PRESENT
;
47 const int kCrashServiceStartupTimeoutMs
= 500;
49 const wchar_t kIEImageName
[] = L
"iexplore.exe";
50 const wchar_t kIEBrokerImageName
[] = L
"ieuser.exe";
51 const char kChromeImageName
[] = "chrome.exe";
52 const wchar_t kIEProfileName
[] = L
"iexplore";
53 const wchar_t kChromeLauncher
[] = L
"chrome_launcher.exe";
56 const base::TimeDelta kChromeFrameLongNavigationTimeout
=
57 base::TimeDelta::FromSeconds(30);
58 const base::TimeDelta kChromeFrameVeryLongNavigationTimeout
=
59 base::TimeDelta::FromSeconds(90);
61 const base::TimeDelta kChromeFrameLongNavigationTimeout
=
62 base::TimeDelta::FromSeconds(15);
63 const base::TimeDelta kChromeFrameVeryLongNavigationTimeout
=
64 base::TimeDelta::FromSeconds(45);
67 // Callback function for EnumThreadWindows.
68 BOOL CALLBACK
CloseWindowsThreadCallback(HWND hwnd
, LPARAM param
) {
69 int& count
= *reinterpret_cast<int*>(param
);
70 if (IsWindowVisible(hwnd
)) {
71 if (IsWindowEnabled(hwnd
)) {
73 if (!::SendMessageTimeout(hwnd
, WM_SYSCOMMAND
, SC_CLOSE
, 0, SMTO_BLOCK
,
75 LOG(WARNING
) << "Window hung: " << base::StringPrintf(L
"%08X", hwnd
);
79 LOG(WARNING
) << "Skipping disabled window: "
80 << base::StringPrintf(L
"%08X", hwnd
);
83 return TRUE
; // continue enumeration
86 // Attempts to close all non-child, visible windows on the given thread.
87 // The return value is the number of visible windows a close request was
89 int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id
) {
90 int window_close_attempts
= 0;
91 EnumThreadWindows(thread_id
, CloseWindowsThreadCallback
,
92 reinterpret_cast<LPARAM
>(&window_close_attempts
));
93 return window_close_attempts
;
96 // Enumerates the threads of a process and attempts to close visible non-child
97 // windows on all threads of the process.
98 // The return value is the number of visible windows a close request was
100 int CloseVisibleWindowsOnAllThreads(HANDLE process
) {
101 DWORD process_id
= ::GetProcessId(process
);
102 if (process_id
== 0) {
107 base::win::ScopedHandle
snapshot(
108 CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD
, 0));
109 if (!snapshot
.IsValid()) {
114 int window_close_attempts
= 0;
115 THREADENTRY32 te
= { sizeof(THREADENTRY32
) };
116 if (Thread32First(snapshot
, &te
)) {
118 if (RTL_CONTAINS_FIELD(&te
, te
.dwSize
, th32OwnerProcessID
) &&
119 te
.th32OwnerProcessID
== process_id
) {
120 window_close_attempts
+=
121 CloseVisibleTopLevelWindowsOnThread(te
.th32ThreadID
);
123 te
.dwSize
= sizeof(te
);
124 } while (Thread32Next(snapshot
, &te
));
127 return window_close_attempts
;
130 std::wstring
GetExecutableAppPath(const std::wstring
& file
) {
131 std::wstring kAppPathsKey
=
132 L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\";
134 std::wstring app_path
;
135 base::win::RegKey
key(HKEY_LOCAL_MACHINE
, (kAppPathsKey
+ file
).c_str(),
138 key
.ReadValue(NULL
, &app_path
);
144 std::wstring
FormatCommandForApp(const std::wstring
& exe_name
,
145 const std::wstring
& argument
) {
146 std::wstring
reg_path(
147 base::StringPrintf(L
"Applications\\%ls\\shell\\open\\command",
149 base::win::RegKey
key(HKEY_CLASSES_ROOT
, reg_path
.c_str(), KEY_READ
);
151 std::wstring command
;
153 key
.ReadValue(NULL
, &command
);
154 int found
= command
.find(L
"%1");
156 command
.replace(found
, 2, argument
);
162 base::ProcessHandle
LaunchExecutable(const std::wstring
& executable
,
163 const std::wstring
& argument
) {
164 base::ProcessHandle process
= NULL
;
165 std::wstring path
= GetExecutableAppPath(executable
);
167 path
= FormatCommandForApp(executable
, argument
);
169 LOG(ERROR
) << "Failed to find executable: " << executable
;
171 CommandLine cmdline
= CommandLine::FromString(path
);
172 if (!base::LaunchProcess(cmdline
, base::LaunchOptions(), &process
)) {
173 LOG(ERROR
) << "LaunchProcess failed: " << ::GetLastError();
177 CommandLine
cmdline((FilePath(path
)));
178 cmdline
.AppendArgNative(argument
);
179 if (!base::LaunchProcess(cmdline
, base::LaunchOptions(), &process
)) {
180 LOG(ERROR
) << "LaunchProcess failed: " << ::GetLastError();
186 base::ProcessHandle
LaunchChrome(const std::wstring
& url
,
187 const FilePath
& user_data_dir
) {
189 PathService::Get(base::DIR_MODULE
, &path
);
190 path
= path
.AppendASCII(kChromeImageName
);
192 CommandLine
cmd(path
);
193 cmd
.AppendSwitch(switches::kNoFirstRun
);
194 if (!user_data_dir
.empty())
195 cmd
.AppendSwitchPath(switches::kUserDataDir
, user_data_dir
);
196 cmd
.AppendArgNative(url
);
198 base::ProcessHandle process
= NULL
;
199 base::LaunchProcess(cmd
, base::LaunchOptions(), &process
);
203 base::ProcessHandle
LaunchIEOnVista(const std::wstring
& url
) {
204 typedef HRESULT (WINAPI
* IELaunchURLPtr
)(const wchar_t* url
,
205 PROCESS_INFORMATION
* pi
,
208 IELaunchURLPtr launch
;
209 PROCESS_INFORMATION pi
= {0};
210 IELAUNCHURLINFO info
= {sizeof info
, 0};
211 HMODULE h
= LoadLibrary(L
"ieframe.dll");
213 LOG(ERROR
) << "Failed to load ieframe.dll: " << ::GetLastError();
216 launch
= reinterpret_cast<IELaunchURLPtr
>(GetProcAddress(h
, "IELaunchURL"));
218 HRESULT hr
= launch(url
.c_str(), &pi
, &info
);
221 CloseHandle(pi
.hThread
);
223 LOG(ERROR
) << base::StringPrintf("IELaunchURL failed: 0x%08X", hr
);
228 base::ProcessHandle
LaunchIE(const std::wstring
& url
) {
229 if (GetInstalledIEVersion() >= IE_8
) {
230 chrome_frame_test::ClearIESessionHistory();
233 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
234 return LaunchIEOnVista(url
);
236 return LaunchExecutable(kIEImageName
, url
);
239 int CloseAllIEWindows() {
242 base::win::ScopedComPtr
<IShellWindows
> windows
;
243 HRESULT hr
= ::CoCreateInstance(__uuidof(ShellWindows
), NULL
, CLSCTX_ALL
,
244 IID_IShellWindows
, reinterpret_cast<void**>(windows
.Receive()));
245 DCHECK(SUCCEEDED(hr
));
248 long count
= 0; // NOLINT
249 windows
->get_Count(&count
);
250 VARIANT i
= { VT_I4
};
251 for (i
.lVal
= 0; i
.lVal
< count
; ++i
.lVal
) {
252 base::win::ScopedComPtr
<IDispatch
> folder
;
253 windows
->Item(i
, folder
.Receive());
254 if (folder
!= NULL
) {
255 base::win::ScopedComPtr
<IWebBrowser2
> browser
;
256 if (SUCCEEDED(browser
.QueryFrom(folder
))) {
259 // Check the class of the browser window to make sure we only close
261 if (browser
->get_HWND(reinterpret_cast<SHANDLE_PTR
*>(window
))) {
262 wchar_t class_name
[MAX_PATH
];
263 if (::GetClassName(window
, class_name
, arraysize(class_name
))) {
264 is_ie
= _wcsicmp(class_name
, L
"IEFrame") == 0;
280 LowIntegrityToken::LowIntegrityToken() : impersonated_(false) {
283 LowIntegrityToken::~LowIntegrityToken() {
287 BOOL
LowIntegrityToken::RevertToSelf() {
290 DCHECK(IsImpersonated());
291 ok
= ::RevertToSelf();
293 impersonated_
= false;
299 BOOL
LowIntegrityToken::Impersonate() {
300 DCHECK(!impersonated_
);
301 DCHECK(!IsImpersonated());
302 HANDLE process_token_handle
= NULL
;
303 BOOL ok
= ::OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE
,
304 &process_token_handle
);
306 LOG(ERROR
) << "::OpenProcessToken failed: " << GetLastError();
310 base::win::ScopedHandle
process_token(process_token_handle
);
311 // Create impersonation low integrity token.
312 HANDLE impersonation_token_handle
= NULL
;
313 ok
= ::DuplicateTokenEx(process_token
,
314 TOKEN_QUERY
| TOKEN_IMPERSONATE
| TOKEN_ADJUST_DEFAULT
, NULL
,
315 SecurityImpersonation
, TokenImpersonation
, &impersonation_token_handle
);
317 LOG(ERROR
) << "::DuplicateTokenEx failed: " << GetLastError();
321 // TODO(stoyan): sandbox/win/src/restricted_token_utils.cc has
322 // SetTokenIntegrityLevel function already.
323 base::win::ScopedHandle
impersonation_token(impersonation_token_handle
);
324 PSID integrity_sid
= NULL
;
325 TOKEN_MANDATORY_LABEL tml
= {0};
326 ok
= ::ConvertStringSidToSid(SDDL_ML_LOW
, &integrity_sid
);
328 LOG(ERROR
) << "::ConvertStringSidToSid failed: " << GetLastError();
332 tml
.Label
.Attributes
= SE_GROUP_INTEGRITY
| SE_GROUP_INTEGRITY_ENABLED
;
333 tml
.Label
.Sid
= integrity_sid
;
334 ok
= ::SetTokenInformation(impersonation_token
, TokenIntegrityLevel
,
335 &tml
, sizeof(tml
) + ::GetLengthSid(integrity_sid
));
336 ::LocalFree(integrity_sid
);
338 LOG(ERROR
) << "::SetTokenInformation failed: " << GetLastError();
342 // Switch current thread to low integrity.
343 ok
= ::ImpersonateLoggedOnUser(impersonation_token
);
345 impersonated_
= true;
347 LOG(ERROR
) << "::ImpersonateLoggedOnUser failed: " << GetLastError();
353 bool LowIntegrityToken::IsImpersonated() {
355 if (!::OpenThreadToken(::GetCurrentThread(), 0, false, &token
) &&
356 ::GetLastError() != ERROR_NO_TOKEN
) {
361 ::CloseHandle(token
);
366 HRESULT
LaunchIEAsComServer(IWebBrowser2
** web_browser
) {
370 if (GetInstalledIEVersion() >= IE_8
) {
371 chrome_frame_test::ClearIESessionHistory();
374 AllowSetForegroundWindow(ASFW_ANY
);
377 DWORD cocreate_flags
= CLSCTX_LOCAL_SERVER
;
378 chrome_frame_test::LowIntegrityToken token
;
379 base::IntegrityLevel integrity_level
= base::INTEGRITY_UNKNOWN
;
380 // Vista has a bug which manifests itself when a medium integrity process
381 // launches a COM server like IE which runs in protected mode due to UAC.
382 // This causes the IWebBrowser2 interface which is returned to be useless,
383 // i.e it does not receive any events, etc. Our workaround for this is
384 // to impersonate a low integrity token and then launch IE. Skip this if the
385 // tests are running at high integrity, since the workaround results in the
386 // medium-integrity broker exiting, and the low-integrity IE is therefore
387 // unable to get chrome_launcher running at medium integrity.
388 if (base::win::GetVersion() == base::win::VERSION_VISTA
&&
389 GetInstalledIEVersion() == IE_7
&&
390 base::GetProcessIntegrityLevel(base::Process::Current().handle(),
392 integrity_level
!= base::HIGH_INTEGRITY
) {
393 // Create medium integrity browser that will launch IE broker.
394 base::win::ScopedComPtr
<IWebBrowser2
> medium_integrity_browser
;
395 hr
= medium_integrity_browser
.CreateInstance(CLSID_InternetExplorer
, NULL
,
396 CLSCTX_LOCAL_SERVER
);
399 medium_integrity_browser
->Quit();
400 // Broker remains alive.
401 if (!token
.Impersonate()) {
402 hr
= HRESULT_FROM_WIN32(GetLastError());
406 cocreate_flags
|= CLSCTX_ENABLE_CLOAKING
;
409 hr
= ::CoCreateInstance(CLSID_InternetExplorer
, NULL
,
410 cocreate_flags
, IID_IWebBrowser2
,
411 reinterpret_cast<void**>(web_browser
));
412 // ~LowIntegrityToken() will switch integrity back to medium.
416 std::wstring
GetExeVersion(const std::wstring
& exe_path
) {
417 scoped_ptr
<FileVersionInfo
> ie_version_info(
418 FileVersionInfo::CreateFileVersionInfo(FilePath(exe_path
)));
419 return ie_version_info
->product_version();
422 IEVersion
GetInstalledIEVersion() {
423 std::wstring path
= chrome_frame_test::GetExecutableAppPath(kIEImageName
);
424 std::wstring version
= GetExeVersion(path
);
426 switch (version
[0]) {
441 return IE_UNSUPPORTED
;
444 FilePath
GetProfilePathForIE() {
445 FilePath profile_path
;
446 // Browsers without IDeleteBrowsingHistory in non-priv mode
447 // have their profiles moved into "Temporary Internet Files".
448 // The code below basically retrieves the version of IE and computes
449 // the profile directory accordingly.
450 if (GetInstalledIEVersion() <= IE_7
) {
451 profile_path
= GetIETemporaryFilesFolder();
452 profile_path
= profile_path
.Append(L
"Google Chrome Frame");
454 GetChromeFrameProfilePath(kIEProfileName
, &profile_path
);
459 FilePath
GetTestDataFolder() {
461 PathService::Get(base::DIR_SOURCE_ROOT
, &test_dir
);
462 test_dir
= test_dir
.Append(FILE_PATH_LITERAL("chrome_frame"))
463 .Append(FILE_PATH_LITERAL("test"))
464 .Append(FILE_PATH_LITERAL("data"));
468 FilePath
GetSeleniumTestFolder() {
470 PathService::Get(base::DIR_SOURCE_ROOT
, &test_dir
);
471 test_dir
= test_dir
.Append(FILE_PATH_LITERAL("data"))
472 .Append(FILE_PATH_LITERAL("selenium_core"));
476 std::wstring
GetPathFromUrl(const std::wstring
& url
) {
477 string16 url16
= WideToUTF16(url
);
478 GURL gurl
= GURL(url16
);
479 if (gurl
.has_query()) {
480 GURL::Replacements replacements
;
481 replacements
.ClearQuery();
482 gurl
= gurl
.ReplaceComponents(replacements
);
484 return UTF8ToWide(gurl
.PathForRequest());
487 std::wstring
GetPathAndQueryFromUrl(const std::wstring
& url
) {
488 string16 url16
= WideToUTF16(url
);
489 GURL gurl
= GURL(url16
);
490 return UTF8ToWide(gurl
.PathForRequest());
493 std::wstring
GetClipboardText() {
495 ui::Clipboard::GetForCurrentThread()->ReadText(
496 ui::Clipboard::BUFFER_STANDARD
, &text16
);
497 return UTF16ToWide(text16
);
500 void SetClipboardText(const std::wstring
& text
) {
501 ui::ScopedClipboardWriter
clipboard_writer(
502 ui::Clipboard::GetForCurrentThread(),
503 ui::Clipboard::BUFFER_STANDARD
);
504 clipboard_writer
.WriteText(WideToUTF16(text
));
507 bool AddCFMetaTag(std::string
* html_data
) {
512 std::string lower
= StringToLowerASCII(*html_data
);
513 size_t head
= lower
.find("<head>");
514 if (head
== std::string::npos
) {
515 // Add missing head section.
516 size_t html
= lower
.find("<html>");
517 if (html
!= std::string::npos
) {
518 head
= html
+ strlen("<html>");
519 html_data
->insert(head
, "<head></head>");
521 LOG(ERROR
) << "Meta tag will not be injected "
522 << "because the html tag could not be found";
525 if (head
!= std::string::npos
) {
527 head
+ strlen("<head>"),
528 "<meta http-equiv=\"x-ua-compatible\" content=\"chrome=1\" />");
530 return head
!= std::string::npos
;
533 CloseIeAtEndOfScope::~CloseIeAtEndOfScope() {
534 int closed
= CloseAllIEWindows();
535 LOG_IF(ERROR
, closed
!= 0) << "Closed " << closed
<< " windows forcefully";
538 // Attempt to connect to a running crash_service instance. Success occurs if we
539 // can actually connect to the service's pipe or we receive ERROR_PIPE_BUSY.
540 // Waits up to |timeout_ms| for success. |timeout_ms| may be 0, meaning only try
541 // once, or negative, meaning wait forever.
542 bool DetectRunningCrashService(int timeout_ms
) {
543 // Wait for the crash_service.exe to be ready for clients.
544 base::Time start
= base::Time::Now();
545 base::win::ScopedHandle new_pipe
;
548 new_pipe
.Set(::CreateFile(kCrashServicePipeName
,
549 kCrashServicePipeDesiredAccess
,
551 NULL
, // lpSecurityAttributes
553 kCrashServicePipeFlagsAndAttributes
,
554 NULL
)); // hTemplateFile
556 if (new_pipe
.IsValid()) {
560 switch (::GetLastError()) {
561 case ERROR_PIPE_BUSY
:
562 // OK, it exists, let's assume that clients will eventually be able to
565 case ERROR_FILE_NOT_FOUND
:
569 DPLOG(WARNING
) << "Unexpected error while checking crash_service.exe's "
571 // Go ahead and wait in case it clears up.
575 if (timeout_ms
== 0) {
577 } else if (timeout_ms
> 0) {
578 base::TimeDelta duration
= base::Time::Now() - start
;
579 if (duration
.InMilliseconds() > timeout_ms
) {
588 base::ProcessHandle
StartCrashService() {
589 if (DetectRunningCrashService(kCrashServiceStartupTimeoutMs
)) {
590 VLOG(1) << "crash_service.exe is already running. We will use the "
591 "existing process and leave it running after tests complete.";
596 if (!PathService::Get(base::DIR_EXE
, &exe_dir
)) {
601 base::ProcessHandle crash_service
= NULL
;
603 VLOG(1) << "Starting crash_service.exe so you know if a test crashes!";
605 FilePath crash_service_path
= exe_dir
.AppendASCII("crash_service.exe");
606 if (!base::LaunchProcess(crash_service_path
.value(), base::LaunchOptions(),
608 LOG(ERROR
) << "Couldn't start crash_service.exe";
612 base::Time start
= base::Time::Now();
614 if (DetectRunningCrashService(kCrashServiceStartupTimeoutMs
)) {
615 VLOG(1) << "crash_service.exe is ready for clients in "
616 << (base::Time::Now() - start
).InMilliseconds() << " ms.";
617 return crash_service
;
619 LOG(ERROR
) << "crash_service.exe failed to accept client connections "
620 "within " << kCrashServiceStartupTimeoutMs
<< " ms. "
621 "Terminating it now.";
623 // First check to see if it's even still running just to minimize the
624 // likelihood of spurious error messages from KillProcess.
625 if (WAIT_OBJECT_0
!= ::WaitForSingleObject(crash_service
, 0)) {
626 base::KillProcess(crash_service
, 0, false);
632 ScopedVirtualizeHklmAndHkcu::ScopedVirtualizeHklmAndHkcu() {
633 override_manager_
.OverrideRegistry(HKEY_LOCAL_MACHINE
, L
"hklm_fake");
634 override_manager_
.OverrideRegistry(HKEY_CURRENT_USER
, L
"hkcu_fake");
637 ScopedVirtualizeHklmAndHkcu::~ScopedVirtualizeHklmAndHkcu() {
640 void ScopedVirtualizeHklmAndHkcu::RemoveAllOverrides() {
641 override_manager_
.RemoveAllOverrides();
644 bool KillProcesses(const std::wstring
& executable_name
, int exit_code
,
647 base::NamedProcessIterator
iter(executable_name
, NULL
);
648 while (const base::ProcessEntry
* entry
= iter
.NextProcessEntry()) {
649 result
&= base::KillProcessById(entry
->pid(), exit_code
, wait
);
654 ScopedChromeFrameRegistrar::RegistrationType
GetTestBedType() {
655 if (GetConfigBool(false, L
"PerUserTestBed")) {
656 return ScopedChromeFrameRegistrar::PER_USER
;
658 return ScopedChromeFrameRegistrar::SYSTEM_LEVEL
;
662 void ClearIESessionHistory() {
663 FilePath session_history_path
;
664 if (!PathService::Get(base::DIR_LOCAL_APP_DATA
, &session_history_path
))
667 session_history_path
= session_history_path
.AppendASCII("Microsoft");
668 session_history_path
= session_history_path
.AppendASCII("Internet Explorer");
669 session_history_path
= session_history_path
.AppendASCII("Recovery");
670 file_util::Delete(session_history_path
, true);
673 std::string
GetLocalIPv4Address() {
675 net::NetworkInterfaceList nic_list
;
677 if (!net::GetNetworkList(&nic_list
)) {
678 LOG(ERROR
) << "GetNetworkList failed to look up non-loopback adapters. "
679 << "Tests will be run over the loopback adapter, which may "
680 << "result in hangs.";
682 // GetNetworkList only returns 'Up' non-loopback adapters. Select the first
683 // IPv4 address found - we should be able to bind/connect over it.
684 for (size_t i
= 0; i
< nic_list
.size(); ++i
) {
685 if (nic_list
[i
].address
.size() != net::kIPv4AddressSize
)
687 char* address_string
=
688 inet_ntoa(*reinterpret_cast<in_addr
*>(&nic_list
[i
].address
[0]));
689 DCHECK(address_string
!= NULL
);
690 if (address_string
!= NULL
) {
691 LOG(INFO
) << "HTTP tests will run over " << address_string
<< ".";
692 address
.assign(address_string
);
698 if (address
.empty()) {
699 LOG(ERROR
) << "Failed to find a non-loopback IP_V4 address. Tests will be "
700 << "run over the loopback adapter, which may result in hangs.";
701 address
.assign("127.0.0.1");
707 } // namespace chrome_frame_test